From 5337b5c3cadec9caffd460f974b4569fd636c88f Mon Sep 17 00:00:00 2001 From: CloudWebRTC Date: Mon, 29 Jul 2024 16:23:25 +0800 Subject: [PATCH 001/274] Fix broken examples. (#382) * Fix broken examples. * update. --- examples/api/Cargo.toml | 2 +- examples/basic_room/Cargo.toml | 4 ++-- examples/basic_room/src/main.rs | 4 ++-- examples/basic_room_async/Cargo.toml | 4 ++-- examples/basic_room_dispatcher/Cargo.toml | 4 ++-- examples/mobile/Cargo.toml | 4 ++-- examples/mobile/src/lib.rs | 2 +- examples/play_from_disk/Cargo.toml | 2 +- examples/save_to_disk/Cargo.toml | 2 +- examples/save_to_disk/src/main.rs | 2 +- examples/webhooks/Cargo.toml | 2 +- examples/wgpu_room/Cargo.toml | 2 +- examples/wgpu_room/src/app.rs | 24 +++++++++++------------ examples/wgpu_room/src/service.rs | 6 +++--- examples/wgpu_room/src/sine_track.rs | 1 + 15 files changed, 33 insertions(+), 32 deletions(-) diff --git a/examples/api/Cargo.toml b/examples/api/Cargo.toml index 7b1e70870..61167c8b5 100644 --- a/examples/api/Cargo.toml +++ b/examples/api/Cargo.toml @@ -5,6 +5,6 @@ edition = "2021" [dependencies] tokio = { version = "1", features = ["full", "parking_lot"] } -livekit-api = { path = "../../livekit-api", version = "0.3.2", features = ["native-tls"] } +livekit-api = { path = "../../livekit-api", features = ["native-tls"] } futures = "0.3" diff --git a/examples/basic_room/Cargo.toml b/examples/basic_room/Cargo.toml index 3d59ad2b3..1b4c70dec 100644 --- a/examples/basic_room/Cargo.toml +++ b/examples/basic_room/Cargo.toml @@ -6,6 +6,6 @@ edition = "2021" [dependencies] tokio = { version = "1", features = ["full"] } env_logger = "0.10" -livekit = { path = "../../livekit", version = "0.3.2", features = ["native-tls"]} -livekit-api = { path = "../../livekit-api", version = "0.3.2"} +livekit = { path = "../../livekit", features = ["native-tls"]} +livekit-api = { path = "../../livekit-api"} log = "0.4" diff --git a/examples/basic_room/src/main.rs b/examples/basic_room/src/main.rs index d83c04402..b023feb57 100644 --- a/examples/basic_room/src/main.rs +++ b/examples/basic_room/src/main.rs @@ -27,12 +27,12 @@ async fn main() { let (room, mut rx) = Room::connect(&url, &token, RoomOptions::default()) .await .unwrap(); - log::info!("Connected to room: {} - {}", room.name(), room.sid()); + 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(), - kind: DataPacketKind::Reliable, + reliable: true, ..Default::default() }) .await diff --git a/examples/basic_room_async/Cargo.toml b/examples/basic_room_async/Cargo.toml index aba602eec..957eab51e 100644 --- a/examples/basic_room_async/Cargo.toml +++ b/examples/basic_room_async/Cargo.toml @@ -7,8 +7,8 @@ edition = "2021" futures = "0.3.0" async-std = "1" env_logger = "0.10" -livekit = { path = "../../livekit", version = "0.3.0", default-features = false, features = ["native-tls", "async"]} -livekit-api = { path = "../../livekit-api", version = "0.3.0"} +livekit = { path = "../../livekit", default-features = false, features = ["native-tls", "async"]} +livekit-api = { path = "../../livekit-api" } log = "0.4" [workspace] \ No newline at end of file diff --git a/examples/basic_room_dispatcher/Cargo.toml b/examples/basic_room_dispatcher/Cargo.toml index 6f2c961c7..291bea2f7 100644 --- a/examples/basic_room_dispatcher/Cargo.toml +++ b/examples/basic_room_dispatcher/Cargo.toml @@ -7,8 +7,8 @@ edition = "2021" futures = "0.3.0" smol = "2.0.0" env_logger = "0.10" -livekit = { path = "../../livekit", version = "0.3.0", default-features = false, features = ["native-tls", "dispatcher"]} -livekit-api = { path = "../../livekit-api", version = "0.3.0"} +livekit = { path = "../../livekit", default-features = false, features = ["native-tls", "dispatcher"]} +livekit-api = { path = "../../livekit-api" } log = "0.4" [workspace] \ No newline at end of file diff --git a/examples/mobile/Cargo.toml b/examples/mobile/Cargo.toml index 50882bc2a..57cfcdbf8 100644 --- a/examples/mobile/Cargo.toml +++ b/examples/mobile/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [dependencies] lazy_static = "1.4.0" -livekit = { path = "../../livekit", version = "0.3.2", features = ["rustls-tls-webpki-roots"] } +livekit = { path = "../../livekit", features = ["rustls-tls-webpki-roots"] } log = "0.4.19" tokio = { version = "1", features = ["full"] } @@ -14,7 +14,7 @@ jni = "0.21.1" android_logger = "0.13.1" [build-dependencies] -webrtc-sys-build = { path = "../../webrtc-sys/build", version = "0.3.2" } +webrtc-sys-build = { path = "../../webrtc-sys/build" } [lib] crate-type = ["cdylib"] diff --git a/examples/mobile/src/lib.rs b/examples/mobile/src/lib.rs index 0f7611a0e..bee4af745 100644 --- a/examples/mobile/src/lib.rs +++ b/examples/mobile/src/lib.rs @@ -32,7 +32,7 @@ pub fn livekit_connect(url: String, token: String) { } let (room, mut events) = res.unwrap(); - log::info!("Connected to room {}", room.sid()); + log::info!("Connected to room {}", String::from(room.sid().await)); while let Some(event) = events.recv().await { log::info!("Received event {:?}", event); diff --git a/examples/play_from_disk/Cargo.toml b/examples/play_from_disk/Cargo.toml index 11a816527..aa50dac9f 100644 --- a/examples/play_from_disk/Cargo.toml +++ b/examples/play_from_disk/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" [dependencies] tokio = { version = "1", features = ["full"] } -livekit = { path = "../../livekit", version = "0.3.2" } +livekit = { path = "../../livekit" } thiserror = "1.0.47" log = "0.4.20" env_logger = "0.10.0" diff --git a/examples/save_to_disk/Cargo.toml b/examples/save_to_disk/Cargo.toml index 22521f18c..ca0ac59b5 100644 --- a/examples/save_to_disk/Cargo.toml +++ b/examples/save_to_disk/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" [dependencies] tokio = { version = "1", features = ["full"] } -livekit = { path = "../../livekit", version = "0.3.2" } +livekit = { path = "../../livekit" } bytes = "1.4.0" tokio-util = "0.7.8" futures = "0.3.28" diff --git a/examples/save_to_disk/src/main.rs b/examples/save_to_disk/src/main.rs index 34b38e8e5..d378dedce 100644 --- a/examples/save_to_disk/src/main.rs +++ b/examples/save_to_disk/src/main.rs @@ -92,7 +92,7 @@ async fn main() { let (room, mut rx) = Room::connect(&url, &token, RoomOptions::default()) .await .unwrap(); - println!("Connected to room: {} - {}", room.name(), room.sid()); + println!("Connected to room: {} - {}", room.name(), String::from(room.sid().await)); while let Some(msg) = rx.recv().await { #[allow(clippy::single_match)] diff --git a/examples/webhooks/Cargo.toml b/examples/webhooks/Cargo.toml index c4e688976..b2686079c 100644 --- a/examples/webhooks/Cargo.toml +++ b/examples/webhooks/Cargo.toml @@ -4,6 +4,6 @@ version = "0.1.0" edition = "2021" [dependencies] -livekit-api = { version = "0.3.2", path = "../../livekit-api" } +livekit-api = { path = "../../livekit-api" } hyper = { version = "0.14", features = ["full"] } tokio = { version = "1", features = ["full"] } diff --git a/examples/wgpu_room/Cargo.toml b/examples/wgpu_room/Cargo.toml index 73e5fb6e0..e0d210a5a 100644 --- a/examples/wgpu_room/Cargo.toml +++ b/examples/wgpu_room/Cargo.toml @@ -9,7 +9,7 @@ tracing = ["console-subscriber", "tokio/tracing"] [dependencies] tokio = { version = "1", features = ["full", "parking_lot"] } -livekit = { path = "../../livekit", version = "0.3.2", features = ["native-tls"] } +livekit = { path = "../../livekit", features = ["native-tls"] } futures = "0.3" winit = "0.28" parking_lot = { version = "0.12.1", features = ["deadlock_detection"] } diff --git a/examples/wgpu_room/src/app.rs b/examples/wgpu_room/src/app.rs index 159614861..99001f27a 100644 --- a/examples/wgpu_room/src/app.rs +++ b/examples/wgpu_room/src/app.rs @@ -21,7 +21,7 @@ struct AppState { pub struct LkApp { async_runtime: tokio::runtime::Runtime, state: AppState, - video_renderers: HashMap<(ParticipantSid, TrackSid), VideoRenderer>, + video_renderers: HashMap<(ParticipantIdentity, TrackSid), VideoRenderer>, connecting: bool, connection_failure: Option, render_state: egui_wgpu::RenderState, @@ -87,7 +87,7 @@ impl LkApp { video_track.rtc_track(), ); self.video_renderers - .insert((participant.sid(), track.sid()), video_renderer); + .insert((participant.identity(), track.sid()), video_renderer); } else if let RemoteTrack::Audio(_) = track { // TODO(theomonnom): Once we support media devices, we can play audio tracks here } @@ -98,7 +98,7 @@ impl LkApp { participant, } => { self.video_renderers - .remove(&(participant.sid(), track.sid())); + .remove(&(participant.identity(), track.sid())); } RoomEvent::LocalTrackPublished { track, @@ -113,7 +113,7 @@ impl LkApp { video_track.rtc_track(), ); self.video_renderers - .insert((participant.sid(), track.sid()), video_renderer); + .insert((participant.identity(), track.sid()), video_renderer); } } RoomEvent::LocalTrackUnpublished { @@ -121,7 +121,7 @@ impl LkApp { participant, } => { self.video_renderers - .remove(&(participant.sid(), publication.sid())); + .remove(&(participant.identity(), publication.sid())); } RoomEvent::Disconnected { reason: _ } => { self.video_renderers.clear(); @@ -238,11 +238,11 @@ impl LkApp { if let Some(room) = room.as_ref() { ui.label(format!("Name: {}", room.name())); - ui.label(format!("Sid: {}", room.sid())); + //ui.label(format!("Sid: {}", String::from(room.sid().await))); ui.label(format!("ConnectionState: {:?}", room.connection_state())); ui.label(format!( "ParticipantCount: {:?}", - room.participants().len() + 1 + room.remote_participants().len() + 1 )); } @@ -250,7 +250,7 @@ impl LkApp { ui.separator(); } - /// Show participants and their tracks + /// Show remote_participants and their tracks fn right_panel(&self, ui: &mut egui::Ui) { ui.label("Participants"); ui.separator(); @@ -261,16 +261,16 @@ impl LkApp { egui::ScrollArea::vertical().show(ui, |ui| { // Iterate with sorted keys to avoid flickers (Because this is a immediate mode UI) - let participants = room.participants(); + let participants = room.remote_participants(); let mut sorted_participants = participants .keys() .cloned() - .collect::>(); + .collect::>(); sorted_participants.sort_by(|a, b| a.as_str().cmp(b.as_str())); for psid in sorted_participants { let participant = participants.get(&psid).unwrap(); - let tracks = participant.tracks(); + let tracks = participant.track_publications(); let mut sorted_tracks = tracks.keys().cloned().collect::>(); sorted_tracks.sort_by(|a, b| a.as_str().cmp(b.as_str())); @@ -343,7 +343,7 @@ impl LkApp { ui.video_frame(|ui| { let room = room.as_ref().unwrap().clone(); - if let Some(participant) = room.participants().get(participant_sid) + if let Some(participant) = room.remote_participants().get(participant_sid) { draw_video( participant.name().as_str(), diff --git a/examples/wgpu_room/src/service.rs b/examples/wgpu_room/src/service.rs index 1d1d3653c..9d3a26278 100644 --- a/examples/wgpu_room/src/service.rs +++ b/examples/wgpu_room/src/service.rs @@ -202,7 +202,7 @@ async fn service_task(inner: Arc, mut cmd_rx: mpsc::UnboundedRecei } AsyncCmd::LogStats => { if let Some(state) = running_state.as_ref() { - for (_, publication) in state.room.local_participant().tracks() { + for (_, publication) in state.room.local_participant().track_publications() { if let Some(track) = publication.track() { log::info!( "track stats: LOCAL {:?} {:?}", @@ -212,8 +212,8 @@ async fn service_task(inner: Arc, mut cmd_rx: mpsc::UnboundedRecei } } - for (_, participant) in state.room.participants() { - for (_, publication) in participant.tracks() { + for (_, participant) in state.room.remote_participants() { + for (_, publication) in participant.track_publications() { if let Some(track) = publication.track() { log::info!( "track stats: {:?} {:?} {:?}", diff --git a/examples/wgpu_room/src/sine_track.rs b/examples/wgpu_room/src/sine_track.rs index c21a9aaf8..becaf8217 100644 --- a/examples/wgpu_room/src/sine_track.rs +++ b/examples/wgpu_room/src/sine_track.rs @@ -46,6 +46,7 @@ impl SineTrack { AudioSourceOptions::default(), params.sample_rate, params.num_channels, + None, ), params, room, From 1edece0f0ee6c9f3c05c6c1ec2669240ec4874b5 Mon Sep 17 00:00:00 2001 From: CloudWebRTC Date: Fri, 2 Aug 2024 17:09:01 +0800 Subject: [PATCH 002/274] Using rustls-tls-webpki-roots flag for iOS ffi build. (#384) --- .github/workflows/ffi-builds.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ffi-builds.yml b/.github/workflows/ffi-builds.yml index d2ed3641f..66ccc1418 100644 --- a/.github/workflows/ffi-builds.yml +++ b/.github/workflows/ffi-builds.yml @@ -58,12 +58,14 @@ jobs: target: aarch64-apple-ios iphoneos_deployment_target: "13.0" name: ffi-ios-arm64 + buildargs: --no-default-features --features "rustls-tls-webpki-roots" - os: macos-13 platform: ios dylib: liblivekit_ffi.a target: aarch64-apple-ios-sim iphoneos_deployment_target: "13.0" name: ffi-ios-sim-arm64 + buildargs: --no-default-features --features "rustls-tls-webpki-roots" - os: ubuntu-20.04 platform: linux build_image: quay.io/pypa/manylinux_2_28_x86_64 From 8045919f5a558d9c62bfd30db1cb0302cb3d1c83 Mon Sep 17 00:00:00 2001 From: aoife cassidy Date: Fri, 2 Aug 2024 10:26:55 -0700 Subject: [PATCH 003/274] livekit-ffi: add DisconnectReason --- livekit-ffi/protocol/room.proto | 28 ++++- livekit-ffi/src/conversion/room.rs | 18 +++ livekit-ffi/src/livekit.proto.rs | 66 ++++++++++- livekit-ffi/src/server/room.rs | 6 +- livekit-protocol/protocol | 2 +- livekit-protocol/src/livekit.rs | 159 +++++++++++++++----------- livekit-protocol/src/livekit.serde.rs | 78 ++++++++++--- livekit/src/prelude.rs | 4 +- livekit/src/rtc_engine/rtc_session.rs | 1 + 9 files changed, 269 insertions(+), 93 deletions(-) diff --git a/livekit-ffi/protocol/room.proto b/livekit-ffi/protocol/room.proto index b18421aa6..738280e14 100644 --- a/livekit-ffi/protocol/room.proto +++ b/livekit-ffi/protocol/room.proto @@ -268,6 +268,30 @@ enum DataPacketKind { KIND_RELIABLE = 1; } +enum DisconnectReason { + UNKNOWN_REASON = 0; + // the client initiated the disconnect + CLIENT_INITIATED = 1; + // another participant with the same identity has joined the room + DUPLICATE_IDENTITY = 2; + // the server instance is shutting down + SERVER_SHUTDOWN = 3; + // RoomService.RemoveParticipant was called + PARTICIPANT_REMOVED = 4; + // RoomService.DeleteRoom was called + ROOM_DELETED = 5; + // the client is attempting to resume a session, but server is not aware of it + STATE_MISMATCH = 6; + // client was unable to connect fully + JOIN_FAILURE = 7; + // Cloud-only, the server requested Participant to migrate the connection elsewhere + MIGRATION = 8; + // the signal websocket was closed unexpectedly + SIGNAL_CLOSE = 9; + // the room was closed, due to all Standard and Ingress participants having left + ROOM_CLOSED = 10; +} + message TranscriptionSegment { string id = 1; string text = 2; @@ -450,7 +474,9 @@ message TranscriptionReceived { message ConnectionStateChanged { ConnectionState state = 1; } message Connected {} -message Disconnected {} +message Disconnected { + DisconnectReason reason = 1; +} message Reconnecting {} message Reconnected {} diff --git a/livekit-ffi/src/conversion/room.rs b/livekit-ffi/src/conversion/room.rs index e8ce14bf7..8fa7f7e09 100644 --- a/livekit-ffi/src/conversion/room.rs +++ b/livekit-ffi/src/conversion/room.rs @@ -82,6 +82,24 @@ impl From for proto::EncryptionType { } } +impl From for proto::DisconnectReason { + fn from(value: DisconnectReason) -> Self { + match value { + DisconnectReason::UnknownReason => Self::UnknownReason, + DisconnectReason::ClientInitiated => Self::ClientInitiated, + DisconnectReason::DuplicateIdentity => Self::DuplicateIdentity, + DisconnectReason::ServerShutdown => Self::ServerShutdown, + DisconnectReason::ParticipantRemoved => Self::ParticipantRemoved, + DisconnectReason::RoomDeleted => Self::RoomDeleted, + DisconnectReason::StateMismatch => Self::StateMismatch, + DisconnectReason::JoinFailure => Self::JoinFailure, + DisconnectReason::Migration => Self::Migration, + DisconnectReason::SignalClose => Self::SignalClose, + DisconnectReason::RoomClosed => Self::RoomClosed, + } + } +} + impl From for KeyProviderOptions { fn from(value: proto::KeyProviderOptions) -> Self { Self { diff --git a/livekit-ffi/src/livekit.proto.rs b/livekit-ffi/src/livekit.proto.rs index d3cba8d2b..6a3c8a3d1 100644 --- a/livekit-ffi/src/livekit.proto.rs +++ b/livekit-ffi/src/livekit.proto.rs @@ -1,5 +1,4 @@ // @generated -// This file is @generated by prost-build. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FrameCryptor { @@ -2608,6 +2607,8 @@ pub struct Connected { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct Disconnected { + #[prost(enumeration="DisconnectReason", tag="1")] + pub reason: i32, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -2767,6 +2768,69 @@ impl DataPacketKind { } } } +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum DisconnectReason { + UnknownReason = 0, + /// the client initiated the disconnect + ClientInitiated = 1, + /// another participant with the same identity has joined the room + DuplicateIdentity = 2, + /// the server instance is shutting down + ServerShutdown = 3, + /// RoomService.RemoveParticipant was called + ParticipantRemoved = 4, + /// RoomService.DeleteRoom was called + RoomDeleted = 5, + /// the client is attempting to resume a session, but server is not aware of it + StateMismatch = 6, + /// client was unable to connect fully + JoinFailure = 7, + /// Cloud-only, the server requested Participant to migrate the connection elsewhere + Migration = 8, + /// the signal websocket was closed unexpectedly + SignalClose = 9, + /// the room was closed, due to all Standard and Ingress participants having left + RoomClosed = 10, +} +impl DisconnectReason { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + DisconnectReason::UnknownReason => "UNKNOWN_REASON", + DisconnectReason::ClientInitiated => "CLIENT_INITIATED", + DisconnectReason::DuplicateIdentity => "DUPLICATE_IDENTITY", + DisconnectReason::ServerShutdown => "SERVER_SHUTDOWN", + DisconnectReason::ParticipantRemoved => "PARTICIPANT_REMOVED", + DisconnectReason::RoomDeleted => "ROOM_DELETED", + DisconnectReason::StateMismatch => "STATE_MISMATCH", + DisconnectReason::JoinFailure => "JOIN_FAILURE", + DisconnectReason::Migration => "MIGRATION", + DisconnectReason::SignalClose => "SIGNAL_CLOSE", + DisconnectReason::RoomClosed => "ROOM_CLOSED", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "UNKNOWN_REASON" => Some(Self::UnknownReason), + "CLIENT_INITIATED" => Some(Self::ClientInitiated), + "DUPLICATE_IDENTITY" => Some(Self::DuplicateIdentity), + "SERVER_SHUTDOWN" => Some(Self::ServerShutdown), + "PARTICIPANT_REMOVED" => Some(Self::ParticipantRemoved), + "ROOM_DELETED" => Some(Self::RoomDeleted), + "STATE_MISMATCH" => Some(Self::StateMismatch), + "JOIN_FAILURE" => Some(Self::JoinFailure), + "MIGRATION" => Some(Self::Migration), + "SIGNAL_CLOSE" => Some(Self::SignalClose), + "ROOM_CLOSED" => Some(Self::RoomClosed), + _ => None, + } + } +} /// Create a new AudioStream /// AudioStream is used to receive audio frames from a track #[allow(clippy::derive_partial_eq_without_eq)] diff --git a/livekit-ffi/src/server/room.rs b/livekit-ffi/src/server/room.rs index b8e268837..9785d879c 100644 --- a/livekit-ffi/src/server/room.rs +++ b/livekit-ffi/src/server/room.rs @@ -964,8 +964,10 @@ async fn forward_event( RoomEvent::Connected { .. } => { // Ignore here, we're already sent the event on connect (see above) } - RoomEvent::Disconnected { reason: _ } => { - let _ = send_event(proto::room_event::Message::Disconnected(proto::Disconnected {})); + RoomEvent::Disconnected { reason } => { + let _ = send_event(proto::room_event::Message::Disconnected(proto::Disconnected { + reason: proto::DisconnectReason::from(reason).into(), + })); } RoomEvent::Reconnecting => { present_state.lock().reconnecting = true; diff --git a/livekit-protocol/protocol b/livekit-protocol/protocol index 9e23fdbd0..5a524703e 160000 --- a/livekit-protocol/protocol +++ b/livekit-protocol/protocol @@ -1 +1 @@ -Subproject commit 9e23fdbd08859769a29ea4319a973e35ac01d0bc +Subproject commit 5a524703ead68a9946efc6e2a4d62203ec688fbe diff --git a/livekit-protocol/src/livekit.rs b/livekit-protocol/src/livekit.rs index bc52a7540..f815e547f 100644 --- a/livekit-protocol/src/livekit.rs +++ b/livekit-protocol/src/livekit.rs @@ -995,15 +995,26 @@ impl ClientConfigSetting { #[repr(i32)] pub enum DisconnectReason { UnknownReason = 0, + /// the client initiated the disconnect ClientInitiated = 1, + /// another participant with the same identity has joined the room DuplicateIdentity = 2, + /// the server instance is shutting down ServerShutdown = 3, + /// RoomService.RemoveParticipant was called ParticipantRemoved = 4, + /// RoomService.DeleteRoom was called RoomDeleted = 5, + /// the client is attempting to resume a session, but server is not aware of it StateMismatch = 6, + /// client was unable to connect fully JoinFailure = 7, + /// Cloud-only, the server requested Participant to migrate the connection elsewhere Migration = 8, + /// the signal websocket was closed unexpectedly SignalClose = 9, + /// the room was closed, due to all Standard and Ingress participants having left + RoomClosed = 10, } impl DisconnectReason { /// String value of the enum field names used in the ProtoBuf definition. @@ -1022,6 +1033,7 @@ impl DisconnectReason { DisconnectReason::JoinFailure => "JOIN_FAILURE", DisconnectReason::Migration => "MIGRATION", DisconnectReason::SignalClose => "SIGNAL_CLOSE", + DisconnectReason::RoomClosed => "ROOM_CLOSED", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1037,6 +1049,7 @@ impl DisconnectReason { "JOIN_FAILURE" => Some(Self::JoinFailure), "MIGRATION" => Some(Self::Migration), "SIGNAL_CLOSE" => Some(Self::SignalClose), + "ROOM_CLOSED" => Some(Self::RoomClosed), _ => None, } } @@ -2006,6 +2019,7 @@ pub enum StreamProtocol { /// protocol chosen based on urls DefaultProtocol = 0, Rtmp = 1, + Srt = 2, } impl StreamProtocol { /// String value of the enum field names used in the ProtoBuf definition. @@ -2016,6 +2030,7 @@ impl StreamProtocol { match self { StreamProtocol::DefaultProtocol => "DEFAULT_PROTOCOL", StreamProtocol::Rtmp => "RTMP", + StreamProtocol::Srt => "SRT", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -2023,6 +2038,7 @@ impl StreamProtocol { match value { "DEFAULT_PROTOCOL" => Some(Self::DefaultProtocol), "RTMP" => Some(Self::Rtmp), + "SRT" => Some(Self::Srt), _ => None, } } @@ -2323,6 +2339,8 @@ pub struct TrickleRequest { pub candidate_init: ::prost::alloc::string::String, #[prost(enumeration="SignalTarget", tag="2")] pub target: i32, + #[prost(bool, tag="3")] + pub r#final: bool, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -2892,75 +2910,11 @@ impl CandidateProtocol { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] -pub struct CreateAgentDispatchRequest { - #[prost(string, tag="1")] - pub agent_name: ::prost::alloc::string::String, - #[prost(string, tag="2")] - pub room: ::prost::alloc::string::String, - #[prost(string, tag="3")] - pub metadata: ::prost::alloc::string::String, -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct RoomAgentDispatch { - #[prost(string, tag="1")] - pub agent_name: ::prost::alloc::string::String, - #[prost(string, tag="2")] - pub metadata: ::prost::alloc::string::String, -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct DeleteAgentDispatchRequest { - #[prost(string, tag="1")] - pub dispatch_id: ::prost::alloc::string::String, -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct ListAgentDispatchRequesst { - /// if set, only the dispatch whose id is given will be returned - #[prost(string, tag="1")] - pub dispatch_id: ::prost::alloc::string::String, - /// name of the room to list agents for. Must be set. - #[prost(string, tag="2")] - pub room: ::prost::alloc::string::String, -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct ListAgentDispatchResponse { - #[prost(message, repeated, tag="1")] - pub agent_dispatches: ::prost::alloc::vec::Vec, -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct AgentDispatch { - #[prost(string, tag="1")] - pub id: ::prost::alloc::string::String, - #[prost(string, tag="2")] - pub agent_name: ::prost::alloc::string::String, - #[prost(string, tag="3")] - pub room: ::prost::alloc::string::String, - #[prost(string, tag="4")] - pub metadata: ::prost::alloc::string::String, - #[prost(message, optional, tag="5")] - pub state: ::core::option::Option, -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct AgentDispatchState { - /// For dispatches of tyoe JT_ROOM, there will be at most 1 job. - /// For dispatches of type JT_PUBLISHER, there will be 1 per publisher. - #[prost(message, repeated, tag="1")] - pub jobs: ::prost::alloc::vec::Vec, - #[prost(int64, tag="2")] - pub created_at: i64, - #[prost(int64, tag="3")] - pub deleted_at: i64, -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] pub struct Job { #[prost(string, tag="1")] pub id: ::prost::alloc::string::String, + #[prost(string, tag="9")] + pub dispatch_id: ::prost::alloc::string::String, #[prost(enumeration="JobType", tag="2")] pub r#type: i32, #[prost(message, optional, tag="3")] @@ -3083,8 +3037,7 @@ pub struct RegisterWorkerRequest { /// string worker_id = 2; #[prost(string, tag="3")] pub version: ::prost::alloc::string::String, - #[prost(string, tag="4")] - pub name: ::prost::alloc::string::String, + /// string name = 4 \[deprecated = true\]; #[prost(uint32, tag="5")] pub ping_interval: u32, #[prost(string, optional, tag="6")] @@ -3112,7 +3065,7 @@ pub struct MigrateJobRequest { pub struct AvailabilityRequest { #[prost(message, optional, tag="1")] pub job: ::core::option::Option, - /// True when the job was previously assigned to another worker but has been + /// True when the job was previously assigned to another worker but has been /// migrated due to different reasons (e.g. worker failure, job migration) #[prost(bool, tag="2")] pub resuming: bool, @@ -3132,6 +3085,8 @@ pub struct AvailabilityResponse { pub participant_identity: ::prost::alloc::string::String, #[prost(string, tag="6")] pub participant_metadata: ::prost::alloc::string::String, + #[prost(map="string, string", tag="7")] + pub participant_attributes: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -3258,6 +3213,72 @@ impl JobStatus { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct CreateAgentDispatchRequest { + #[prost(string, tag="1")] + pub agent_name: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub room: ::prost::alloc::string::String, + #[prost(string, tag="3")] + pub metadata: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RoomAgentDispatch { + #[prost(string, tag="1")] + pub agent_name: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub metadata: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DeleteAgentDispatchRequest { + #[prost(string, tag="1")] + pub dispatch_id: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ListAgentDispatchRequesst { + /// if set, only the dispatch whose id is given will be returned + #[prost(string, tag="1")] + pub dispatch_id: ::prost::alloc::string::String, + /// name of the room to list agents for. Must be set. + #[prost(string, tag="2")] + pub room: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ListAgentDispatchResponse { + #[prost(message, repeated, tag="1")] + pub agent_dispatches: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct AgentDispatch { + #[prost(string, tag="1")] + pub id: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub agent_name: ::prost::alloc::string::String, + #[prost(string, tag="3")] + pub room: ::prost::alloc::string::String, + #[prost(string, tag="4")] + pub metadata: ::prost::alloc::string::String, + #[prost(message, optional, tag="5")] + pub state: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct AgentDispatchState { + /// For dispatches of tyoe JT_ROOM, there will be at most 1 job. + /// For dispatches of type JT_PUBLISHER, there will be 1 per publisher. + #[prost(message, repeated, tag="1")] + pub jobs: ::prost::alloc::vec::Vec, + #[prost(int64, tag="2")] + pub created_at: i64, + #[prost(int64, tag="3")] + pub deleted_at: i64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct CreateRoomRequest { /// name of the room #[prost(string, tag="1")] diff --git a/livekit-protocol/src/livekit.serde.rs b/livekit-protocol/src/livekit.serde.rs index 918fddc87..d162333eb 100644 --- a/livekit-protocol/src/livekit.serde.rs +++ b/livekit-protocol/src/livekit.serde.rs @@ -1524,6 +1524,9 @@ impl serde::Serialize for AvailabilityResponse { if !self.participant_metadata.is_empty() { len += 1; } + if !self.participant_attributes.is_empty() { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.AvailabilityResponse", len)?; if !self.job_id.is_empty() { struct_ser.serialize_field("jobId", &self.job_id)?; @@ -1543,6 +1546,9 @@ impl serde::Serialize for AvailabilityResponse { if !self.participant_metadata.is_empty() { struct_ser.serialize_field("participantMetadata", &self.participant_metadata)?; } + if !self.participant_attributes.is_empty() { + struct_ser.serialize_field("participantAttributes", &self.participant_attributes)?; + } struct_ser.end() } } @@ -1564,6 +1570,8 @@ impl<'de> serde::Deserialize<'de> for AvailabilityResponse { "participantIdentity", "participant_metadata", "participantMetadata", + "participant_attributes", + "participantAttributes", ]; #[allow(clippy::enum_variant_names)] @@ -1574,6 +1582,7 @@ impl<'de> serde::Deserialize<'de> for AvailabilityResponse { ParticipantName, ParticipantIdentity, ParticipantMetadata, + ParticipantAttributes, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -1602,6 +1611,7 @@ impl<'de> serde::Deserialize<'de> for AvailabilityResponse { "participantName" | "participant_name" => Ok(GeneratedField::ParticipantName), "participantIdentity" | "participant_identity" => Ok(GeneratedField::ParticipantIdentity), "participantMetadata" | "participant_metadata" => Ok(GeneratedField::ParticipantMetadata), + "participantAttributes" | "participant_attributes" => Ok(GeneratedField::ParticipantAttributes), _ => Ok(GeneratedField::__SkipField__), } } @@ -1627,6 +1637,7 @@ impl<'de> serde::Deserialize<'de> for AvailabilityResponse { let mut participant_name__ = None; let mut participant_identity__ = None; let mut participant_metadata__ = None; + let mut participant_attributes__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::JobId => { @@ -1665,6 +1676,14 @@ impl<'de> serde::Deserialize<'de> for AvailabilityResponse { } participant_metadata__ = Some(map_.next_value()?); } + GeneratedField::ParticipantAttributes => { + if participant_attributes__.is_some() { + return Err(serde::de::Error::duplicate_field("participantAttributes")); + } + participant_attributes__ = Some( + map_.next_value::>()? + ); + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -1677,6 +1696,7 @@ impl<'de> serde::Deserialize<'de> for AvailabilityResponse { participant_name: participant_name__.unwrap_or_default(), participant_identity: participant_identity__.unwrap_or_default(), participant_metadata: participant_metadata__.unwrap_or_default(), + participant_attributes: participant_attributes__.unwrap_or_default(), }) } } @@ -5805,6 +5825,7 @@ impl serde::Serialize for DisconnectReason { Self::JoinFailure => "JOIN_FAILURE", Self::Migration => "MIGRATION", Self::SignalClose => "SIGNAL_CLOSE", + Self::RoomClosed => "ROOM_CLOSED", }; serializer.serialize_str(variant) } @@ -5826,6 +5847,7 @@ impl<'de> serde::Deserialize<'de> for DisconnectReason { "JOIN_FAILURE", "MIGRATION", "SIGNAL_CLOSE", + "ROOM_CLOSED", ]; struct GeneratedVisitor; @@ -5876,6 +5898,7 @@ impl<'de> serde::Deserialize<'de> for DisconnectReason { "JOIN_FAILURE" => Ok(DisconnectReason::JoinFailure), "MIGRATION" => Ok(DisconnectReason::Migration), "SIGNAL_CLOSE" => Ok(DisconnectReason::SignalClose), + "ROOM_CLOSED" => Ok(DisconnectReason::RoomClosed), _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), } } @@ -10343,6 +10366,9 @@ impl serde::Serialize for Job { if !self.id.is_empty() { len += 1; } + if !self.dispatch_id.is_empty() { + len += 1; + } if self.r#type != 0 { len += 1; } @@ -10368,6 +10394,9 @@ impl serde::Serialize for Job { if !self.id.is_empty() { struct_ser.serialize_field("id", &self.id)?; } + if !self.dispatch_id.is_empty() { + struct_ser.serialize_field("dispatchId", &self.dispatch_id)?; + } if self.r#type != 0 { let v = JobType::try_from(self.r#type) .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.r#type)))?; @@ -10402,6 +10431,8 @@ impl<'de> serde::Deserialize<'de> for Job { { const FIELDS: &[&str] = &[ "id", + "dispatch_id", + "dispatchId", "type", "room", "participant", @@ -10415,6 +10446,7 @@ impl<'de> serde::Deserialize<'de> for Job { #[allow(clippy::enum_variant_names)] enum GeneratedField { Id, + DispatchId, Type, Room, Participant, @@ -10445,6 +10477,7 @@ impl<'de> serde::Deserialize<'de> for Job { { match value { "id" => Ok(GeneratedField::Id), + "dispatchId" | "dispatch_id" => Ok(GeneratedField::DispatchId), "type" => Ok(GeneratedField::Type), "room" => Ok(GeneratedField::Room), "participant" => Ok(GeneratedField::Participant), @@ -10472,6 +10505,7 @@ impl<'de> serde::Deserialize<'de> for Job { V: serde::de::MapAccess<'de>, { let mut id__ = None; + let mut dispatch_id__ = None; let mut r#type__ = None; let mut room__ = None; let mut participant__ = None; @@ -10487,6 +10521,12 @@ impl<'de> serde::Deserialize<'de> for Job { } id__ = Some(map_.next_value()?); } + GeneratedField::DispatchId => { + if dispatch_id__.is_some() { + return Err(serde::de::Error::duplicate_field("dispatchId")); + } + dispatch_id__ = Some(map_.next_value()?); + } GeneratedField::Type => { if r#type__.is_some() { return Err(serde::de::Error::duplicate_field("type")); @@ -10536,6 +10576,7 @@ impl<'de> serde::Deserialize<'de> for Job { } Ok(Job { id: id__.unwrap_or_default(), + dispatch_id: dispatch_id__.unwrap_or_default(), r#type: r#type__.unwrap_or_default(), room: room__, participant: participant__, @@ -17056,9 +17097,6 @@ impl serde::Serialize for RegisterWorkerRequest { if !self.version.is_empty() { len += 1; } - if !self.name.is_empty() { - len += 1; - } if self.ping_interval != 0 { len += 1; } @@ -17080,9 +17118,6 @@ impl serde::Serialize for RegisterWorkerRequest { if !self.version.is_empty() { struct_ser.serialize_field("version", &self.version)?; } - if !self.name.is_empty() { - struct_ser.serialize_field("name", &self.name)?; - } if self.ping_interval != 0 { struct_ser.serialize_field("pingInterval", &self.ping_interval)?; } @@ -17106,7 +17141,6 @@ impl<'de> serde::Deserialize<'de> for RegisterWorkerRequest { "agent_name", "agentName", "version", - "name", "ping_interval", "pingInterval", "namespace", @@ -17119,7 +17153,6 @@ impl<'de> serde::Deserialize<'de> for RegisterWorkerRequest { Type, AgentName, Version, - Name, PingInterval, Namespace, AllowedPermissions, @@ -17148,7 +17181,6 @@ impl<'de> serde::Deserialize<'de> for RegisterWorkerRequest { "type" => Ok(GeneratedField::Type), "agentName" | "agent_name" => Ok(GeneratedField::AgentName), "version" => Ok(GeneratedField::Version), - "name" => Ok(GeneratedField::Name), "pingInterval" | "ping_interval" => Ok(GeneratedField::PingInterval), "namespace" => Ok(GeneratedField::Namespace), "allowedPermissions" | "allowed_permissions" => Ok(GeneratedField::AllowedPermissions), @@ -17174,7 +17206,6 @@ impl<'de> serde::Deserialize<'de> for RegisterWorkerRequest { let mut r#type__ = None; let mut agent_name__ = None; let mut version__ = None; - let mut name__ = None; let mut ping_interval__ = None; let mut namespace__ = None; let mut allowed_permissions__ = None; @@ -17198,12 +17229,6 @@ impl<'de> serde::Deserialize<'de> for RegisterWorkerRequest { } version__ = Some(map_.next_value()?); } - GeneratedField::Name => { - if name__.is_some() { - return Err(serde::de::Error::duplicate_field("name")); - } - name__ = Some(map_.next_value()?); - } GeneratedField::PingInterval => { if ping_interval__.is_some() { return Err(serde::de::Error::duplicate_field("pingInterval")); @@ -17233,7 +17258,6 @@ impl<'de> serde::Deserialize<'de> for RegisterWorkerRequest { r#type: r#type__.unwrap_or_default(), agent_name: agent_name__.unwrap_or_default(), version: version__.unwrap_or_default(), - name: name__.unwrap_or_default(), ping_interval: ping_interval__.unwrap_or_default(), namespace: namespace__, allowed_permissions: allowed_permissions__, @@ -24520,6 +24544,7 @@ impl serde::Serialize for StreamProtocol { let variant = match self { Self::DefaultProtocol => "DEFAULT_PROTOCOL", Self::Rtmp => "RTMP", + Self::Srt => "SRT", }; serializer.serialize_str(variant) } @@ -24533,6 +24558,7 @@ impl<'de> serde::Deserialize<'de> for StreamProtocol { const FIELDS: &[&str] = &[ "DEFAULT_PROTOCOL", "RTMP", + "SRT", ]; struct GeneratedVisitor; @@ -24575,6 +24601,7 @@ impl<'de> serde::Deserialize<'de> for StreamProtocol { match value { "DEFAULT_PROTOCOL" => Ok(StreamProtocol::DefaultProtocol), "RTMP" => Ok(StreamProtocol::Rtmp), + "SRT" => Ok(StreamProtocol::Srt), _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), } } @@ -27772,6 +27799,9 @@ impl serde::Serialize for TrickleRequest { if self.target != 0 { len += 1; } + if self.r#final { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.TrickleRequest", len)?; if !self.candidate_init.is_empty() { struct_ser.serialize_field("candidateInit", &self.candidate_init)?; @@ -27781,6 +27811,9 @@ impl serde::Serialize for TrickleRequest { .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.target)))?; struct_ser.serialize_field("target", &v)?; } + if self.r#final { + struct_ser.serialize_field("final", &self.r#final)?; + } struct_ser.end() } } @@ -27793,12 +27826,14 @@ impl<'de> serde::Deserialize<'de> for TrickleRequest { const FIELDS: &[&str] = &[ "candidateInit", "target", + "final", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { CandidateInit, Target, + Final, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -27823,6 +27858,7 @@ impl<'de> serde::Deserialize<'de> for TrickleRequest { match value { "candidateInit" => Ok(GeneratedField::CandidateInit), "target" => Ok(GeneratedField::Target), + "final" => Ok(GeneratedField::Final), _ => Ok(GeneratedField::__SkipField__), } } @@ -27844,6 +27880,7 @@ impl<'de> serde::Deserialize<'de> for TrickleRequest { { let mut candidate_init__ = None; let mut target__ = None; + let mut r#final__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::CandidateInit => { @@ -27858,6 +27895,12 @@ impl<'de> serde::Deserialize<'de> for TrickleRequest { } target__ = Some(map_.next_value::()? as i32); } + GeneratedField::Final => { + if r#final__.is_some() { + return Err(serde::de::Error::duplicate_field("final")); + } + r#final__ = Some(map_.next_value()?); + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -27866,6 +27909,7 @@ impl<'de> serde::Deserialize<'de> for TrickleRequest { Ok(TrickleRequest { candidate_init: candidate_init__.unwrap_or_default(), target: target__.unwrap_or_default(), + r#final: r#final__.unwrap_or_default(), }) } } diff --git a/livekit/src/prelude.rs b/livekit/src/prelude.rs index 791c08014..e801014aa 100644 --- a/livekit/src/prelude.rs +++ b/livekit/src/prelude.rs @@ -20,6 +20,6 @@ pub use crate::{ AudioTrack, LocalAudioTrack, LocalTrack, LocalVideoTrack, RemoteAudioTrack, RemoteTrack, RemoteVideoTrack, StreamState, Track, TrackDimension, TrackKind, TrackSource, VideoTrack, }, - ConnectionState, DataPacket, DataPacketKind, Room, RoomError, RoomEvent, RoomOptions, - RoomResult, SipDTMF, Transcription, TranscriptionSegment, + ConnectionState, DataPacket, DataPacketKind, DisconnectReason, Room, RoomError, RoomEvent, + RoomOptions, RoomResult, SipDTMF, Transcription, TranscriptionSegment, }; diff --git a/livekit/src/rtc_engine/rtc_session.rs b/livekit/src/rtc_engine/rtc_session.rs index 6f1823244..c20a5b24f 100644 --- a/livekit/src/rtc_engine/rtc_session.rs +++ b/livekit/src/rtc_engine/rtc_session.rs @@ -518,6 +518,7 @@ impl SessionInner { }) .unwrap(), target: target as i32, + ..Default::default() })) .await; } From 21c98c5011dfeb680579eaf8eb7e27e0354d01ce Mon Sep 17 00:00:00 2001 From: aoife cassidy Date: Fri, 2 Aug 2024 11:45:17 -0700 Subject: [PATCH 004/274] livekit: expose ParticipantKind --- livekit/src/proto.rs | 12 ++++++++++++ livekit/src/room/mod.rs | 10 +++++++++- livekit/src/room/participant/local_participant.rs | 9 +++++++-- livekit/src/room/participant/mod.rs | 13 +++++++++++++ livekit/src/room/participant/remote_participant.rs | 9 +++++++-- 5 files changed, 48 insertions(+), 5 deletions(-) diff --git a/livekit/src/proto.rs b/livekit/src/proto.rs index 260d150a0..ed6a4aaf3 100644 --- a/livekit/src/proto.rs +++ b/livekit/src/proto.rs @@ -110,3 +110,15 @@ impl From for encryption::Type { } } } + +impl From for participant::ParticipantKind { + fn from(value: participant_info::Kind) -> Self { + match value { + participant_info::Kind::Standard => participant::ParticipantKind::Standard, + participant_info::Kind::Ingress => participant::ParticipantKind::Ingress, + participant_info::Kind::Egress => participant::ParticipantKind::Egress, + participant_info::Kind::Sip => participant::ParticipantKind::Sip, + participant_info::Kind::Agent => participant::ParticipantKind::Agent, + } + } +} diff --git a/livekit/src/room/mod.rs b/livekit/src/room/mod.rs index d94387045..bcad3014b 100644 --- a/livekit/src/room/mod.rs +++ b/livekit/src/room/mod.rs @@ -33,7 +33,10 @@ use proto::{promise::Promise, SignalTarget}; use thiserror::Error; use tokio::sync::{mpsc, oneshot, Mutex as AsyncMutex}; -use self::e2ee::{manager::E2eeManager, E2eeOptions}; +use self::{ + e2ee::{manager::E2eeManager, E2eeOptions}, + participant::ParticipantKind, +}; pub use crate::rtc_engine::SimulateScenario; use crate::{ participant::ConnectionQuality, @@ -331,6 +334,7 @@ impl Room { let pi = join_response.participant.unwrap(); let local_participant = LocalParticipant::new( rtc_engine.clone(), + pi.kind().into(), pi.sid.try_into().unwrap(), pi.identity.into(), pi.name, @@ -458,6 +462,7 @@ impl Room { let participant = { let pi = pi.clone(); inner.create_participant( + pi.kind().into(), pi.sid.try_into().unwrap(), pi.identity.into(), pi.name, @@ -680,6 +685,7 @@ impl RoomSession { let remote_participant = { let pi = pi.clone(); self.create_participant( + pi.kind().into(), pi.sid.try_into().unwrap(), pi.identity.into(), pi.name, @@ -1087,6 +1093,7 @@ impl RoomSession { /// Also add it to the participants list fn create_participant( self: &Arc, + kind: ParticipantKind, sid: ParticipantSid, identity: ParticipantIdentity, name: String, @@ -1095,6 +1102,7 @@ impl RoomSession { ) -> RemoteParticipant { let participant = RemoteParticipant::new( self.rtc_engine.clone(), + kind, sid.clone(), identity.clone(), name, diff --git a/livekit/src/room/participant/local_participant.rs b/livekit/src/room/participant/local_participant.rs index 5067ce0ef..ca1339e91 100644 --- a/livekit/src/room/participant/local_participant.rs +++ b/livekit/src/room/participant/local_participant.rs @@ -22,7 +22,7 @@ use libwebrtc::rtp_parameters::RtpEncodingParameters; use livekit_protocol as proto; use parking_lot::Mutex; -use super::{ConnectionQuality, ParticipantInner}; +use super::{ConnectionQuality, ParticipantInner, ParticipantKind}; use crate::{ e2ee::EncryptionType, options, @@ -65,6 +65,7 @@ impl Debug for LocalParticipant { impl LocalParticipant { pub(crate) fn new( rtc_engine: Arc, + kind: ParticipantKind, sid: ParticipantSid, identity: ParticipantIdentity, name: String, @@ -73,7 +74,7 @@ impl LocalParticipant { encryption_type: EncryptionType, ) -> Self { Self { - inner: super::new_inner(rtc_engine, sid, identity, name, metadata, attributes), + inner: super::new_inner(rtc_engine, sid, identity, name, metadata, attributes, kind), local: Arc::new(LocalInfo { events: LocalEvents::default(), encryption_type }), } } @@ -434,4 +435,8 @@ impl LocalParticipant { pub fn connection_quality(&self) -> ConnectionQuality { self.inner.info.read().connection_quality } + + pub fn kind(&self) -> ParticipantKind { + self.inner.info.read().kind + } } diff --git a/livekit/src/room/participant/mod.rs b/livekit/src/room/participant/mod.rs index 4de15ef01..d423ea295 100644 --- a/livekit/src/room/participant/mod.rs +++ b/livekit/src/room/participant/mod.rs @@ -35,6 +35,15 @@ pub enum ConnectionQuality { Lost, } +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum ParticipantKind { + Standard, + Ingress, + Egress, + Sip, + Agent, +} + #[derive(Debug, Clone)] pub enum Participant { Local(LocalParticipant), @@ -80,6 +89,7 @@ struct ParticipantInfo { pub speaking: bool, pub audio_level: f32, pub connection_quality: ConnectionQuality, + pub kind: ParticipantKind, } type TrackMutedHandler = Box; @@ -111,6 +121,7 @@ pub(super) fn new_inner( name: String, metadata: String, attributes: HashMap, + kind: ParticipantKind, ) -> Arc { Arc::new(ParticipantInner { rtc_engine, @@ -120,6 +131,7 @@ pub(super) fn new_inner( name, metadata, attributes, + kind, speaking: false, audio_level: 0.0, connection_quality: ConnectionQuality::Excellent, @@ -135,6 +147,7 @@ pub(super) fn update_info( new_info: proto::ParticipantInfo, ) { let mut info = inner.info.write(); + info.kind = new_info.kind().into(); info.sid = new_info.sid.try_into().unwrap(); info.identity = new_info.identity.into(); diff --git a/livekit/src/room/participant/remote_participant.rs b/livekit/src/room/participant/remote_participant.rs index f66b4f63d..7ca6e6bd5 100644 --- a/livekit/src/room/participant/remote_participant.rs +++ b/livekit/src/room/participant/remote_participant.rs @@ -24,7 +24,7 @@ use livekit_protocol as proto; use livekit_runtime::timeout; use parking_lot::Mutex; -use super::{ConnectionQuality, ParticipantInner, TrackKind}; +use super::{ConnectionQuality, ParticipantInner, ParticipantKind, TrackKind}; use crate::{prelude::*, rtc_engine::RtcEngine, track::TrackError}; const ADD_TRACK_TIMEOUT: Duration = Duration::from_secs(5); @@ -70,6 +70,7 @@ impl Debug for RemoteParticipant { impl RemoteParticipant { pub(crate) fn new( rtc_engine: Arc, + kind: ParticipantKind, sid: ParticipantSid, identity: ParticipantIdentity, name: String, @@ -78,7 +79,7 @@ impl RemoteParticipant { auto_subscribe: bool, ) -> Self { Self { - inner: super::new_inner(rtc_engine, sid, identity, name, metadata, attributes), + inner: super::new_inner(rtc_engine, sid, identity, name, metadata, attributes, kind), remote: Arc::new(RemoteInfo { events: Default::default(), auto_subscribe }), } } @@ -458,4 +459,8 @@ impl RemoteParticipant { pub fn connection_quality(&self) -> ConnectionQuality { self.inner.info.read().connection_quality } + + pub fn kind(&self) -> ParticipantKind { + self.inner.info.read().kind + } } From 807b273b15fc726c1dace8dd183c5d1ad3959b7c Mon Sep 17 00:00:00 2001 From: David Zhao Date: Sun, 4 Aug 2024 16:57:50 -0700 Subject: [PATCH 005/274] update readme (#387) --- .github/banner_dark.png | Bin 184240 -> 189438 bytes .github/banner_light.png | Bin 58359 -> 59295 bytes README.md | 8 ++++---- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/banner_dark.png b/.github/banner_dark.png index 7f9fe2a9b06cf1e412aa03a8ebb8b3c5e0ad2dda..002ec714ac56ff26a6b4bf571065b4fc31f50c5a 100644 GIT binary patch literal 189438 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

PNhtbCEuo5lM^u6OF|Tms@ugIx$L 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�$ProeuM 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

{*gPqMoN0VJv2_lUB5^D#oIW~3(#_E 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 AnVn*h1={AyvlE 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

doX%i&GYF^rQKzlN7ueoPhpM?;1y@ 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
  • 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
  • *Uh{*P)zenwWRS^?NK#F`lM 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 E00Sev_5c6? literal 58359 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 ZxP(f-Vo`~UnU9mq%P~9=S8@0>{U2P10oVWl diff --git a/README.md b/README.md index 426bcffec..29feec1f3 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ # 📹🎙️🦀 Rust Client SDK for LiveKit -Use this SDK to add real-time video, audio and data features to your Rust app. By connecting to a self- or cloud-hosted LiveKit server, you can quickly build applications like interactive live streaming or video calls with just a few lines of code. +Use this SDK to add realtime video, audio and data features to your Rust app. By connecting to LiveKit Cloud or a self-hosted server, you can quickly build applications such as multi-modal AI, live streaming, or video calls with just a few lines of code. [![crates.io](https://img.shields.io/crates/v/livekit.svg)](https://crates.io/crates/livekit) @@ -188,11 +188,11 @@ We'll first use it as a basis for our Unity SDK (under development), but over ti
    - + - - + +
    LiveKit Ecosystem
    Real-time SDKsReact Components · Browser · iOS/macOS · Android · Flutter · React Native · Rust · Node.js · Python · Unity (web) · Unity (beta)
    Realtime SDKsReact Components · Browser · Swift Components · iOS/macOS/visionOS · Android · Flutter · React Native · Rust · Node.js · Python · Unity (web) · Unity (beta)
    Server APIsNode.js · Golang · Ruby · Java/Kotlin · Python · Rust · PHP (community)
    Agents FrameworksPython · Playground
    ServicesLivekit server · Egress · Ingress · SIP
    ResourcesDocs · Example apps · Cloud · Self-hosting · CLI
    ServicesLiveKit server · Egress · Ingress · SIP
    ResourcesDocs · Example apps · Cloud · Self-hosting · CLI
    From 00c1ca142c40798c942ba898526870c27c804a58 Mon Sep 17 00:00:00 2001 From: aoife cassidy Date: Wed, 14 Aug 2024 02:14:14 -0700 Subject: [PATCH 006/274] fix {Open,Boring}SSL conflicts (#376) --- .../libwebrtc/boringssl_prefix_symbols.txt | 3636 +++++++++++++++++ webrtc-sys/libwebrtc/build_linux.sh | 5 + webrtc-sys/libwebrtc/patches/fix_m114.patch | 54 + 3 files changed, 3695 insertions(+) create mode 100644 webrtc-sys/libwebrtc/boringssl_prefix_symbols.txt create mode 100644 webrtc-sys/libwebrtc/patches/fix_m114.patch diff --git a/webrtc-sys/libwebrtc/boringssl_prefix_symbols.txt b/webrtc-sys/libwebrtc/boringssl_prefix_symbols.txt new file mode 100644 index 000000000..2a17f1a7a --- /dev/null +++ b/webrtc-sys/libwebrtc/boringssl_prefix_symbols.txt @@ -0,0 +1,3636 @@ +ACCESS_DESCRIPTION_free LK_ACCESS_DESCRIPTION_free +ACCESS_DESCRIPTION_it LK_ACCESS_DESCRIPTION_it +ACCESS_DESCRIPTION_new LK_ACCESS_DESCRIPTION_new +AES_CMAC LK_AES_CMAC +AES_cbc_encrypt LK_AES_cbc_encrypt +AES_cfb128_encrypt LK_AES_cfb128_encrypt +AES_ctr128_encrypt LK_AES_ctr128_encrypt +AES_decrypt LK_AES_decrypt +AES_ecb_encrypt LK_AES_ecb_encrypt +AES_encrypt LK_AES_encrypt +AES_ofb128_encrypt LK_AES_ofb128_encrypt +AES_set_decrypt_key LK_AES_set_decrypt_key +AES_set_encrypt_key LK_AES_set_encrypt_key +AES_unwrap_key LK_AES_unwrap_key +AES_unwrap_key_padded LK_AES_unwrap_key_padded +AES_wrap_key LK_AES_wrap_key +AES_wrap_key_padded LK_AES_wrap_key_padded +ASN1_ANY_it LK_ASN1_ANY_it +ASN1_BIT_STRING_check LK_ASN1_BIT_STRING_check +ASN1_BIT_STRING_free LK_ASN1_BIT_STRING_free +ASN1_BIT_STRING_get_bit LK_ASN1_BIT_STRING_get_bit +ASN1_BIT_STRING_it LK_ASN1_BIT_STRING_it +ASN1_BIT_STRING_new LK_ASN1_BIT_STRING_new +ASN1_BIT_STRING_num_bytes LK_ASN1_BIT_STRING_num_bytes +ASN1_BIT_STRING_set LK_ASN1_BIT_STRING_set +ASN1_BIT_STRING_set_bit LK_ASN1_BIT_STRING_set_bit +ASN1_BMPSTRING_free LK_ASN1_BMPSTRING_free +ASN1_BMPSTRING_it LK_ASN1_BMPSTRING_it +ASN1_BMPSTRING_new LK_ASN1_BMPSTRING_new +ASN1_BOOLEAN_it LK_ASN1_BOOLEAN_it +ASN1_ENUMERATED_free LK_ASN1_ENUMERATED_free +ASN1_ENUMERATED_get LK_ASN1_ENUMERATED_get +ASN1_ENUMERATED_get_int64 LK_ASN1_ENUMERATED_get_int64 +ASN1_ENUMERATED_get_uint64 LK_ASN1_ENUMERATED_get_uint64 +ASN1_ENUMERATED_it LK_ASN1_ENUMERATED_it +ASN1_ENUMERATED_new LK_ASN1_ENUMERATED_new +ASN1_ENUMERATED_set LK_ASN1_ENUMERATED_set +ASN1_ENUMERATED_set_int64 LK_ASN1_ENUMERATED_set_int64 +ASN1_ENUMERATED_set_uint64 LK_ASN1_ENUMERATED_set_uint64 +ASN1_ENUMERATED_to_BN LK_ASN1_ENUMERATED_to_BN +ASN1_FBOOLEAN_it LK_ASN1_FBOOLEAN_it +ASN1_GENERALIZEDTIME_adj LK_ASN1_GENERALIZEDTIME_adj +ASN1_GENERALIZEDTIME_check LK_ASN1_GENERALIZEDTIME_check +ASN1_GENERALIZEDTIME_free LK_ASN1_GENERALIZEDTIME_free +ASN1_GENERALIZEDTIME_it LK_ASN1_GENERALIZEDTIME_it +ASN1_GENERALIZEDTIME_new LK_ASN1_GENERALIZEDTIME_new +ASN1_GENERALIZEDTIME_print LK_ASN1_GENERALIZEDTIME_print +ASN1_GENERALIZEDTIME_set LK_ASN1_GENERALIZEDTIME_set +ASN1_GENERALIZEDTIME_set_string LK_ASN1_GENERALIZEDTIME_set_string +ASN1_GENERALSTRING_free LK_ASN1_GENERALSTRING_free +ASN1_GENERALSTRING_it LK_ASN1_GENERALSTRING_it +ASN1_GENERALSTRING_new LK_ASN1_GENERALSTRING_new +ASN1_IA5STRING_free LK_ASN1_IA5STRING_free +ASN1_IA5STRING_it LK_ASN1_IA5STRING_it +ASN1_IA5STRING_new LK_ASN1_IA5STRING_new +ASN1_INTEGER_cmp LK_ASN1_INTEGER_cmp +ASN1_INTEGER_dup LK_ASN1_INTEGER_dup +ASN1_INTEGER_free LK_ASN1_INTEGER_free +ASN1_INTEGER_get LK_ASN1_INTEGER_get +ASN1_INTEGER_get_int64 LK_ASN1_INTEGER_get_int64 +ASN1_INTEGER_get_uint64 LK_ASN1_INTEGER_get_uint64 +ASN1_INTEGER_it LK_ASN1_INTEGER_it +ASN1_INTEGER_new LK_ASN1_INTEGER_new +ASN1_INTEGER_set LK_ASN1_INTEGER_set +ASN1_INTEGER_set_int64 LK_ASN1_INTEGER_set_int64 +ASN1_INTEGER_set_uint64 LK_ASN1_INTEGER_set_uint64 +ASN1_INTEGER_to_BN LK_ASN1_INTEGER_to_BN +ASN1_NULL_free LK_ASN1_NULL_free +ASN1_NULL_it LK_ASN1_NULL_it +ASN1_NULL_new LK_ASN1_NULL_new +ASN1_OBJECT_create LK_ASN1_OBJECT_create +ASN1_OBJECT_free LK_ASN1_OBJECT_free +ASN1_OBJECT_it LK_ASN1_OBJECT_it +ASN1_OBJECT_new LK_ASN1_OBJECT_new +ASN1_OCTET_STRING_cmp LK_ASN1_OCTET_STRING_cmp +ASN1_OCTET_STRING_dup LK_ASN1_OCTET_STRING_dup +ASN1_OCTET_STRING_free LK_ASN1_OCTET_STRING_free +ASN1_OCTET_STRING_it LK_ASN1_OCTET_STRING_it +ASN1_OCTET_STRING_new LK_ASN1_OCTET_STRING_new +ASN1_OCTET_STRING_set LK_ASN1_OCTET_STRING_set +ASN1_PRINTABLESTRING_free LK_ASN1_PRINTABLESTRING_free +ASN1_PRINTABLESTRING_it LK_ASN1_PRINTABLESTRING_it +ASN1_PRINTABLESTRING_new LK_ASN1_PRINTABLESTRING_new +ASN1_PRINTABLE_free LK_ASN1_PRINTABLE_free +ASN1_PRINTABLE_it LK_ASN1_PRINTABLE_it +ASN1_PRINTABLE_new LK_ASN1_PRINTABLE_new +ASN1_SEQUENCE_ANY_it LK_ASN1_SEQUENCE_ANY_it +ASN1_SEQUENCE_it LK_ASN1_SEQUENCE_it +ASN1_SET_ANY_it LK_ASN1_SET_ANY_it +ASN1_STRING_TABLE_add LK_ASN1_STRING_TABLE_add +ASN1_STRING_TABLE_cleanup LK_ASN1_STRING_TABLE_cleanup +ASN1_STRING_cmp LK_ASN1_STRING_cmp +ASN1_STRING_copy LK_ASN1_STRING_copy +ASN1_STRING_data LK_ASN1_STRING_data +ASN1_STRING_dup LK_ASN1_STRING_dup +ASN1_STRING_free LK_ASN1_STRING_free +ASN1_STRING_get0_data LK_ASN1_STRING_get0_data +ASN1_STRING_get_default_mask LK_ASN1_STRING_get_default_mask +ASN1_STRING_length LK_ASN1_STRING_length +ASN1_STRING_new LK_ASN1_STRING_new +ASN1_STRING_print LK_ASN1_STRING_print +ASN1_STRING_print_ex LK_ASN1_STRING_print_ex +ASN1_STRING_print_ex_fp LK_ASN1_STRING_print_ex_fp +ASN1_STRING_set LK_ASN1_STRING_set +ASN1_STRING_set0 LK_ASN1_STRING_set0 +ASN1_STRING_set_by_NID LK_ASN1_STRING_set_by_NID +ASN1_STRING_set_default_mask LK_ASN1_STRING_set_default_mask +ASN1_STRING_set_default_mask_asc LK_ASN1_STRING_set_default_mask_asc +ASN1_STRING_to_UTF8 LK_ASN1_STRING_to_UTF8 +ASN1_STRING_type LK_ASN1_STRING_type +ASN1_STRING_type_new LK_ASN1_STRING_type_new +ASN1_T61STRING_free LK_ASN1_T61STRING_free +ASN1_T61STRING_it LK_ASN1_T61STRING_it +ASN1_T61STRING_new LK_ASN1_T61STRING_new +ASN1_TBOOLEAN_it LK_ASN1_TBOOLEAN_it +ASN1_TIME_adj LK_ASN1_TIME_adj +ASN1_TIME_check LK_ASN1_TIME_check +ASN1_TIME_diff LK_ASN1_TIME_diff +ASN1_TIME_free LK_ASN1_TIME_free +ASN1_TIME_it LK_ASN1_TIME_it +ASN1_TIME_new LK_ASN1_TIME_new +ASN1_TIME_print LK_ASN1_TIME_print +ASN1_TIME_set LK_ASN1_TIME_set +ASN1_TIME_set_posix LK_ASN1_TIME_set_posix +ASN1_TIME_set_string LK_ASN1_TIME_set_string +ASN1_TIME_set_string_X509 LK_ASN1_TIME_set_string_X509 +ASN1_TIME_to_generalizedtime LK_ASN1_TIME_to_generalizedtime +ASN1_TIME_to_posix LK_ASN1_TIME_to_posix +ASN1_TIME_to_time_t LK_ASN1_TIME_to_time_t +ASN1_TYPE_cmp LK_ASN1_TYPE_cmp +ASN1_TYPE_free LK_ASN1_TYPE_free +ASN1_TYPE_get LK_ASN1_TYPE_get +ASN1_TYPE_new LK_ASN1_TYPE_new +ASN1_TYPE_set LK_ASN1_TYPE_set +ASN1_TYPE_set1 LK_ASN1_TYPE_set1 +ASN1_UNIVERSALSTRING_free LK_ASN1_UNIVERSALSTRING_free +ASN1_UNIVERSALSTRING_it LK_ASN1_UNIVERSALSTRING_it +ASN1_UNIVERSALSTRING_new LK_ASN1_UNIVERSALSTRING_new +ASN1_UTCTIME_adj LK_ASN1_UTCTIME_adj +ASN1_UTCTIME_check LK_ASN1_UTCTIME_check +ASN1_UTCTIME_cmp_time_t LK_ASN1_UTCTIME_cmp_time_t +ASN1_UTCTIME_free LK_ASN1_UTCTIME_free +ASN1_UTCTIME_it LK_ASN1_UTCTIME_it +ASN1_UTCTIME_new LK_ASN1_UTCTIME_new +ASN1_UTCTIME_print LK_ASN1_UTCTIME_print +ASN1_UTCTIME_set LK_ASN1_UTCTIME_set +ASN1_UTCTIME_set_string LK_ASN1_UTCTIME_set_string +ASN1_UTF8STRING_free LK_ASN1_UTF8STRING_free +ASN1_UTF8STRING_it LK_ASN1_UTF8STRING_it +ASN1_UTF8STRING_new LK_ASN1_UTF8STRING_new +ASN1_VISIBLESTRING_free LK_ASN1_VISIBLESTRING_free +ASN1_VISIBLESTRING_it LK_ASN1_VISIBLESTRING_it +ASN1_VISIBLESTRING_new LK_ASN1_VISIBLESTRING_new +ASN1_digest LK_ASN1_digest +ASN1_generate_v3 LK_ASN1_generate_v3 +ASN1_get_object LK_ASN1_get_object +ASN1_item_d2i LK_ASN1_item_d2i +ASN1_item_d2i_bio LK_ASN1_item_d2i_bio +ASN1_item_d2i_fp LK_ASN1_item_d2i_fp +ASN1_item_digest LK_ASN1_item_digest +ASN1_item_dup LK_ASN1_item_dup +ASN1_item_ex_d2i LK_ASN1_item_ex_d2i +ASN1_item_ex_free LK_ASN1_item_ex_free +ASN1_item_ex_i2d LK_ASN1_item_ex_i2d +ASN1_item_ex_new LK_ASN1_item_ex_new +ASN1_item_free LK_ASN1_item_free +ASN1_item_i2d LK_ASN1_item_i2d +ASN1_item_i2d_bio LK_ASN1_item_i2d_bio +ASN1_item_i2d_fp LK_ASN1_item_i2d_fp +ASN1_item_new LK_ASN1_item_new +ASN1_item_pack LK_ASN1_item_pack +ASN1_item_sign LK_ASN1_item_sign +ASN1_item_sign_ctx LK_ASN1_item_sign_ctx +ASN1_item_unpack LK_ASN1_item_unpack +ASN1_item_verify LK_ASN1_item_verify +ASN1_mbstring_copy LK_ASN1_mbstring_copy +ASN1_mbstring_ncopy LK_ASN1_mbstring_ncopy +ASN1_object_size LK_ASN1_object_size +ASN1_primitive_free LK_ASN1_primitive_free +ASN1_put_eoc LK_ASN1_put_eoc +ASN1_put_object LK_ASN1_put_object +ASN1_tag2bit LK_ASN1_tag2bit +ASN1_tag2str LK_ASN1_tag2str +ASN1_template_free LK_ASN1_template_free +AUTHORITY_INFO_ACCESS_free LK_AUTHORITY_INFO_ACCESS_free +AUTHORITY_INFO_ACCESS_it LK_AUTHORITY_INFO_ACCESS_it +AUTHORITY_INFO_ACCESS_new LK_AUTHORITY_INFO_ACCESS_new +AUTHORITY_KEYID_free LK_AUTHORITY_KEYID_free +AUTHORITY_KEYID_it LK_AUTHORITY_KEYID_it +AUTHORITY_KEYID_new LK_AUTHORITY_KEYID_new +BASIC_CONSTRAINTS_free LK_BASIC_CONSTRAINTS_free +BASIC_CONSTRAINTS_it LK_BASIC_CONSTRAINTS_it +BASIC_CONSTRAINTS_new LK_BASIC_CONSTRAINTS_new +BF_cbc_encrypt LK_BF_cbc_encrypt +BF_decrypt LK_BF_decrypt +BF_ecb_encrypt LK_BF_ecb_encrypt +BF_encrypt LK_BF_encrypt +BF_set_key LK_BF_set_key +BIO_append_filename LK_BIO_append_filename +BIO_callback_ctrl LK_BIO_callback_ctrl +BIO_clear_flags LK_BIO_clear_flags +BIO_clear_retry_flags LK_BIO_clear_retry_flags +BIO_copy_next_retry LK_BIO_copy_next_retry +BIO_ctrl LK_BIO_ctrl +BIO_ctrl_get_read_request LK_BIO_ctrl_get_read_request +BIO_ctrl_get_write_guarantee LK_BIO_ctrl_get_write_guarantee +BIO_ctrl_pending LK_BIO_ctrl_pending +BIO_do_connect LK_BIO_do_connect +BIO_eof LK_BIO_eof +BIO_f_base64 LK_BIO_f_base64 +BIO_find_type LK_BIO_find_type +BIO_flush LK_BIO_flush +BIO_free LK_BIO_free +BIO_free_all LK_BIO_free_all +BIO_get_data LK_BIO_get_data +BIO_get_ex_data LK_BIO_get_ex_data +BIO_get_ex_new_index LK_BIO_get_ex_new_index +BIO_get_fd LK_BIO_get_fd +BIO_get_fp LK_BIO_get_fp +BIO_get_init LK_BIO_get_init +BIO_get_mem_data LK_BIO_get_mem_data +BIO_get_mem_ptr LK_BIO_get_mem_ptr +BIO_get_new_index LK_BIO_get_new_index +BIO_get_retry_flags LK_BIO_get_retry_flags +BIO_get_retry_reason LK_BIO_get_retry_reason +BIO_get_shutdown LK_BIO_get_shutdown +BIO_gets LK_BIO_gets +BIO_hexdump LK_BIO_hexdump +BIO_indent LK_BIO_indent +BIO_int_ctrl LK_BIO_int_ctrl +BIO_mem_contents LK_BIO_mem_contents +BIO_meth_free LK_BIO_meth_free +BIO_meth_new LK_BIO_meth_new +BIO_meth_set_create LK_BIO_meth_set_create +BIO_meth_set_ctrl LK_BIO_meth_set_ctrl +BIO_meth_set_destroy LK_BIO_meth_set_destroy +BIO_meth_set_gets LK_BIO_meth_set_gets +BIO_meth_set_puts LK_BIO_meth_set_puts +BIO_meth_set_read LK_BIO_meth_set_read +BIO_meth_set_write LK_BIO_meth_set_write +BIO_method_type LK_BIO_method_type +BIO_new LK_BIO_new +BIO_new_bio_pair LK_BIO_new_bio_pair +BIO_new_connect LK_BIO_new_connect +BIO_new_fd LK_BIO_new_fd +BIO_new_file LK_BIO_new_file +BIO_new_fp LK_BIO_new_fp +BIO_new_mem_buf LK_BIO_new_mem_buf +BIO_new_socket LK_BIO_new_socket +BIO_next LK_BIO_next +BIO_number_read LK_BIO_number_read +BIO_number_written LK_BIO_number_written +BIO_pending LK_BIO_pending +BIO_pop LK_BIO_pop +BIO_printf LK_BIO_printf +BIO_ptr_ctrl LK_BIO_ptr_ctrl +BIO_push LK_BIO_push +BIO_puts LK_BIO_puts +BIO_read LK_BIO_read +BIO_read_asn1 LK_BIO_read_asn1 +BIO_read_filename LK_BIO_read_filename +BIO_reset LK_BIO_reset +BIO_rw_filename LK_BIO_rw_filename +BIO_s_connect LK_BIO_s_connect +BIO_s_fd LK_BIO_s_fd +BIO_s_file LK_BIO_s_file +BIO_s_mem LK_BIO_s_mem +BIO_s_socket LK_BIO_s_socket +BIO_seek LK_BIO_seek +BIO_set_close LK_BIO_set_close +BIO_set_conn_hostname LK_BIO_set_conn_hostname +BIO_set_conn_int_port LK_BIO_set_conn_int_port +BIO_set_conn_port LK_BIO_set_conn_port +BIO_set_data LK_BIO_set_data +BIO_set_ex_data LK_BIO_set_ex_data +BIO_set_fd LK_BIO_set_fd +BIO_set_flags LK_BIO_set_flags +BIO_set_fp LK_BIO_set_fp +BIO_set_init LK_BIO_set_init +BIO_set_mem_buf LK_BIO_set_mem_buf +BIO_set_mem_eof_return LK_BIO_set_mem_eof_return +BIO_set_nbio LK_BIO_set_nbio +BIO_set_retry_read LK_BIO_set_retry_read +BIO_set_retry_reason LK_BIO_set_retry_reason +BIO_set_retry_special LK_BIO_set_retry_special +BIO_set_retry_write LK_BIO_set_retry_write +BIO_set_shutdown LK_BIO_set_shutdown +BIO_set_write_buffer_size LK_BIO_set_write_buffer_size +BIO_should_io_special LK_BIO_should_io_special +BIO_should_read LK_BIO_should_read +BIO_should_retry LK_BIO_should_retry +BIO_should_write LK_BIO_should_write +BIO_shutdown_wr LK_BIO_shutdown_wr +BIO_snprintf LK_BIO_snprintf +BIO_tell LK_BIO_tell +BIO_test_flags LK_BIO_test_flags +BIO_up_ref LK_BIO_up_ref +BIO_vfree LK_BIO_vfree +BIO_vsnprintf LK_BIO_vsnprintf +BIO_wpending LK_BIO_wpending +BIO_write LK_BIO_write +BIO_write_all LK_BIO_write_all +BIO_write_filename LK_BIO_write_filename +BLAKE2B256 LK_BLAKE2B256 +BLAKE2B256_Final LK_BLAKE2B256_Final +BLAKE2B256_Init LK_BLAKE2B256_Init +BLAKE2B256_Update LK_BLAKE2B256_Update +BN_BLINDING_convert LK_BN_BLINDING_convert +BN_BLINDING_free LK_BN_BLINDING_free +BN_BLINDING_invalidate LK_BN_BLINDING_invalidate +BN_BLINDING_invert LK_BN_BLINDING_invert +BN_BLINDING_new LK_BN_BLINDING_new +BN_CTX_end LK_BN_CTX_end +BN_CTX_free LK_BN_CTX_free +BN_CTX_get LK_BN_CTX_get +BN_CTX_new LK_BN_CTX_new +BN_CTX_start LK_BN_CTX_start +BN_GENCB_call LK_BN_GENCB_call +BN_GENCB_free LK_BN_GENCB_free +BN_GENCB_get_arg LK_BN_GENCB_get_arg +BN_GENCB_new LK_BN_GENCB_new +BN_GENCB_set LK_BN_GENCB_set +BN_MONT_CTX_copy LK_BN_MONT_CTX_copy +BN_MONT_CTX_free LK_BN_MONT_CTX_free +BN_MONT_CTX_new LK_BN_MONT_CTX_new +BN_MONT_CTX_new_consttime LK_BN_MONT_CTX_new_consttime +BN_MONT_CTX_new_for_modulus LK_BN_MONT_CTX_new_for_modulus +BN_MONT_CTX_set LK_BN_MONT_CTX_set +BN_MONT_CTX_set_locked LK_BN_MONT_CTX_set_locked +BN_abs_is_word LK_BN_abs_is_word +BN_add LK_BN_add +BN_add_word LK_BN_add_word +BN_asc2bn LK_BN_asc2bn +BN_bin2bn LK_BN_bin2bn +BN_bn2bin LK_BN_bn2bin +BN_bn2bin_padded LK_BN_bn2bin_padded +BN_bn2binpad LK_BN_bn2binpad +BN_bn2cbb_padded LK_BN_bn2cbb_padded +BN_bn2dec LK_BN_bn2dec +BN_bn2hex LK_BN_bn2hex +BN_bn2le_padded LK_BN_bn2le_padded +BN_bn2lebinpad LK_BN_bn2lebinpad +BN_bn2mpi LK_BN_bn2mpi +BN_clear LK_BN_clear +BN_clear_bit LK_BN_clear_bit +BN_clear_free LK_BN_clear_free +BN_cmp LK_BN_cmp +BN_cmp_word LK_BN_cmp_word +BN_copy LK_BN_copy +BN_count_low_zero_bits LK_BN_count_low_zero_bits +BN_dec2bn LK_BN_dec2bn +BN_div LK_BN_div +BN_div_word LK_BN_div_word +BN_dup LK_BN_dup +BN_enhanced_miller_rabin_primality_test LK_BN_enhanced_miller_rabin_primality_test +BN_equal_consttime LK_BN_equal_consttime +BN_exp LK_BN_exp +BN_free LK_BN_free +BN_from_montgomery LK_BN_from_montgomery +BN_gcd LK_BN_gcd +BN_generate_prime_ex LK_BN_generate_prime_ex +BN_get_rfc3526_prime_1536 LK_BN_get_rfc3526_prime_1536 +BN_get_rfc3526_prime_2048 LK_BN_get_rfc3526_prime_2048 +BN_get_rfc3526_prime_3072 LK_BN_get_rfc3526_prime_3072 +BN_get_rfc3526_prime_4096 LK_BN_get_rfc3526_prime_4096 +BN_get_rfc3526_prime_6144 LK_BN_get_rfc3526_prime_6144 +BN_get_rfc3526_prime_8192 LK_BN_get_rfc3526_prime_8192 +BN_get_u64 LK_BN_get_u64 +BN_get_word LK_BN_get_word +BN_hex2bn LK_BN_hex2bn +BN_init LK_BN_init +BN_is_bit_set LK_BN_is_bit_set +BN_is_negative LK_BN_is_negative +BN_is_odd LK_BN_is_odd +BN_is_one LK_BN_is_one +BN_is_pow2 LK_BN_is_pow2 +BN_is_prime_ex LK_BN_is_prime_ex +BN_is_prime_fasttest_ex LK_BN_is_prime_fasttest_ex +BN_is_word LK_BN_is_word +BN_is_zero LK_BN_is_zero +BN_le2bn LK_BN_le2bn +BN_lebin2bn LK_BN_lebin2bn +BN_lshift LK_BN_lshift +BN_lshift1 LK_BN_lshift1 +BN_marshal_asn1 LK_BN_marshal_asn1 +BN_mask_bits LK_BN_mask_bits +BN_mod_add LK_BN_mod_add +BN_mod_add_quick LK_BN_mod_add_quick +BN_mod_exp LK_BN_mod_exp +BN_mod_exp2_mont LK_BN_mod_exp2_mont +BN_mod_exp_mont LK_BN_mod_exp_mont +BN_mod_exp_mont_consttime LK_BN_mod_exp_mont_consttime +BN_mod_exp_mont_word LK_BN_mod_exp_mont_word +BN_mod_inverse LK_BN_mod_inverse +BN_mod_inverse_blinded LK_BN_mod_inverse_blinded +BN_mod_inverse_odd LK_BN_mod_inverse_odd +BN_mod_lshift LK_BN_mod_lshift +BN_mod_lshift1 LK_BN_mod_lshift1 +BN_mod_lshift1_quick LK_BN_mod_lshift1_quick +BN_mod_lshift_quick LK_BN_mod_lshift_quick +BN_mod_mul LK_BN_mod_mul +BN_mod_mul_montgomery LK_BN_mod_mul_montgomery +BN_mod_pow2 LK_BN_mod_pow2 +BN_mod_sqr LK_BN_mod_sqr +BN_mod_sqrt LK_BN_mod_sqrt +BN_mod_sub LK_BN_mod_sub +BN_mod_sub_quick LK_BN_mod_sub_quick +BN_mod_word LK_BN_mod_word +BN_mpi2bn LK_BN_mpi2bn +BN_mul LK_BN_mul +BN_mul_word LK_BN_mul_word +BN_new LK_BN_new +BN_nnmod LK_BN_nnmod +BN_nnmod_pow2 LK_BN_nnmod_pow2 +BN_num_bits LK_BN_num_bits +BN_num_bits_word LK_BN_num_bits_word +BN_num_bytes LK_BN_num_bytes +BN_one LK_BN_one +BN_parse_asn1_unsigned LK_BN_parse_asn1_unsigned +BN_primality_test LK_BN_primality_test +BN_print LK_BN_print +BN_print_fp LK_BN_print_fp +BN_pseudo_rand LK_BN_pseudo_rand +BN_pseudo_rand_range LK_BN_pseudo_rand_range +BN_rand LK_BN_rand +BN_rand_range LK_BN_rand_range +BN_rand_range_ex LK_BN_rand_range_ex +BN_rshift LK_BN_rshift +BN_rshift1 LK_BN_rshift1 +BN_secure_new LK_BN_secure_new +BN_set_bit LK_BN_set_bit +BN_set_negative LK_BN_set_negative +BN_set_u64 LK_BN_set_u64 +BN_set_word LK_BN_set_word +BN_sqr LK_BN_sqr +BN_sqrt LK_BN_sqrt +BN_sub LK_BN_sub +BN_sub_word LK_BN_sub_word +BN_to_ASN1_ENUMERATED LK_BN_to_ASN1_ENUMERATED +BN_to_ASN1_INTEGER LK_BN_to_ASN1_INTEGER +BN_to_montgomery LK_BN_to_montgomery +BN_uadd LK_BN_uadd +BN_ucmp LK_BN_ucmp +BN_usub LK_BN_usub +BN_value_one LK_BN_value_one +BN_zero LK_BN_zero +BORINGSSL_function_hit LK_BORINGSSL_function_hit +BORINGSSL_keccak LK_BORINGSSL_keccak +BORINGSSL_keccak_absorb LK_BORINGSSL_keccak_absorb +BORINGSSL_keccak_init LK_BORINGSSL_keccak_init +BORINGSSL_keccak_squeeze LK_BORINGSSL_keccak_squeeze +BORINGSSL_self_test LK_BORINGSSL_self_test +BUF_MEM_append LK_BUF_MEM_append +BUF_MEM_free LK_BUF_MEM_free +BUF_MEM_grow LK_BUF_MEM_grow +BUF_MEM_grow_clean LK_BUF_MEM_grow_clean +BUF_MEM_new LK_BUF_MEM_new +BUF_MEM_reserve LK_BUF_MEM_reserve +BUF_memdup LK_BUF_memdup +BUF_strdup LK_BUF_strdup +BUF_strlcat LK_BUF_strlcat +BUF_strlcpy LK_BUF_strlcpy +BUF_strndup LK_BUF_strndup +BUF_strnlen LK_BUF_strnlen +CAST_S_table0 LK_CAST_S_table0 +CAST_S_table1 LK_CAST_S_table1 +CAST_S_table2 LK_CAST_S_table2 +CAST_S_table3 LK_CAST_S_table3 +CAST_S_table4 LK_CAST_S_table4 +CAST_S_table5 LK_CAST_S_table5 +CAST_S_table6 LK_CAST_S_table6 +CAST_S_table7 LK_CAST_S_table7 +CAST_cbc_encrypt LK_CAST_cbc_encrypt +CAST_cfb64_encrypt LK_CAST_cfb64_encrypt +CAST_decrypt LK_CAST_decrypt +CAST_ecb_encrypt LK_CAST_ecb_encrypt +CAST_encrypt LK_CAST_encrypt +CAST_set_key LK_CAST_set_key +CBB_add_asn1 LK_CBB_add_asn1 +CBB_add_asn1_bool LK_CBB_add_asn1_bool +CBB_add_asn1_int64 LK_CBB_add_asn1_int64 +CBB_add_asn1_int64_with_tag LK_CBB_add_asn1_int64_with_tag +CBB_add_asn1_octet_string LK_CBB_add_asn1_octet_string +CBB_add_asn1_oid_from_text LK_CBB_add_asn1_oid_from_text +CBB_add_asn1_uint64 LK_CBB_add_asn1_uint64 +CBB_add_asn1_uint64_with_tag LK_CBB_add_asn1_uint64_with_tag +CBB_add_bytes LK_CBB_add_bytes +CBB_add_latin1 LK_CBB_add_latin1 +CBB_add_space LK_CBB_add_space +CBB_add_u16 LK_CBB_add_u16 +CBB_add_u16_length_prefixed LK_CBB_add_u16_length_prefixed +CBB_add_u16le LK_CBB_add_u16le +CBB_add_u24 LK_CBB_add_u24 +CBB_add_u24_length_prefixed LK_CBB_add_u24_length_prefixed +CBB_add_u32 LK_CBB_add_u32 +CBB_add_u32le LK_CBB_add_u32le +CBB_add_u64 LK_CBB_add_u64 +CBB_add_u64le LK_CBB_add_u64le +CBB_add_u8 LK_CBB_add_u8 +CBB_add_u8_length_prefixed LK_CBB_add_u8_length_prefixed +CBB_add_ucs2_be LK_CBB_add_ucs2_be +CBB_add_utf32_be LK_CBB_add_utf32_be +CBB_add_utf8 LK_CBB_add_utf8 +CBB_add_zeros LK_CBB_add_zeros +CBB_cleanup LK_CBB_cleanup +CBB_data LK_CBB_data +CBB_did_write LK_CBB_did_write +CBB_discard_child LK_CBB_discard_child +CBB_finish LK_CBB_finish +CBB_finish_i2d LK_CBB_finish_i2d +CBB_flush LK_CBB_flush +CBB_flush_asn1_set_of LK_CBB_flush_asn1_set_of +CBB_get_utf8_len LK_CBB_get_utf8_len +CBB_init LK_CBB_init +CBB_init_fixed LK_CBB_init_fixed +CBB_len LK_CBB_len +CBB_reserve LK_CBB_reserve +CBB_zero LK_CBB_zero +CBS_asn1_ber_to_der LK_CBS_asn1_ber_to_der +CBS_asn1_bitstring_has_bit LK_CBS_asn1_bitstring_has_bit +CBS_asn1_oid_to_text LK_CBS_asn1_oid_to_text +CBS_contains_zero_byte LK_CBS_contains_zero_byte +CBS_copy_bytes LK_CBS_copy_bytes +CBS_get_any_asn1 LK_CBS_get_any_asn1 +CBS_get_any_asn1_element LK_CBS_get_any_asn1_element +CBS_get_any_ber_asn1_element LK_CBS_get_any_ber_asn1_element +CBS_get_asn1 LK_CBS_get_asn1 +CBS_get_asn1_bool LK_CBS_get_asn1_bool +CBS_get_asn1_element LK_CBS_get_asn1_element +CBS_get_asn1_implicit_string LK_CBS_get_asn1_implicit_string +CBS_get_asn1_int64 LK_CBS_get_asn1_int64 +CBS_get_asn1_uint64 LK_CBS_get_asn1_uint64 +CBS_get_bytes LK_CBS_get_bytes +CBS_get_last_u8 LK_CBS_get_last_u8 +CBS_get_latin1 LK_CBS_get_latin1 +CBS_get_optional_asn1 LK_CBS_get_optional_asn1 +CBS_get_optional_asn1_bool LK_CBS_get_optional_asn1_bool +CBS_get_optional_asn1_octet_string LK_CBS_get_optional_asn1_octet_string +CBS_get_optional_asn1_uint64 LK_CBS_get_optional_asn1_uint64 +CBS_get_u16 LK_CBS_get_u16 +CBS_get_u16_length_prefixed LK_CBS_get_u16_length_prefixed +CBS_get_u16le LK_CBS_get_u16le +CBS_get_u24 LK_CBS_get_u24 +CBS_get_u24_length_prefixed LK_CBS_get_u24_length_prefixed +CBS_get_u32 LK_CBS_get_u32 +CBS_get_u32le LK_CBS_get_u32le +CBS_get_u64 LK_CBS_get_u64 +CBS_get_u64_decimal LK_CBS_get_u64_decimal +CBS_get_u64le LK_CBS_get_u64le +CBS_get_u8 LK_CBS_get_u8 +CBS_get_u8_length_prefixed LK_CBS_get_u8_length_prefixed +CBS_get_ucs2_be LK_CBS_get_ucs2_be +CBS_get_until_first LK_CBS_get_until_first +CBS_get_utf32_be LK_CBS_get_utf32_be +CBS_get_utf8 LK_CBS_get_utf8 +CBS_is_unsigned_asn1_integer LK_CBS_is_unsigned_asn1_integer +CBS_is_valid_asn1_bitstring LK_CBS_is_valid_asn1_bitstring +CBS_is_valid_asn1_integer LK_CBS_is_valid_asn1_integer +CBS_is_valid_asn1_oid LK_CBS_is_valid_asn1_oid +CBS_mem_equal LK_CBS_mem_equal +CBS_parse_generalized_time LK_CBS_parse_generalized_time +CBS_parse_utc_time LK_CBS_parse_utc_time +CBS_peek_asn1_tag LK_CBS_peek_asn1_tag +CBS_skip LK_CBS_skip +CBS_stow LK_CBS_stow +CBS_strdup LK_CBS_strdup +CERTIFICATEPOLICIES_free LK_CERTIFICATEPOLICIES_free +CERTIFICATEPOLICIES_it LK_CERTIFICATEPOLICIES_it +CERTIFICATEPOLICIES_new LK_CERTIFICATEPOLICIES_new +CMAC_CTX_copy LK_CMAC_CTX_copy +CMAC_CTX_free LK_CMAC_CTX_free +CMAC_CTX_new LK_CMAC_CTX_new +CMAC_Final LK_CMAC_Final +CMAC_Init LK_CMAC_Init +CMAC_Reset LK_CMAC_Reset +CMAC_Update LK_CMAC_Update +CONF_VALUE_new LK_CONF_VALUE_new +CONF_modules_free LK_CONF_modules_free +CONF_modules_load_file LK_CONF_modules_load_file +CONF_parse_list LK_CONF_parse_list +CRL_DIST_POINTS_free LK_CRL_DIST_POINTS_free +CRL_DIST_POINTS_it LK_CRL_DIST_POINTS_it +CRL_DIST_POINTS_new LK_CRL_DIST_POINTS_new +CRYPTO_BUFFER_POOL_free LK_CRYPTO_BUFFER_POOL_free +CRYPTO_BUFFER_POOL_new LK_CRYPTO_BUFFER_POOL_new +CRYPTO_BUFFER_alloc LK_CRYPTO_BUFFER_alloc +CRYPTO_BUFFER_data LK_CRYPTO_BUFFER_data +CRYPTO_BUFFER_free LK_CRYPTO_BUFFER_free +CRYPTO_BUFFER_init_CBS LK_CRYPTO_BUFFER_init_CBS +CRYPTO_BUFFER_len LK_CRYPTO_BUFFER_len +CRYPTO_BUFFER_new LK_CRYPTO_BUFFER_new +CRYPTO_BUFFER_new_from_CBS LK_CRYPTO_BUFFER_new_from_CBS +CRYPTO_BUFFER_new_from_static_data_unsafe LK_CRYPTO_BUFFER_new_from_static_data_unsafe +CRYPTO_BUFFER_up_ref LK_CRYPTO_BUFFER_up_ref +CRYPTO_MUTEX_cleanup LK_CRYPTO_MUTEX_cleanup +CRYPTO_MUTEX_init LK_CRYPTO_MUTEX_init +CRYPTO_MUTEX_lock_read LK_CRYPTO_MUTEX_lock_read +CRYPTO_MUTEX_lock_write LK_CRYPTO_MUTEX_lock_write +CRYPTO_MUTEX_unlock_read LK_CRYPTO_MUTEX_unlock_read +CRYPTO_MUTEX_unlock_write LK_CRYPTO_MUTEX_unlock_write +CRYPTO_POLYVAL_finish LK_CRYPTO_POLYVAL_finish +CRYPTO_POLYVAL_init LK_CRYPTO_POLYVAL_init +CRYPTO_POLYVAL_update_blocks LK_CRYPTO_POLYVAL_update_blocks +CRYPTO_THREADID_current LK_CRYPTO_THREADID_current +CRYPTO_THREADID_set_callback LK_CRYPTO_THREADID_set_callback +CRYPTO_THREADID_set_numeric LK_CRYPTO_THREADID_set_numeric +CRYPTO_THREADID_set_pointer LK_CRYPTO_THREADID_set_pointer +CRYPTO_cbc128_decrypt LK_CRYPTO_cbc128_decrypt +CRYPTO_cbc128_encrypt LK_CRYPTO_cbc128_encrypt +CRYPTO_cfb128_1_encrypt LK_CRYPTO_cfb128_1_encrypt +CRYPTO_cfb128_8_encrypt LK_CRYPTO_cfb128_8_encrypt +CRYPTO_cfb128_encrypt LK_CRYPTO_cfb128_encrypt +CRYPTO_chacha_20 LK_CRYPTO_chacha_20 +CRYPTO_cleanup_all_ex_data LK_CRYPTO_cleanup_all_ex_data +CRYPTO_ctr128_encrypt LK_CRYPTO_ctr128_encrypt +CRYPTO_ctr128_encrypt_ctr32 LK_CRYPTO_ctr128_encrypt_ctr32 +CRYPTO_fips_186_2_prf LK_CRYPTO_fips_186_2_prf +CRYPTO_fork_detect_force_madv_wipeonfork_for_testing LK_CRYPTO_fork_detect_force_madv_wipeonfork_for_testing +CRYPTO_free LK_CRYPTO_free +CRYPTO_free_ex_data LK_CRYPTO_free_ex_data +CRYPTO_gcm128_aad LK_CRYPTO_gcm128_aad +CRYPTO_gcm128_decrypt LK_CRYPTO_gcm128_decrypt +CRYPTO_gcm128_decrypt_ctr32 LK_CRYPTO_gcm128_decrypt_ctr32 +CRYPTO_gcm128_encrypt LK_CRYPTO_gcm128_encrypt +CRYPTO_gcm128_encrypt_ctr32 LK_CRYPTO_gcm128_encrypt_ctr32 +CRYPTO_gcm128_finish LK_CRYPTO_gcm128_finish +CRYPTO_gcm128_init_key LK_CRYPTO_gcm128_init_key +CRYPTO_gcm128_setiv LK_CRYPTO_gcm128_setiv +CRYPTO_gcm128_tag LK_CRYPTO_gcm128_tag +CRYPTO_get_dynlock_create_callback LK_CRYPTO_get_dynlock_create_callback +CRYPTO_get_dynlock_destroy_callback LK_CRYPTO_get_dynlock_destroy_callback +CRYPTO_get_dynlock_lock_callback LK_CRYPTO_get_dynlock_lock_callback +CRYPTO_get_ex_data LK_CRYPTO_get_ex_data +CRYPTO_get_ex_new_index_ex LK_CRYPTO_get_ex_new_index_ex +CRYPTO_get_fork_generation LK_CRYPTO_get_fork_generation +CRYPTO_get_lock_name LK_CRYPTO_get_lock_name +CRYPTO_get_locking_callback LK_CRYPTO_get_locking_callback +CRYPTO_get_thread_local LK_CRYPTO_get_thread_local +CRYPTO_ghash_init LK_CRYPTO_ghash_init +CRYPTO_has_asm LK_CRYPTO_has_asm +CRYPTO_hchacha20 LK_CRYPTO_hchacha20 +CRYPTO_init_sysrand LK_CRYPTO_init_sysrand +CRYPTO_is_confidential_build LK_CRYPTO_is_confidential_build +CRYPTO_library_init LK_CRYPTO_library_init +CRYPTO_malloc LK_CRYPTO_malloc +CRYPTO_malloc_init LK_CRYPTO_malloc_init +CRYPTO_memcmp LK_CRYPTO_memcmp +CRYPTO_new_ex_data LK_CRYPTO_new_ex_data +CRYPTO_num_locks LK_CRYPTO_num_locks +CRYPTO_ofb128_encrypt LK_CRYPTO_ofb128_encrypt +CRYPTO_once LK_CRYPTO_once +CRYPTO_poly1305_finish LK_CRYPTO_poly1305_finish +CRYPTO_poly1305_init LK_CRYPTO_poly1305_init +CRYPTO_poly1305_update LK_CRYPTO_poly1305_update +CRYPTO_pre_sandbox_init LK_CRYPTO_pre_sandbox_init +CRYPTO_rdrand LK_CRYPTO_rdrand +CRYPTO_rdrand_multiple8_buf LK_CRYPTO_rdrand_multiple8_buf +CRYPTO_realloc LK_CRYPTO_realloc +CRYPTO_refcount_dec_and_test_zero LK_CRYPTO_refcount_dec_and_test_zero +CRYPTO_refcount_inc LK_CRYPTO_refcount_inc +CRYPTO_secure_malloc_init LK_CRYPTO_secure_malloc_init +CRYPTO_secure_malloc_initialized LK_CRYPTO_secure_malloc_initialized +CRYPTO_secure_used LK_CRYPTO_secure_used +CRYPTO_set_add_lock_callback LK_CRYPTO_set_add_lock_callback +CRYPTO_set_dynlock_create_callback LK_CRYPTO_set_dynlock_create_callback +CRYPTO_set_dynlock_destroy_callback LK_CRYPTO_set_dynlock_destroy_callback +CRYPTO_set_dynlock_lock_callback LK_CRYPTO_set_dynlock_lock_callback +CRYPTO_set_ex_data LK_CRYPTO_set_ex_data +CRYPTO_set_id_callback LK_CRYPTO_set_id_callback +CRYPTO_set_locking_callback LK_CRYPTO_set_locking_callback +CRYPTO_set_thread_local LK_CRYPTO_set_thread_local +CRYPTO_sysrand LK_CRYPTO_sysrand +CRYPTO_sysrand_for_seed LK_CRYPTO_sysrand_for_seed +CRYPTO_sysrand_if_available LK_CRYPTO_sysrand_if_available +CRYPTO_tls13_hkdf_expand_label LK_CRYPTO_tls13_hkdf_expand_label +CRYPTO_tls1_prf LK_CRYPTO_tls1_prf +CTR_DRBG_clear LK_CTR_DRBG_clear +CTR_DRBG_free LK_CTR_DRBG_free +CTR_DRBG_generate LK_CTR_DRBG_generate +CTR_DRBG_init LK_CTR_DRBG_init +CTR_DRBG_new LK_CTR_DRBG_new +CTR_DRBG_reseed LK_CTR_DRBG_reseed +ChaCha20_ctr32_avx2 LK_ChaCha20_ctr32_avx2 +ChaCha20_ctr32_nohw LK_ChaCha20_ctr32_nohw +ChaCha20_ctr32_ssse3 LK_ChaCha20_ctr32_ssse3 +ChaCha20_ctr32_ssse3_4x LK_ChaCha20_ctr32_ssse3_4x +DES_decrypt3 LK_DES_decrypt3 +DES_ecb3_encrypt LK_DES_ecb3_encrypt +DES_ecb3_encrypt_ex LK_DES_ecb3_encrypt_ex +DES_ecb_encrypt LK_DES_ecb_encrypt +DES_ecb_encrypt_ex LK_DES_ecb_encrypt_ex +DES_ede2_cbc_encrypt LK_DES_ede2_cbc_encrypt +DES_ede3_cbc_encrypt LK_DES_ede3_cbc_encrypt +DES_ede3_cbc_encrypt_ex LK_DES_ede3_cbc_encrypt_ex +DES_ede3_cfb64_encrypt LK_DES_ede3_cfb64_encrypt +DES_ede3_cfb_encrypt LK_DES_ede3_cfb_encrypt +DES_encrypt3 LK_DES_encrypt3 +DES_ncbc_encrypt LK_DES_ncbc_encrypt +DES_ncbc_encrypt_ex LK_DES_ncbc_encrypt_ex +DES_set_key LK_DES_set_key +DES_set_key_ex LK_DES_set_key_ex +DES_set_key_unchecked LK_DES_set_key_unchecked +DES_set_odd_parity LK_DES_set_odd_parity +DH_bits LK_DH_bits +DH_check LK_DH_check +DH_check_pub_key LK_DH_check_pub_key +DH_compute_key LK_DH_compute_key +DH_compute_key_hashed LK_DH_compute_key_hashed +DH_compute_key_padded LK_DH_compute_key_padded +DH_free LK_DH_free +DH_generate_key LK_DH_generate_key +DH_generate_parameters LK_DH_generate_parameters +DH_generate_parameters_ex LK_DH_generate_parameters_ex +DH_get0_g LK_DH_get0_g +DH_get0_key LK_DH_get0_key +DH_get0_p LK_DH_get0_p +DH_get0_pqg LK_DH_get0_pqg +DH_get0_priv_key LK_DH_get0_priv_key +DH_get0_pub_key LK_DH_get0_pub_key +DH_get0_q LK_DH_get0_q +DH_get_rfc7919_2048 LK_DH_get_rfc7919_2048 +DH_marshal_parameters LK_DH_marshal_parameters +DH_new LK_DH_new +DH_num_bits LK_DH_num_bits +DH_parse_parameters LK_DH_parse_parameters +DH_set0_key LK_DH_set0_key +DH_set0_pqg LK_DH_set0_pqg +DH_set_length LK_DH_set_length +DH_size LK_DH_size +DH_up_ref LK_DH_up_ref +DHparams_dup LK_DHparams_dup +DIRECTORYSTRING_free LK_DIRECTORYSTRING_free +DIRECTORYSTRING_it LK_DIRECTORYSTRING_it +DIRECTORYSTRING_new LK_DIRECTORYSTRING_new +DISPLAYTEXT_free LK_DISPLAYTEXT_free +DISPLAYTEXT_it LK_DISPLAYTEXT_it +DISPLAYTEXT_new LK_DISPLAYTEXT_new +DIST_POINT_NAME_free LK_DIST_POINT_NAME_free +DIST_POINT_NAME_it LK_DIST_POINT_NAME_it +DIST_POINT_NAME_new LK_DIST_POINT_NAME_new +DIST_POINT_free LK_DIST_POINT_free +DIST_POINT_it LK_DIST_POINT_it +DIST_POINT_new LK_DIST_POINT_new +DIST_POINT_set_dpname LK_DIST_POINT_set_dpname +DSA_SIG_free LK_DSA_SIG_free +DSA_SIG_get0 LK_DSA_SIG_get0 +DSA_SIG_marshal LK_DSA_SIG_marshal +DSA_SIG_new LK_DSA_SIG_new +DSA_SIG_parse LK_DSA_SIG_parse +DSA_SIG_set0 LK_DSA_SIG_set0 +DSA_bits LK_DSA_bits +DSA_check_signature LK_DSA_check_signature +DSA_do_check_signature LK_DSA_do_check_signature +DSA_do_sign LK_DSA_do_sign +DSA_do_verify LK_DSA_do_verify +DSA_dup_DH LK_DSA_dup_DH +DSA_free LK_DSA_free +DSA_generate_key LK_DSA_generate_key +DSA_generate_parameters LK_DSA_generate_parameters +DSA_generate_parameters_ex LK_DSA_generate_parameters_ex +DSA_get0_g LK_DSA_get0_g +DSA_get0_key LK_DSA_get0_key +DSA_get0_p LK_DSA_get0_p +DSA_get0_pqg LK_DSA_get0_pqg +DSA_get0_priv_key LK_DSA_get0_priv_key +DSA_get0_pub_key LK_DSA_get0_pub_key +DSA_get0_q LK_DSA_get0_q +DSA_get_ex_data LK_DSA_get_ex_data +DSA_get_ex_new_index LK_DSA_get_ex_new_index +DSA_marshal_parameters LK_DSA_marshal_parameters +DSA_marshal_private_key LK_DSA_marshal_private_key +DSA_marshal_public_key LK_DSA_marshal_public_key +DSA_new LK_DSA_new +DSA_parse_parameters LK_DSA_parse_parameters +DSA_parse_private_key LK_DSA_parse_private_key +DSA_parse_public_key LK_DSA_parse_public_key +DSA_set0_key LK_DSA_set0_key +DSA_set0_pqg LK_DSA_set0_pqg +DSA_set_ex_data LK_DSA_set_ex_data +DSA_sign LK_DSA_sign +DSA_size LK_DSA_size +DSA_up_ref LK_DSA_up_ref +DSA_verify LK_DSA_verify +DSAparams_dup LK_DSAparams_dup +DeleteThreadLocalValue LK_DeleteThreadLocalValue +ECDH_compute_key LK_ECDH_compute_key +ECDH_compute_key_fips LK_ECDH_compute_key_fips +ECDSA_SIG_free LK_ECDSA_SIG_free +ECDSA_SIG_from_bytes LK_ECDSA_SIG_from_bytes +ECDSA_SIG_get0 LK_ECDSA_SIG_get0 +ECDSA_SIG_get0_r LK_ECDSA_SIG_get0_r +ECDSA_SIG_get0_s LK_ECDSA_SIG_get0_s +ECDSA_SIG_marshal LK_ECDSA_SIG_marshal +ECDSA_SIG_max_len LK_ECDSA_SIG_max_len +ECDSA_SIG_new LK_ECDSA_SIG_new +ECDSA_SIG_parse LK_ECDSA_SIG_parse +ECDSA_SIG_set0 LK_ECDSA_SIG_set0 +ECDSA_SIG_to_bytes LK_ECDSA_SIG_to_bytes +ECDSA_do_sign LK_ECDSA_do_sign +ECDSA_do_verify LK_ECDSA_do_verify +ECDSA_sign LK_ECDSA_sign +ECDSA_sign_with_nonce_and_leak_private_key_for_testing LK_ECDSA_sign_with_nonce_and_leak_private_key_for_testing +ECDSA_size LK_ECDSA_size +ECDSA_verify LK_ECDSA_verify +EC_GFp_mont_method LK_EC_GFp_mont_method +EC_GFp_nistp224_method LK_EC_GFp_nistp224_method +EC_GFp_nistp256_method LK_EC_GFp_nistp256_method +EC_GFp_nistz256_method LK_EC_GFp_nistz256_method +EC_GROUP_cmp LK_EC_GROUP_cmp +EC_GROUP_dup LK_EC_GROUP_dup +EC_GROUP_free LK_EC_GROUP_free +EC_GROUP_get0_generator LK_EC_GROUP_get0_generator +EC_GROUP_get0_order LK_EC_GROUP_get0_order +EC_GROUP_get_asn1_flag LK_EC_GROUP_get_asn1_flag +EC_GROUP_get_cofactor LK_EC_GROUP_get_cofactor +EC_GROUP_get_curve_GFp LK_EC_GROUP_get_curve_GFp +EC_GROUP_get_curve_name LK_EC_GROUP_get_curve_name +EC_GROUP_get_degree LK_EC_GROUP_get_degree +EC_GROUP_get_order LK_EC_GROUP_get_order +EC_GROUP_method_of LK_EC_GROUP_method_of +EC_GROUP_new_by_curve_name LK_EC_GROUP_new_by_curve_name +EC_GROUP_new_curve_GFp LK_EC_GROUP_new_curve_GFp +EC_GROUP_order_bits LK_EC_GROUP_order_bits +EC_GROUP_set_asn1_flag LK_EC_GROUP_set_asn1_flag +EC_GROUP_set_generator LK_EC_GROUP_set_generator +EC_GROUP_set_point_conversion_form LK_EC_GROUP_set_point_conversion_form +EC_KEY_check_fips LK_EC_KEY_check_fips +EC_KEY_check_key LK_EC_KEY_check_key +EC_KEY_derive_from_secret LK_EC_KEY_derive_from_secret +EC_KEY_dup LK_EC_KEY_dup +EC_KEY_free LK_EC_KEY_free +EC_KEY_generate_key LK_EC_KEY_generate_key +EC_KEY_generate_key_fips LK_EC_KEY_generate_key_fips +EC_KEY_get0_group LK_EC_KEY_get0_group +EC_KEY_get0_private_key LK_EC_KEY_get0_private_key +EC_KEY_get0_public_key LK_EC_KEY_get0_public_key +EC_KEY_get_conv_form LK_EC_KEY_get_conv_form +EC_KEY_get_enc_flags LK_EC_KEY_get_enc_flags +EC_KEY_get_ex_data LK_EC_KEY_get_ex_data +EC_KEY_get_ex_new_index LK_EC_KEY_get_ex_new_index +EC_KEY_is_opaque LK_EC_KEY_is_opaque +EC_KEY_key2buf LK_EC_KEY_key2buf +EC_KEY_marshal_curve_name LK_EC_KEY_marshal_curve_name +EC_KEY_marshal_private_key LK_EC_KEY_marshal_private_key +EC_KEY_new LK_EC_KEY_new +EC_KEY_new_by_curve_name LK_EC_KEY_new_by_curve_name +EC_KEY_new_method LK_EC_KEY_new_method +EC_KEY_oct2key LK_EC_KEY_oct2key +EC_KEY_oct2priv LK_EC_KEY_oct2priv +EC_KEY_parse_curve_name LK_EC_KEY_parse_curve_name +EC_KEY_parse_parameters LK_EC_KEY_parse_parameters +EC_KEY_parse_private_key LK_EC_KEY_parse_private_key +EC_KEY_priv2buf LK_EC_KEY_priv2buf +EC_KEY_priv2oct LK_EC_KEY_priv2oct +EC_KEY_set_asn1_flag LK_EC_KEY_set_asn1_flag +EC_KEY_set_conv_form LK_EC_KEY_set_conv_form +EC_KEY_set_enc_flags LK_EC_KEY_set_enc_flags +EC_KEY_set_ex_data LK_EC_KEY_set_ex_data +EC_KEY_set_group LK_EC_KEY_set_group +EC_KEY_set_private_key LK_EC_KEY_set_private_key +EC_KEY_set_public_key LK_EC_KEY_set_public_key +EC_KEY_set_public_key_affine_coordinates LK_EC_KEY_set_public_key_affine_coordinates +EC_KEY_up_ref LK_EC_KEY_up_ref +EC_METHOD_get_field_type LK_EC_METHOD_get_field_type +EC_POINT_add LK_EC_POINT_add +EC_POINT_clear_free LK_EC_POINT_clear_free +EC_POINT_cmp LK_EC_POINT_cmp +EC_POINT_copy LK_EC_POINT_copy +EC_POINT_dbl LK_EC_POINT_dbl +EC_POINT_dup LK_EC_POINT_dup +EC_POINT_free LK_EC_POINT_free +EC_POINT_get_affine_coordinates LK_EC_POINT_get_affine_coordinates +EC_POINT_get_affine_coordinates_GFp LK_EC_POINT_get_affine_coordinates_GFp +EC_POINT_invert LK_EC_POINT_invert +EC_POINT_is_at_infinity LK_EC_POINT_is_at_infinity +EC_POINT_is_on_curve LK_EC_POINT_is_on_curve +EC_POINT_mul LK_EC_POINT_mul +EC_POINT_new LK_EC_POINT_new +EC_POINT_oct2point LK_EC_POINT_oct2point +EC_POINT_point2buf LK_EC_POINT_point2buf +EC_POINT_point2cbb LK_EC_POINT_point2cbb +EC_POINT_point2oct LK_EC_POINT_point2oct +EC_POINT_set_affine_coordinates LK_EC_POINT_set_affine_coordinates +EC_POINT_set_affine_coordinates_GFp LK_EC_POINT_set_affine_coordinates_GFp +EC_POINT_set_compressed_coordinates_GFp LK_EC_POINT_set_compressed_coordinates_GFp +EC_POINT_set_to_infinity LK_EC_POINT_set_to_infinity +EC_curve_nid2nist LK_EC_curve_nid2nist +EC_curve_nist2nid LK_EC_curve_nist2nid +EC_get_builtin_curves LK_EC_get_builtin_curves +EC_group_p224 LK_EC_group_p224 +EC_group_p256 LK_EC_group_p256 +EC_group_p384 LK_EC_group_p384 +EC_group_p521 LK_EC_group_p521 +EC_hash_to_curve_p256_xmd_sha256_sswu LK_EC_hash_to_curve_p256_xmd_sha256_sswu +EC_hash_to_curve_p384_xmd_sha384_sswu LK_EC_hash_to_curve_p384_xmd_sha384_sswu +ED25519_keypair LK_ED25519_keypair +ED25519_keypair_from_seed LK_ED25519_keypair_from_seed +ED25519_sign LK_ED25519_sign +ED25519_verify LK_ED25519_verify +EDIPARTYNAME_free LK_EDIPARTYNAME_free +EDIPARTYNAME_it LK_EDIPARTYNAME_it +EDIPARTYNAME_new LK_EDIPARTYNAME_new +ENGINE_free LK_ENGINE_free +ENGINE_get_ECDSA_method LK_ENGINE_get_ECDSA_method +ENGINE_get_RSA_method LK_ENGINE_get_RSA_method +ENGINE_load_builtin_engines LK_ENGINE_load_builtin_engines +ENGINE_new LK_ENGINE_new +ENGINE_register_all_complete LK_ENGINE_register_all_complete +ENGINE_set_ECDSA_method LK_ENGINE_set_ECDSA_method +ENGINE_set_RSA_method LK_ENGINE_set_RSA_method +ERR_SAVE_STATE_free LK_ERR_SAVE_STATE_free +ERR_add_error_data LK_ERR_add_error_data +ERR_add_error_dataf LK_ERR_add_error_dataf +ERR_clear_error LK_ERR_clear_error +ERR_clear_system_error LK_ERR_clear_system_error +ERR_error_string LK_ERR_error_string +ERR_error_string_n LK_ERR_error_string_n +ERR_free_strings LK_ERR_free_strings +ERR_func_error_string LK_ERR_func_error_string +ERR_get_error LK_ERR_get_error +ERR_get_error_line LK_ERR_get_error_line +ERR_get_error_line_data LK_ERR_get_error_line_data +ERR_get_next_error_library LK_ERR_get_next_error_library +ERR_lib_error_string LK_ERR_lib_error_string +ERR_lib_symbol_name LK_ERR_lib_symbol_name +ERR_load_BIO_strings LK_ERR_load_BIO_strings +ERR_load_ERR_strings LK_ERR_load_ERR_strings +ERR_load_RAND_strings LK_ERR_load_RAND_strings +ERR_load_crypto_strings LK_ERR_load_crypto_strings +ERR_peek_error LK_ERR_peek_error +ERR_peek_error_line LK_ERR_peek_error_line +ERR_peek_error_line_data LK_ERR_peek_error_line_data +ERR_peek_last_error LK_ERR_peek_last_error +ERR_peek_last_error_line LK_ERR_peek_last_error_line +ERR_peek_last_error_line_data LK_ERR_peek_last_error_line_data +ERR_pop_to_mark LK_ERR_pop_to_mark +ERR_print_errors LK_ERR_print_errors +ERR_print_errors_cb LK_ERR_print_errors_cb +ERR_print_errors_fp LK_ERR_print_errors_fp +ERR_put_error LK_ERR_put_error +ERR_reason_error_string LK_ERR_reason_error_string +ERR_reason_symbol_name LK_ERR_reason_symbol_name +ERR_remove_state LK_ERR_remove_state +ERR_remove_thread_state LK_ERR_remove_thread_state +ERR_restore_state LK_ERR_restore_state +ERR_save_state LK_ERR_save_state +ERR_set_error_data LK_ERR_set_error_data +ERR_set_mark LK_ERR_set_mark +EVP_AEAD_CTX_aead LK_EVP_AEAD_CTX_aead +EVP_AEAD_CTX_cleanup LK_EVP_AEAD_CTX_cleanup +EVP_AEAD_CTX_free LK_EVP_AEAD_CTX_free +EVP_AEAD_CTX_get_iv LK_EVP_AEAD_CTX_get_iv +EVP_AEAD_CTX_init LK_EVP_AEAD_CTX_init +EVP_AEAD_CTX_init_with_direction LK_EVP_AEAD_CTX_init_with_direction +EVP_AEAD_CTX_new LK_EVP_AEAD_CTX_new +EVP_AEAD_CTX_open LK_EVP_AEAD_CTX_open +EVP_AEAD_CTX_open_gather LK_EVP_AEAD_CTX_open_gather +EVP_AEAD_CTX_seal LK_EVP_AEAD_CTX_seal +EVP_AEAD_CTX_seal_scatter LK_EVP_AEAD_CTX_seal_scatter +EVP_AEAD_CTX_tag_len LK_EVP_AEAD_CTX_tag_len +EVP_AEAD_CTX_zero LK_EVP_AEAD_CTX_zero +EVP_AEAD_key_length LK_EVP_AEAD_key_length +EVP_AEAD_max_overhead LK_EVP_AEAD_max_overhead +EVP_AEAD_max_tag_len LK_EVP_AEAD_max_tag_len +EVP_AEAD_nonce_length LK_EVP_AEAD_nonce_length +EVP_BytesToKey LK_EVP_BytesToKey +EVP_CIPHER_CTX_block_size LK_EVP_CIPHER_CTX_block_size +EVP_CIPHER_CTX_cipher LK_EVP_CIPHER_CTX_cipher +EVP_CIPHER_CTX_cleanup LK_EVP_CIPHER_CTX_cleanup +EVP_CIPHER_CTX_copy LK_EVP_CIPHER_CTX_copy +EVP_CIPHER_CTX_ctrl LK_EVP_CIPHER_CTX_ctrl +EVP_CIPHER_CTX_encrypting LK_EVP_CIPHER_CTX_encrypting +EVP_CIPHER_CTX_flags LK_EVP_CIPHER_CTX_flags +EVP_CIPHER_CTX_free LK_EVP_CIPHER_CTX_free +EVP_CIPHER_CTX_get_app_data LK_EVP_CIPHER_CTX_get_app_data +EVP_CIPHER_CTX_init LK_EVP_CIPHER_CTX_init +EVP_CIPHER_CTX_iv_length LK_EVP_CIPHER_CTX_iv_length +EVP_CIPHER_CTX_key_length LK_EVP_CIPHER_CTX_key_length +EVP_CIPHER_CTX_mode LK_EVP_CIPHER_CTX_mode +EVP_CIPHER_CTX_new LK_EVP_CIPHER_CTX_new +EVP_CIPHER_CTX_nid LK_EVP_CIPHER_CTX_nid +EVP_CIPHER_CTX_reset LK_EVP_CIPHER_CTX_reset +EVP_CIPHER_CTX_set_app_data LK_EVP_CIPHER_CTX_set_app_data +EVP_CIPHER_CTX_set_flags LK_EVP_CIPHER_CTX_set_flags +EVP_CIPHER_CTX_set_key_length LK_EVP_CIPHER_CTX_set_key_length +EVP_CIPHER_CTX_set_padding LK_EVP_CIPHER_CTX_set_padding +EVP_CIPHER_block_size LK_EVP_CIPHER_block_size +EVP_CIPHER_do_all_sorted LK_EVP_CIPHER_do_all_sorted +EVP_CIPHER_flags LK_EVP_CIPHER_flags +EVP_CIPHER_iv_length LK_EVP_CIPHER_iv_length +EVP_CIPHER_key_length LK_EVP_CIPHER_key_length +EVP_CIPHER_mode LK_EVP_CIPHER_mode +EVP_CIPHER_nid LK_EVP_CIPHER_nid +EVP_Cipher LK_EVP_Cipher +EVP_CipherFinal LK_EVP_CipherFinal +EVP_CipherFinal_ex LK_EVP_CipherFinal_ex +EVP_CipherInit LK_EVP_CipherInit +EVP_CipherInit_ex LK_EVP_CipherInit_ex +EVP_CipherUpdate LK_EVP_CipherUpdate +EVP_DecodeBase64 LK_EVP_DecodeBase64 +EVP_DecodeBlock LK_EVP_DecodeBlock +EVP_DecodeFinal LK_EVP_DecodeFinal +EVP_DecodeInit LK_EVP_DecodeInit +EVP_DecodeUpdate LK_EVP_DecodeUpdate +EVP_DecodedLength LK_EVP_DecodedLength +EVP_DecryptFinal LK_EVP_DecryptFinal +EVP_DecryptFinal_ex LK_EVP_DecryptFinal_ex +EVP_DecryptInit LK_EVP_DecryptInit +EVP_DecryptInit_ex LK_EVP_DecryptInit_ex +EVP_DecryptUpdate LK_EVP_DecryptUpdate +EVP_Digest LK_EVP_Digest +EVP_DigestFinal LK_EVP_DigestFinal +EVP_DigestFinalXOF LK_EVP_DigestFinalXOF +EVP_DigestFinal_ex LK_EVP_DigestFinal_ex +EVP_DigestInit LK_EVP_DigestInit +EVP_DigestInit_ex LK_EVP_DigestInit_ex +EVP_DigestSign LK_EVP_DigestSign +EVP_DigestSignFinal LK_EVP_DigestSignFinal +EVP_DigestSignInit LK_EVP_DigestSignInit +EVP_DigestSignUpdate LK_EVP_DigestSignUpdate +EVP_DigestUpdate LK_EVP_DigestUpdate +EVP_DigestVerify LK_EVP_DigestVerify +EVP_DigestVerifyFinal LK_EVP_DigestVerifyFinal +EVP_DigestVerifyInit LK_EVP_DigestVerifyInit +EVP_DigestVerifyUpdate LK_EVP_DigestVerifyUpdate +EVP_ENCODE_CTX_free LK_EVP_ENCODE_CTX_free +EVP_ENCODE_CTX_new LK_EVP_ENCODE_CTX_new +EVP_EncodeBlock LK_EVP_EncodeBlock +EVP_EncodeFinal LK_EVP_EncodeFinal +EVP_EncodeInit LK_EVP_EncodeInit +EVP_EncodeUpdate LK_EVP_EncodeUpdate +EVP_EncodedLength LK_EVP_EncodedLength +EVP_EncryptFinal LK_EVP_EncryptFinal +EVP_EncryptFinal_ex LK_EVP_EncryptFinal_ex +EVP_EncryptInit LK_EVP_EncryptInit +EVP_EncryptInit_ex LK_EVP_EncryptInit_ex +EVP_EncryptUpdate LK_EVP_EncryptUpdate +EVP_HPKE_AEAD_aead LK_EVP_HPKE_AEAD_aead +EVP_HPKE_AEAD_id LK_EVP_HPKE_AEAD_id +EVP_HPKE_CTX_aead LK_EVP_HPKE_CTX_aead +EVP_HPKE_CTX_cleanup LK_EVP_HPKE_CTX_cleanup +EVP_HPKE_CTX_export LK_EVP_HPKE_CTX_export +EVP_HPKE_CTX_free LK_EVP_HPKE_CTX_free +EVP_HPKE_CTX_kdf LK_EVP_HPKE_CTX_kdf +EVP_HPKE_CTX_kem LK_EVP_HPKE_CTX_kem +EVP_HPKE_CTX_max_overhead LK_EVP_HPKE_CTX_max_overhead +EVP_HPKE_CTX_new LK_EVP_HPKE_CTX_new +EVP_HPKE_CTX_open LK_EVP_HPKE_CTX_open +EVP_HPKE_CTX_seal LK_EVP_HPKE_CTX_seal +EVP_HPKE_CTX_setup_auth_recipient LK_EVP_HPKE_CTX_setup_auth_recipient +EVP_HPKE_CTX_setup_auth_sender LK_EVP_HPKE_CTX_setup_auth_sender +EVP_HPKE_CTX_setup_auth_sender_with_seed_for_testing LK_EVP_HPKE_CTX_setup_auth_sender_with_seed_for_testing +EVP_HPKE_CTX_setup_recipient LK_EVP_HPKE_CTX_setup_recipient +EVP_HPKE_CTX_setup_sender LK_EVP_HPKE_CTX_setup_sender +EVP_HPKE_CTX_setup_sender_with_seed_for_testing LK_EVP_HPKE_CTX_setup_sender_with_seed_for_testing +EVP_HPKE_CTX_zero LK_EVP_HPKE_CTX_zero +EVP_HPKE_KDF_hkdf_md LK_EVP_HPKE_KDF_hkdf_md +EVP_HPKE_KDF_id LK_EVP_HPKE_KDF_id +EVP_HPKE_KEM_enc_len LK_EVP_HPKE_KEM_enc_len +EVP_HPKE_KEM_id LK_EVP_HPKE_KEM_id +EVP_HPKE_KEM_private_key_len LK_EVP_HPKE_KEM_private_key_len +EVP_HPKE_KEM_public_key_len LK_EVP_HPKE_KEM_public_key_len +EVP_HPKE_KEY_cleanup LK_EVP_HPKE_KEY_cleanup +EVP_HPKE_KEY_copy LK_EVP_HPKE_KEY_copy +EVP_HPKE_KEY_free LK_EVP_HPKE_KEY_free +EVP_HPKE_KEY_generate LK_EVP_HPKE_KEY_generate +EVP_HPKE_KEY_init LK_EVP_HPKE_KEY_init +EVP_HPKE_KEY_kem LK_EVP_HPKE_KEY_kem +EVP_HPKE_KEY_move LK_EVP_HPKE_KEY_move +EVP_HPKE_KEY_new LK_EVP_HPKE_KEY_new +EVP_HPKE_KEY_private_key LK_EVP_HPKE_KEY_private_key +EVP_HPKE_KEY_public_key LK_EVP_HPKE_KEY_public_key +EVP_HPKE_KEY_zero LK_EVP_HPKE_KEY_zero +EVP_MD_CTX_block_size LK_EVP_MD_CTX_block_size +EVP_MD_CTX_cleanse LK_EVP_MD_CTX_cleanse +EVP_MD_CTX_cleanup LK_EVP_MD_CTX_cleanup +EVP_MD_CTX_copy LK_EVP_MD_CTX_copy +EVP_MD_CTX_copy_ex LK_EVP_MD_CTX_copy_ex +EVP_MD_CTX_create LK_EVP_MD_CTX_create +EVP_MD_CTX_destroy LK_EVP_MD_CTX_destroy +EVP_MD_CTX_free LK_EVP_MD_CTX_free +EVP_MD_CTX_init LK_EVP_MD_CTX_init +EVP_MD_CTX_md LK_EVP_MD_CTX_md +EVP_MD_CTX_move LK_EVP_MD_CTX_move +EVP_MD_CTX_new LK_EVP_MD_CTX_new +EVP_MD_CTX_reset LK_EVP_MD_CTX_reset +EVP_MD_CTX_set_flags LK_EVP_MD_CTX_set_flags +EVP_MD_CTX_size LK_EVP_MD_CTX_size +EVP_MD_CTX_type LK_EVP_MD_CTX_type +EVP_MD_block_size LK_EVP_MD_block_size +EVP_MD_do_all LK_EVP_MD_do_all +EVP_MD_do_all_sorted LK_EVP_MD_do_all_sorted +EVP_MD_flags LK_EVP_MD_flags +EVP_MD_meth_get_flags LK_EVP_MD_meth_get_flags +EVP_MD_nid LK_EVP_MD_nid +EVP_MD_size LK_EVP_MD_size +EVP_MD_type LK_EVP_MD_type +EVP_PBE_scrypt LK_EVP_PBE_scrypt +EVP_PKCS82PKEY LK_EVP_PKCS82PKEY +EVP_PKEY2PKCS8 LK_EVP_PKEY2PKCS8 +EVP_PKEY_CTX_add1_hkdf_info LK_EVP_PKEY_CTX_add1_hkdf_info +EVP_PKEY_CTX_ctrl LK_EVP_PKEY_CTX_ctrl +EVP_PKEY_CTX_dup LK_EVP_PKEY_CTX_dup +EVP_PKEY_CTX_free LK_EVP_PKEY_CTX_free +EVP_PKEY_CTX_get0_pkey LK_EVP_PKEY_CTX_get0_pkey +EVP_PKEY_CTX_get0_rsa_oaep_label LK_EVP_PKEY_CTX_get0_rsa_oaep_label +EVP_PKEY_CTX_get_rsa_mgf1_md LK_EVP_PKEY_CTX_get_rsa_mgf1_md +EVP_PKEY_CTX_get_rsa_oaep_md LK_EVP_PKEY_CTX_get_rsa_oaep_md +EVP_PKEY_CTX_get_rsa_padding LK_EVP_PKEY_CTX_get_rsa_padding +EVP_PKEY_CTX_get_rsa_pss_saltlen LK_EVP_PKEY_CTX_get_rsa_pss_saltlen +EVP_PKEY_CTX_get_signature_md LK_EVP_PKEY_CTX_get_signature_md +EVP_PKEY_CTX_hkdf_mode LK_EVP_PKEY_CTX_hkdf_mode +EVP_PKEY_CTX_new LK_EVP_PKEY_CTX_new +EVP_PKEY_CTX_new_id LK_EVP_PKEY_CTX_new_id +EVP_PKEY_CTX_set0_rsa_oaep_label LK_EVP_PKEY_CTX_set0_rsa_oaep_label +EVP_PKEY_CTX_set1_hkdf_key LK_EVP_PKEY_CTX_set1_hkdf_key +EVP_PKEY_CTX_set1_hkdf_salt LK_EVP_PKEY_CTX_set1_hkdf_salt +EVP_PKEY_CTX_set_dh_pad LK_EVP_PKEY_CTX_set_dh_pad +EVP_PKEY_CTX_set_dsa_paramgen_bits LK_EVP_PKEY_CTX_set_dsa_paramgen_bits +EVP_PKEY_CTX_set_dsa_paramgen_q_bits LK_EVP_PKEY_CTX_set_dsa_paramgen_q_bits +EVP_PKEY_CTX_set_ec_param_enc LK_EVP_PKEY_CTX_set_ec_param_enc +EVP_PKEY_CTX_set_ec_paramgen_curve_nid LK_EVP_PKEY_CTX_set_ec_paramgen_curve_nid +EVP_PKEY_CTX_set_hkdf_md LK_EVP_PKEY_CTX_set_hkdf_md +EVP_PKEY_CTX_set_rsa_keygen_bits LK_EVP_PKEY_CTX_set_rsa_keygen_bits +EVP_PKEY_CTX_set_rsa_keygen_pubexp LK_EVP_PKEY_CTX_set_rsa_keygen_pubexp +EVP_PKEY_CTX_set_rsa_mgf1_md LK_EVP_PKEY_CTX_set_rsa_mgf1_md +EVP_PKEY_CTX_set_rsa_oaep_md LK_EVP_PKEY_CTX_set_rsa_oaep_md +EVP_PKEY_CTX_set_rsa_padding LK_EVP_PKEY_CTX_set_rsa_padding +EVP_PKEY_CTX_set_rsa_pss_keygen_md LK_EVP_PKEY_CTX_set_rsa_pss_keygen_md +EVP_PKEY_CTX_set_rsa_pss_keygen_mgf1_md LK_EVP_PKEY_CTX_set_rsa_pss_keygen_mgf1_md +EVP_PKEY_CTX_set_rsa_pss_keygen_saltlen LK_EVP_PKEY_CTX_set_rsa_pss_keygen_saltlen +EVP_PKEY_CTX_set_rsa_pss_saltlen LK_EVP_PKEY_CTX_set_rsa_pss_saltlen +EVP_PKEY_CTX_set_signature_md LK_EVP_PKEY_CTX_set_signature_md +EVP_PKEY_assign LK_EVP_PKEY_assign +EVP_PKEY_assign_DH LK_EVP_PKEY_assign_DH +EVP_PKEY_assign_DSA LK_EVP_PKEY_assign_DSA +EVP_PKEY_assign_EC_KEY LK_EVP_PKEY_assign_EC_KEY +EVP_PKEY_assign_RSA LK_EVP_PKEY_assign_RSA +EVP_PKEY_base_id LK_EVP_PKEY_base_id +EVP_PKEY_bits LK_EVP_PKEY_bits +EVP_PKEY_cmp LK_EVP_PKEY_cmp +EVP_PKEY_cmp_parameters LK_EVP_PKEY_cmp_parameters +EVP_PKEY_copy_parameters LK_EVP_PKEY_copy_parameters +EVP_PKEY_decrypt LK_EVP_PKEY_decrypt +EVP_PKEY_decrypt_init LK_EVP_PKEY_decrypt_init +EVP_PKEY_derive LK_EVP_PKEY_derive +EVP_PKEY_derive_init LK_EVP_PKEY_derive_init +EVP_PKEY_derive_set_peer LK_EVP_PKEY_derive_set_peer +EVP_PKEY_encrypt LK_EVP_PKEY_encrypt +EVP_PKEY_encrypt_init LK_EVP_PKEY_encrypt_init +EVP_PKEY_free LK_EVP_PKEY_free +EVP_PKEY_get0 LK_EVP_PKEY_get0 +EVP_PKEY_get0_DH LK_EVP_PKEY_get0_DH +EVP_PKEY_get0_DSA LK_EVP_PKEY_get0_DSA +EVP_PKEY_get0_EC_KEY LK_EVP_PKEY_get0_EC_KEY +EVP_PKEY_get0_RSA LK_EVP_PKEY_get0_RSA +EVP_PKEY_get1_DH LK_EVP_PKEY_get1_DH +EVP_PKEY_get1_DSA LK_EVP_PKEY_get1_DSA +EVP_PKEY_get1_EC_KEY LK_EVP_PKEY_get1_EC_KEY +EVP_PKEY_get1_RSA LK_EVP_PKEY_get1_RSA +EVP_PKEY_get1_tls_encodedpoint LK_EVP_PKEY_get1_tls_encodedpoint +EVP_PKEY_get_raw_private_key LK_EVP_PKEY_get_raw_private_key +EVP_PKEY_get_raw_public_key LK_EVP_PKEY_get_raw_public_key +EVP_PKEY_id LK_EVP_PKEY_id +EVP_PKEY_is_opaque LK_EVP_PKEY_is_opaque +EVP_PKEY_keygen LK_EVP_PKEY_keygen +EVP_PKEY_keygen_init LK_EVP_PKEY_keygen_init +EVP_PKEY_missing_parameters LK_EVP_PKEY_missing_parameters +EVP_PKEY_new LK_EVP_PKEY_new +EVP_PKEY_new_raw_private_key LK_EVP_PKEY_new_raw_private_key +EVP_PKEY_new_raw_public_key LK_EVP_PKEY_new_raw_public_key +EVP_PKEY_paramgen LK_EVP_PKEY_paramgen +EVP_PKEY_paramgen_init LK_EVP_PKEY_paramgen_init +EVP_PKEY_print_params LK_EVP_PKEY_print_params +EVP_PKEY_print_private LK_EVP_PKEY_print_private +EVP_PKEY_print_public LK_EVP_PKEY_print_public +EVP_PKEY_set1_DH LK_EVP_PKEY_set1_DH +EVP_PKEY_set1_DSA LK_EVP_PKEY_set1_DSA +EVP_PKEY_set1_EC_KEY LK_EVP_PKEY_set1_EC_KEY +EVP_PKEY_set1_RSA LK_EVP_PKEY_set1_RSA +EVP_PKEY_set1_tls_encodedpoint LK_EVP_PKEY_set1_tls_encodedpoint +EVP_PKEY_set_type LK_EVP_PKEY_set_type +EVP_PKEY_sign LK_EVP_PKEY_sign +EVP_PKEY_sign_init LK_EVP_PKEY_sign_init +EVP_PKEY_size LK_EVP_PKEY_size +EVP_PKEY_type LK_EVP_PKEY_type +EVP_PKEY_up_ref LK_EVP_PKEY_up_ref +EVP_PKEY_verify LK_EVP_PKEY_verify +EVP_PKEY_verify_init LK_EVP_PKEY_verify_init +EVP_PKEY_verify_recover LK_EVP_PKEY_verify_recover +EVP_PKEY_verify_recover_init LK_EVP_PKEY_verify_recover_init +EVP_SignFinal LK_EVP_SignFinal +EVP_SignInit LK_EVP_SignInit +EVP_SignInit_ex LK_EVP_SignInit_ex +EVP_SignUpdate LK_EVP_SignUpdate +EVP_VerifyFinal LK_EVP_VerifyFinal +EVP_VerifyInit LK_EVP_VerifyInit +EVP_VerifyInit_ex LK_EVP_VerifyInit_ex +EVP_VerifyUpdate LK_EVP_VerifyUpdate +EVP_add_cipher_alias LK_EVP_add_cipher_alias +EVP_add_digest LK_EVP_add_digest +EVP_aead_aes_128_cbc_sha1_tls LK_EVP_aead_aes_128_cbc_sha1_tls +EVP_aead_aes_128_cbc_sha1_tls_implicit_iv LK_EVP_aead_aes_128_cbc_sha1_tls_implicit_iv +EVP_aead_aes_128_cbc_sha256_tls LK_EVP_aead_aes_128_cbc_sha256_tls +EVP_aead_aes_128_ccm_bluetooth LK_EVP_aead_aes_128_ccm_bluetooth +EVP_aead_aes_128_ccm_bluetooth_8 LK_EVP_aead_aes_128_ccm_bluetooth_8 +EVP_aead_aes_128_ccm_matter LK_EVP_aead_aes_128_ccm_matter +EVP_aead_aes_128_ctr_hmac_sha256 LK_EVP_aead_aes_128_ctr_hmac_sha256 +EVP_aead_aes_128_gcm LK_EVP_aead_aes_128_gcm +EVP_aead_aes_128_gcm_randnonce LK_EVP_aead_aes_128_gcm_randnonce +EVP_aead_aes_128_gcm_siv LK_EVP_aead_aes_128_gcm_siv +EVP_aead_aes_128_gcm_tls12 LK_EVP_aead_aes_128_gcm_tls12 +EVP_aead_aes_128_gcm_tls13 LK_EVP_aead_aes_128_gcm_tls13 +EVP_aead_aes_192_gcm LK_EVP_aead_aes_192_gcm +EVP_aead_aes_256_cbc_sha1_tls LK_EVP_aead_aes_256_cbc_sha1_tls +EVP_aead_aes_256_cbc_sha1_tls_implicit_iv LK_EVP_aead_aes_256_cbc_sha1_tls_implicit_iv +EVP_aead_aes_256_ctr_hmac_sha256 LK_EVP_aead_aes_256_ctr_hmac_sha256 +EVP_aead_aes_256_gcm LK_EVP_aead_aes_256_gcm +EVP_aead_aes_256_gcm_randnonce LK_EVP_aead_aes_256_gcm_randnonce +EVP_aead_aes_256_gcm_siv LK_EVP_aead_aes_256_gcm_siv +EVP_aead_aes_256_gcm_tls12 LK_EVP_aead_aes_256_gcm_tls12 +EVP_aead_aes_256_gcm_tls13 LK_EVP_aead_aes_256_gcm_tls13 +EVP_aead_chacha20_poly1305 LK_EVP_aead_chacha20_poly1305 +EVP_aead_des_ede3_cbc_sha1_tls LK_EVP_aead_des_ede3_cbc_sha1_tls +EVP_aead_des_ede3_cbc_sha1_tls_implicit_iv LK_EVP_aead_des_ede3_cbc_sha1_tls_implicit_iv +EVP_aead_xchacha20_poly1305 LK_EVP_aead_xchacha20_poly1305 +EVP_aes_128_cbc LK_EVP_aes_128_cbc +EVP_aes_128_cfb LK_EVP_aes_128_cfb +EVP_aes_128_cfb128 LK_EVP_aes_128_cfb128 +EVP_aes_128_ctr LK_EVP_aes_128_ctr +EVP_aes_128_ecb LK_EVP_aes_128_ecb +EVP_aes_128_gcm LK_EVP_aes_128_gcm +EVP_aes_128_ofb LK_EVP_aes_128_ofb +EVP_aes_192_cbc LK_EVP_aes_192_cbc +EVP_aes_192_cfb LK_EVP_aes_192_cfb +EVP_aes_192_cfb128 LK_EVP_aes_192_cfb128 +EVP_aes_192_ctr LK_EVP_aes_192_ctr +EVP_aes_192_ecb LK_EVP_aes_192_ecb +EVP_aes_192_gcm LK_EVP_aes_192_gcm +EVP_aes_192_ofb LK_EVP_aes_192_ofb +EVP_aes_256_cbc LK_EVP_aes_256_cbc +EVP_aes_256_cfb LK_EVP_aes_256_cfb +EVP_aes_256_cfb128 LK_EVP_aes_256_cfb128 +EVP_aes_256_ctr LK_EVP_aes_256_ctr +EVP_aes_256_ecb LK_EVP_aes_256_ecb +EVP_aes_256_gcm LK_EVP_aes_256_gcm +EVP_aes_256_ofb LK_EVP_aes_256_ofb +EVP_aes_256_xts LK_EVP_aes_256_xts +EVP_bf_cbc LK_EVP_bf_cbc +EVP_bf_cfb LK_EVP_bf_cfb +EVP_bf_ecb LK_EVP_bf_ecb +EVP_blake2b256 LK_EVP_blake2b256 +EVP_cast5_cbc LK_EVP_cast5_cbc +EVP_cast5_ecb LK_EVP_cast5_ecb +EVP_cleanup LK_EVP_cleanup +EVP_des_cbc LK_EVP_des_cbc +EVP_des_ecb LK_EVP_des_ecb +EVP_des_ede LK_EVP_des_ede +EVP_des_ede3 LK_EVP_des_ede3 +EVP_des_ede3_cbc LK_EVP_des_ede3_cbc +EVP_des_ede3_ecb LK_EVP_des_ede3_ecb +EVP_des_ede_cbc LK_EVP_des_ede_cbc +EVP_dss1 LK_EVP_dss1 +EVP_enc_null LK_EVP_enc_null +EVP_get_cipherbyname LK_EVP_get_cipherbyname +EVP_get_cipherbynid LK_EVP_get_cipherbynid +EVP_get_digestbyname LK_EVP_get_digestbyname +EVP_get_digestbynid LK_EVP_get_digestbynid +EVP_get_digestbyobj LK_EVP_get_digestbyobj +EVP_has_aes_hardware LK_EVP_has_aes_hardware +EVP_hpke_aes_128_gcm LK_EVP_hpke_aes_128_gcm +EVP_hpke_aes_256_gcm LK_EVP_hpke_aes_256_gcm +EVP_hpke_chacha20_poly1305 LK_EVP_hpke_chacha20_poly1305 +EVP_hpke_hkdf_sha256 LK_EVP_hpke_hkdf_sha256 +EVP_hpke_x25519_hkdf_sha256 LK_EVP_hpke_x25519_hkdf_sha256 +EVP_marshal_digest_algorithm LK_EVP_marshal_digest_algorithm +EVP_marshal_private_key LK_EVP_marshal_private_key +EVP_marshal_public_key LK_EVP_marshal_public_key +EVP_md4 LK_EVP_md4 +EVP_md5 LK_EVP_md5 +EVP_md5_sha1 LK_EVP_md5_sha1 +EVP_parse_digest_algorithm LK_EVP_parse_digest_algorithm +EVP_parse_private_key LK_EVP_parse_private_key +EVP_parse_public_key LK_EVP_parse_public_key +EVP_rc2_40_cbc LK_EVP_rc2_40_cbc +EVP_rc2_cbc LK_EVP_rc2_cbc +EVP_rc4 LK_EVP_rc4 +EVP_sha1 LK_EVP_sha1 +EVP_sha1_final_with_secret_suffix LK_EVP_sha1_final_with_secret_suffix +EVP_sha224 LK_EVP_sha224 +EVP_sha256 LK_EVP_sha256 +EVP_sha256_final_with_secret_suffix LK_EVP_sha256_final_with_secret_suffix +EVP_sha384 LK_EVP_sha384 +EVP_sha512 LK_EVP_sha512 +EVP_sha512_256 LK_EVP_sha512_256 +EVP_tls_cbc_copy_mac LK_EVP_tls_cbc_copy_mac +EVP_tls_cbc_digest_record LK_EVP_tls_cbc_digest_record +EVP_tls_cbc_record_digest_supported LK_EVP_tls_cbc_record_digest_supported +EVP_tls_cbc_remove_padding LK_EVP_tls_cbc_remove_padding +EXTENDED_KEY_USAGE_free LK_EXTENDED_KEY_USAGE_free +EXTENDED_KEY_USAGE_it LK_EXTENDED_KEY_USAGE_it +EXTENDED_KEY_USAGE_new LK_EXTENDED_KEY_USAGE_new +FIPS_mode LK_FIPS_mode +FIPS_mode_set LK_FIPS_mode_set +FIPS_module_name LK_FIPS_module_name +FIPS_query_algorithm_status LK_FIPS_query_algorithm_status +FIPS_read_counter LK_FIPS_read_counter +FIPS_service_indicator_after_call LK_FIPS_service_indicator_after_call +FIPS_service_indicator_before_call LK_FIPS_service_indicator_before_call +FIPS_version LK_FIPS_version +GENERAL_NAMES_free LK_GENERAL_NAMES_free +GENERAL_NAMES_it LK_GENERAL_NAMES_it +GENERAL_NAMES_new LK_GENERAL_NAMES_new +GENERAL_NAME_cmp LK_GENERAL_NAME_cmp +GENERAL_NAME_dup LK_GENERAL_NAME_dup +GENERAL_NAME_free LK_GENERAL_NAME_free +GENERAL_NAME_get0_otherName LK_GENERAL_NAME_get0_otherName +GENERAL_NAME_get0_value LK_GENERAL_NAME_get0_value +GENERAL_NAME_it LK_GENERAL_NAME_it +GENERAL_NAME_new LK_GENERAL_NAME_new +GENERAL_NAME_print LK_GENERAL_NAME_print +GENERAL_NAME_set0_othername LK_GENERAL_NAME_set0_othername +GENERAL_NAME_set0_value LK_GENERAL_NAME_set0_value +GENERAL_SUBTREE_free LK_GENERAL_SUBTREE_free +GENERAL_SUBTREE_it LK_GENERAL_SUBTREE_it +GENERAL_SUBTREE_new LK_GENERAL_SUBTREE_new +HKDF LK_HKDF +HKDF_expand LK_HKDF_expand +HKDF_extract LK_HKDF_extract +HMAC LK_HMAC +HMAC_CTX_cleanse LK_HMAC_CTX_cleanse +HMAC_CTX_cleanup LK_HMAC_CTX_cleanup +HMAC_CTX_copy LK_HMAC_CTX_copy +HMAC_CTX_copy_ex LK_HMAC_CTX_copy_ex +HMAC_CTX_free LK_HMAC_CTX_free +HMAC_CTX_get_md LK_HMAC_CTX_get_md +HMAC_CTX_init LK_HMAC_CTX_init +HMAC_CTX_new LK_HMAC_CTX_new +HMAC_CTX_reset LK_HMAC_CTX_reset +HMAC_Final LK_HMAC_Final +HMAC_Init LK_HMAC_Init +HMAC_Init_ex LK_HMAC_Init_ex +HMAC_Update LK_HMAC_Update +HMAC_size LK_HMAC_size +HRSS_decap LK_HRSS_decap +HRSS_encap LK_HRSS_encap +HRSS_generate_key LK_HRSS_generate_key +HRSS_marshal_public_key LK_HRSS_marshal_public_key +HRSS_parse_public_key LK_HRSS_parse_public_key +HRSS_poly3_invert LK_HRSS_poly3_invert +HRSS_poly3_mul LK_HRSS_poly3_mul +ISSUING_DIST_POINT_free LK_ISSUING_DIST_POINT_free +ISSUING_DIST_POINT_it LK_ISSUING_DIST_POINT_it +ISSUING_DIST_POINT_new LK_ISSUING_DIST_POINT_new +KYBER_decap LK_KYBER_decap +KYBER_encap LK_KYBER_encap +KYBER_encap_external_entropy LK_KYBER_encap_external_entropy +KYBER_generate_key LK_KYBER_generate_key +KYBER_generate_key_external_entropy LK_KYBER_generate_key_external_entropy +KYBER_marshal_private_key LK_KYBER_marshal_private_key +KYBER_marshal_public_key LK_KYBER_marshal_public_key +KYBER_parse_private_key LK_KYBER_parse_private_key +KYBER_parse_public_key LK_KYBER_parse_public_key +KYBER_public_from_private LK_KYBER_public_from_private +BIO_f_ssl LK_BIO_f_ssl +BIO_set_ssl LK_BIO_set_ssl +CBS_data LK_CBS_data +CBS_init LK_CBS_init +CBS_len LK_CBS_len +DTLS_client_method LK_DTLS_client_method +DTLS_method LK_DTLS_method +DTLS_server_method LK_DTLS_server_method +DTLS_with_buffers_method LK_DTLS_with_buffers_method +DTLSv1_2_client_method LK_DTLSv1_2_client_method +DTLSv1_2_method LK_DTLSv1_2_method +DTLSv1_2_server_method LK_DTLSv1_2_server_method +DTLSv1_client_method LK_DTLSv1_client_method +DTLSv1_get_timeout LK_DTLSv1_get_timeout +DTLSv1_handle_timeout LK_DTLSv1_handle_timeout +DTLSv1_method LK_DTLSv1_method +DTLSv1_server_method LK_DTLSv1_server_method +DTLSv1_set_initial_timeout_duration LK_DTLSv1_set_initial_timeout_duration +ERR_GET_LIB LK_ERR_GET_LIB +ERR_GET_REASON LK_ERR_GET_REASON +ERR_load_SSL_strings LK_ERR_load_SSL_strings +OPENSSL_init_ssl LK_OPENSSL_init_ssl +PEM_read_SSL_SESSION LK_PEM_read_SSL_SESSION +PEM_read_bio_SSL_SESSION LK_PEM_read_bio_SSL_SESSION +PEM_write_SSL_SESSION LK_PEM_write_SSL_SESSION +PEM_write_bio_SSL_SESSION LK_PEM_write_bio_SSL_SESSION +SSL_CIPHER_description LK_SSL_CIPHER_description +SSL_CIPHER_get_auth_nid LK_SSL_CIPHER_get_auth_nid +SSL_CIPHER_get_bits LK_SSL_CIPHER_get_bits +SSL_CIPHER_get_cipher_nid LK_SSL_CIPHER_get_cipher_nid +SSL_CIPHER_get_digest_nid LK_SSL_CIPHER_get_digest_nid +SSL_CIPHER_get_handshake_digest LK_SSL_CIPHER_get_handshake_digest +SSL_CIPHER_get_id LK_SSL_CIPHER_get_id +SSL_CIPHER_get_kx_name LK_SSL_CIPHER_get_kx_name +SSL_CIPHER_get_kx_nid LK_SSL_CIPHER_get_kx_nid +SSL_CIPHER_get_max_version LK_SSL_CIPHER_get_max_version +SSL_CIPHER_get_min_version LK_SSL_CIPHER_get_min_version +SSL_CIPHER_get_name LK_SSL_CIPHER_get_name +SSL_CIPHER_get_prf_nid LK_SSL_CIPHER_get_prf_nid +SSL_CIPHER_get_protocol_id LK_SSL_CIPHER_get_protocol_id +SSL_CIPHER_get_version LK_SSL_CIPHER_get_version +SSL_CIPHER_is_aead LK_SSL_CIPHER_is_aead +SSL_CIPHER_is_block_cipher LK_SSL_CIPHER_is_block_cipher +SSL_CIPHER_standard_name LK_SSL_CIPHER_standard_name +SSL_COMP_add_compression_method LK_SSL_COMP_add_compression_method +SSL_COMP_free_compression_methods LK_SSL_COMP_free_compression_methods +SSL_COMP_get0_name LK_SSL_COMP_get0_name +SSL_COMP_get_compression_methods LK_SSL_COMP_get_compression_methods +SSL_COMP_get_id LK_SSL_COMP_get_id +SSL_COMP_get_name LK_SSL_COMP_get_name +SSL_CREDENTIAL_free LK_SSL_CREDENTIAL_free +SSL_CREDENTIAL_get_ex_data LK_SSL_CREDENTIAL_get_ex_data +SSL_CREDENTIAL_get_ex_new_index LK_SSL_CREDENTIAL_get_ex_new_index +SSL_CREDENTIAL_new_delegated LK_SSL_CREDENTIAL_new_delegated +SSL_CREDENTIAL_new_x509 LK_SSL_CREDENTIAL_new_x509 +SSL_CREDENTIAL_set1_cert_chain LK_SSL_CREDENTIAL_set1_cert_chain +SSL_CREDENTIAL_set1_delegated_credential LK_SSL_CREDENTIAL_set1_delegated_credential +SSL_CREDENTIAL_set1_ocsp_response LK_SSL_CREDENTIAL_set1_ocsp_response +SSL_CREDENTIAL_set1_private_key LK_SSL_CREDENTIAL_set1_private_key +SSL_CREDENTIAL_set1_signed_cert_timestamp_list LK_SSL_CREDENTIAL_set1_signed_cert_timestamp_list +SSL_CREDENTIAL_set1_signing_algorithm_prefs LK_SSL_CREDENTIAL_set1_signing_algorithm_prefs +SSL_CREDENTIAL_set_ex_data LK_SSL_CREDENTIAL_set_ex_data +SSL_CREDENTIAL_set_private_key_method LK_SSL_CREDENTIAL_set_private_key_method +SSL_CREDENTIAL_up_ref LK_SSL_CREDENTIAL_up_ref +SSL_CTX_add0_chain_cert LK_SSL_CTX_add0_chain_cert +SSL_CTX_add1_chain_cert LK_SSL_CTX_add1_chain_cert +SSL_CTX_add1_credential LK_SSL_CTX_add1_credential +SSL_CTX_add_cert_compression_alg LK_SSL_CTX_add_cert_compression_alg +SSL_CTX_add_client_CA LK_SSL_CTX_add_client_CA +SSL_CTX_add_extra_chain_cert LK_SSL_CTX_add_extra_chain_cert +SSL_CTX_add_session LK_SSL_CTX_add_session +SSL_CTX_check_private_key LK_SSL_CTX_check_private_key +SSL_CTX_cipher_in_group LK_SSL_CTX_cipher_in_group +SSL_CTX_clear_chain_certs LK_SSL_CTX_clear_chain_certs +SSL_CTX_clear_extra_chain_certs LK_SSL_CTX_clear_extra_chain_certs +SSL_CTX_clear_mode LK_SSL_CTX_clear_mode +SSL_CTX_clear_options LK_SSL_CTX_clear_options +SSL_CTX_enable_ocsp_stapling LK_SSL_CTX_enable_ocsp_stapling +SSL_CTX_enable_signed_cert_timestamps LK_SSL_CTX_enable_signed_cert_timestamps +SSL_CTX_enable_tls_channel_id LK_SSL_CTX_enable_tls_channel_id +SSL_CTX_flush_sessions LK_SSL_CTX_flush_sessions +SSL_CTX_free LK_SSL_CTX_free +SSL_CTX_get0_certificate LK_SSL_CTX_get0_certificate +SSL_CTX_get0_chain LK_SSL_CTX_get0_chain +SSL_CTX_get0_chain_certs LK_SSL_CTX_get0_chain_certs +SSL_CTX_get0_param LK_SSL_CTX_get0_param +SSL_CTX_get0_privatekey LK_SSL_CTX_get0_privatekey +SSL_CTX_get_cert_store LK_SSL_CTX_get_cert_store +SSL_CTX_get_ciphers LK_SSL_CTX_get_ciphers +SSL_CTX_get_client_CA_list LK_SSL_CTX_get_client_CA_list +SSL_CTX_get_default_passwd_cb LK_SSL_CTX_get_default_passwd_cb +SSL_CTX_get_default_passwd_cb_userdata LK_SSL_CTX_get_default_passwd_cb_userdata +SSL_CTX_get_ex_data LK_SSL_CTX_get_ex_data +SSL_CTX_get_ex_new_index LK_SSL_CTX_get_ex_new_index +SSL_CTX_get_extra_chain_certs LK_SSL_CTX_get_extra_chain_certs +SSL_CTX_get_info_callback LK_SSL_CTX_get_info_callback +SSL_CTX_get_keylog_callback LK_SSL_CTX_get_keylog_callback +SSL_CTX_get_max_cert_list LK_SSL_CTX_get_max_cert_list +SSL_CTX_get_max_proto_version LK_SSL_CTX_get_max_proto_version +SSL_CTX_get_min_proto_version LK_SSL_CTX_get_min_proto_version +SSL_CTX_get_mode LK_SSL_CTX_get_mode +SSL_CTX_get_num_tickets LK_SSL_CTX_get_num_tickets +SSL_CTX_get_options LK_SSL_CTX_get_options +SSL_CTX_get_quiet_shutdown LK_SSL_CTX_get_quiet_shutdown +SSL_CTX_get_read_ahead LK_SSL_CTX_get_read_ahead +SSL_CTX_get_session_cache_mode LK_SSL_CTX_get_session_cache_mode +SSL_CTX_get_timeout LK_SSL_CTX_get_timeout +SSL_CTX_get_tlsext_ticket_keys LK_SSL_CTX_get_tlsext_ticket_keys +SSL_CTX_get_verify_callback LK_SSL_CTX_get_verify_callback +SSL_CTX_get_verify_depth LK_SSL_CTX_get_verify_depth +SSL_CTX_get_verify_mode LK_SSL_CTX_get_verify_mode +SSL_CTX_load_verify_locations LK_SSL_CTX_load_verify_locations +SSL_CTX_need_tmp_RSA LK_SSL_CTX_need_tmp_RSA +SSL_CTX_new LK_SSL_CTX_new +SSL_CTX_remove_session LK_SSL_CTX_remove_session +SSL_CTX_sess_accept LK_SSL_CTX_sess_accept +SSL_CTX_sess_accept_good LK_SSL_CTX_sess_accept_good +SSL_CTX_sess_accept_renegotiate LK_SSL_CTX_sess_accept_renegotiate +SSL_CTX_sess_cache_full LK_SSL_CTX_sess_cache_full +SSL_CTX_sess_cb_hits LK_SSL_CTX_sess_cb_hits +SSL_CTX_sess_connect LK_SSL_CTX_sess_connect +SSL_CTX_sess_connect_good LK_SSL_CTX_sess_connect_good +SSL_CTX_sess_connect_renegotiate LK_SSL_CTX_sess_connect_renegotiate +SSL_CTX_sess_get_cache_size LK_SSL_CTX_sess_get_cache_size +SSL_CTX_sess_get_get_cb LK_SSL_CTX_sess_get_get_cb +SSL_CTX_sess_get_new_cb LK_SSL_CTX_sess_get_new_cb +SSL_CTX_sess_get_remove_cb LK_SSL_CTX_sess_get_remove_cb +SSL_CTX_sess_hits LK_SSL_CTX_sess_hits +SSL_CTX_sess_misses LK_SSL_CTX_sess_misses +SSL_CTX_sess_number LK_SSL_CTX_sess_number +SSL_CTX_sess_set_cache_size LK_SSL_CTX_sess_set_cache_size +SSL_CTX_sess_set_get_cb LK_SSL_CTX_sess_set_get_cb +SSL_CTX_sess_set_new_cb LK_SSL_CTX_sess_set_new_cb +SSL_CTX_sess_set_remove_cb LK_SSL_CTX_sess_set_remove_cb +SSL_CTX_sess_timeouts LK_SSL_CTX_sess_timeouts +SSL_CTX_set0_buffer_pool LK_SSL_CTX_set0_buffer_pool +SSL_CTX_set0_chain LK_SSL_CTX_set0_chain +SSL_CTX_set0_client_CAs LK_SSL_CTX_set0_client_CAs +SSL_CTX_set0_verify_cert_store LK_SSL_CTX_set0_verify_cert_store +SSL_CTX_set1_chain LK_SSL_CTX_set1_chain +SSL_CTX_set1_curves LK_SSL_CTX_set1_curves +SSL_CTX_set1_curves_list LK_SSL_CTX_set1_curves_list +SSL_CTX_set1_ech_keys LK_SSL_CTX_set1_ech_keys +SSL_CTX_set1_group_ids LK_SSL_CTX_set1_group_ids +SSL_CTX_set1_groups LK_SSL_CTX_set1_groups +SSL_CTX_set1_groups_list LK_SSL_CTX_set1_groups_list +SSL_CTX_set1_param LK_SSL_CTX_set1_param +SSL_CTX_set1_sigalgs LK_SSL_CTX_set1_sigalgs +SSL_CTX_set1_sigalgs_list LK_SSL_CTX_set1_sigalgs_list +SSL_CTX_set1_tls_channel_id LK_SSL_CTX_set1_tls_channel_id +SSL_CTX_set1_verify_cert_store LK_SSL_CTX_set1_verify_cert_store +SSL_CTX_set_allow_unknown_alpn_protos LK_SSL_CTX_set_allow_unknown_alpn_protos +SSL_CTX_set_alpn_protos LK_SSL_CTX_set_alpn_protos +SSL_CTX_set_alpn_select_cb LK_SSL_CTX_set_alpn_select_cb +SSL_CTX_set_cert_cb LK_SSL_CTX_set_cert_cb +SSL_CTX_set_cert_store LK_SSL_CTX_set_cert_store +SSL_CTX_set_cert_verify_callback LK_SSL_CTX_set_cert_verify_callback +SSL_CTX_set_chain_and_key LK_SSL_CTX_set_chain_and_key +SSL_CTX_set_cipher_list LK_SSL_CTX_set_cipher_list +SSL_CTX_set_client_CA_list LK_SSL_CTX_set_client_CA_list +SSL_CTX_set_client_cert_cb LK_SSL_CTX_set_client_cert_cb +SSL_CTX_set_compliance_policy LK_SSL_CTX_set_compliance_policy +SSL_CTX_set_current_time_cb LK_SSL_CTX_set_current_time_cb +SSL_CTX_set_custom_verify LK_SSL_CTX_set_custom_verify +SSL_CTX_set_default_passwd_cb LK_SSL_CTX_set_default_passwd_cb +SSL_CTX_set_default_passwd_cb_userdata LK_SSL_CTX_set_default_passwd_cb_userdata +SSL_CTX_set_default_verify_paths LK_SSL_CTX_set_default_verify_paths +SSL_CTX_set_dos_protection_cb LK_SSL_CTX_set_dos_protection_cb +SSL_CTX_set_early_data_enabled LK_SSL_CTX_set_early_data_enabled +SSL_CTX_set_ex_data LK_SSL_CTX_set_ex_data +SSL_CTX_set_false_start_allowed_without_alpn LK_SSL_CTX_set_false_start_allowed_without_alpn +SSL_CTX_set_grease_enabled LK_SSL_CTX_set_grease_enabled +SSL_CTX_set_info_callback LK_SSL_CTX_set_info_callback +SSL_CTX_set_keylog_callback LK_SSL_CTX_set_keylog_callback +SSL_CTX_set_max_cert_list LK_SSL_CTX_set_max_cert_list +SSL_CTX_set_max_proto_version LK_SSL_CTX_set_max_proto_version +SSL_CTX_set_max_send_fragment LK_SSL_CTX_set_max_send_fragment +SSL_CTX_set_min_proto_version LK_SSL_CTX_set_min_proto_version +SSL_CTX_set_mode LK_SSL_CTX_set_mode +SSL_CTX_set_msg_callback LK_SSL_CTX_set_msg_callback +SSL_CTX_set_msg_callback_arg LK_SSL_CTX_set_msg_callback_arg +SSL_CTX_set_next_proto_select_cb LK_SSL_CTX_set_next_proto_select_cb +SSL_CTX_set_next_protos_advertised_cb LK_SSL_CTX_set_next_protos_advertised_cb +SSL_CTX_set_num_tickets LK_SSL_CTX_set_num_tickets +SSL_CTX_set_ocsp_response LK_SSL_CTX_set_ocsp_response +SSL_CTX_set_options LK_SSL_CTX_set_options +SSL_CTX_set_permute_extensions LK_SSL_CTX_set_permute_extensions +SSL_CTX_set_private_key_method LK_SSL_CTX_set_private_key_method +SSL_CTX_set_psk_client_callback LK_SSL_CTX_set_psk_client_callback +SSL_CTX_set_psk_server_callback LK_SSL_CTX_set_psk_server_callback +SSL_CTX_set_purpose LK_SSL_CTX_set_purpose +SSL_CTX_set_quic_method LK_SSL_CTX_set_quic_method +SSL_CTX_set_quiet_shutdown LK_SSL_CTX_set_quiet_shutdown +SSL_CTX_set_read_ahead LK_SSL_CTX_set_read_ahead +SSL_CTX_set_record_protocol_version LK_SSL_CTX_set_record_protocol_version +SSL_CTX_set_retain_only_sha256_of_client_certs LK_SSL_CTX_set_retain_only_sha256_of_client_certs +SSL_CTX_set_reverify_on_resume LK_SSL_CTX_set_reverify_on_resume +SSL_CTX_set_select_certificate_cb LK_SSL_CTX_set_select_certificate_cb +SSL_CTX_set_session_cache_mode LK_SSL_CTX_set_session_cache_mode +SSL_CTX_set_session_id_context LK_SSL_CTX_set_session_id_context +SSL_CTX_set_session_psk_dhe_timeout LK_SSL_CTX_set_session_psk_dhe_timeout +SSL_CTX_set_signed_cert_timestamp_list LK_SSL_CTX_set_signed_cert_timestamp_list +SSL_CTX_set_signing_algorithm_prefs LK_SSL_CTX_set_signing_algorithm_prefs +SSL_CTX_set_srtp_profiles LK_SSL_CTX_set_srtp_profiles +SSL_CTX_set_strict_cipher_list LK_SSL_CTX_set_strict_cipher_list +SSL_CTX_set_ticket_aead_method LK_SSL_CTX_set_ticket_aead_method +SSL_CTX_set_timeout LK_SSL_CTX_set_timeout +SSL_CTX_set_tls_channel_id_enabled LK_SSL_CTX_set_tls_channel_id_enabled +SSL_CTX_set_tlsext_servername_arg LK_SSL_CTX_set_tlsext_servername_arg +SSL_CTX_set_tlsext_servername_callback LK_SSL_CTX_set_tlsext_servername_callback +SSL_CTX_set_tlsext_status_arg LK_SSL_CTX_set_tlsext_status_arg +SSL_CTX_set_tlsext_status_cb LK_SSL_CTX_set_tlsext_status_cb +SSL_CTX_set_tlsext_ticket_key_cb LK_SSL_CTX_set_tlsext_ticket_key_cb +SSL_CTX_set_tlsext_ticket_keys LK_SSL_CTX_set_tlsext_ticket_keys +SSL_CTX_set_tlsext_use_srtp LK_SSL_CTX_set_tlsext_use_srtp +SSL_CTX_set_tmp_dh LK_SSL_CTX_set_tmp_dh +SSL_CTX_set_tmp_dh_callback LK_SSL_CTX_set_tmp_dh_callback +SSL_CTX_set_tmp_ecdh LK_SSL_CTX_set_tmp_ecdh +SSL_CTX_set_tmp_rsa LK_SSL_CTX_set_tmp_rsa +SSL_CTX_set_tmp_rsa_callback LK_SSL_CTX_set_tmp_rsa_callback +SSL_CTX_set_trust LK_SSL_CTX_set_trust +SSL_CTX_set_verify LK_SSL_CTX_set_verify +SSL_CTX_set_verify_algorithm_prefs LK_SSL_CTX_set_verify_algorithm_prefs +SSL_CTX_set_verify_depth LK_SSL_CTX_set_verify_depth +SSL_CTX_up_ref LK_SSL_CTX_up_ref +SSL_CTX_use_PrivateKey LK_SSL_CTX_use_PrivateKey +SSL_CTX_use_PrivateKey_ASN1 LK_SSL_CTX_use_PrivateKey_ASN1 +SSL_CTX_use_PrivateKey_file LK_SSL_CTX_use_PrivateKey_file +SSL_CTX_use_RSAPrivateKey LK_SSL_CTX_use_RSAPrivateKey +SSL_CTX_use_RSAPrivateKey_ASN1 LK_SSL_CTX_use_RSAPrivateKey_ASN1 +SSL_CTX_use_RSAPrivateKey_file LK_SSL_CTX_use_RSAPrivateKey_file +SSL_CTX_use_certificate LK_SSL_CTX_use_certificate +SSL_CTX_use_certificate_ASN1 LK_SSL_CTX_use_certificate_ASN1 +SSL_CTX_use_certificate_chain_file LK_SSL_CTX_use_certificate_chain_file +SSL_CTX_use_certificate_file LK_SSL_CTX_use_certificate_file +SSL_CTX_use_psk_identity_hint LK_SSL_CTX_use_psk_identity_hint +SSL_ECH_KEYS_add LK_SSL_ECH_KEYS_add +SSL_ECH_KEYS_free LK_SSL_ECH_KEYS_free +SSL_ECH_KEYS_has_duplicate_config_id LK_SSL_ECH_KEYS_has_duplicate_config_id +SSL_ECH_KEYS_marshal_retry_configs LK_SSL_ECH_KEYS_marshal_retry_configs +SSL_ECH_KEYS_new LK_SSL_ECH_KEYS_new +SSL_ECH_KEYS_up_ref LK_SSL_ECH_KEYS_up_ref +SSL_SESSION_copy_without_early_data LK_SSL_SESSION_copy_without_early_data +SSL_SESSION_early_data_capable LK_SSL_SESSION_early_data_capable +SSL_SESSION_free LK_SSL_SESSION_free +SSL_SESSION_from_bytes LK_SSL_SESSION_from_bytes +SSL_SESSION_get0_cipher LK_SSL_SESSION_get0_cipher +SSL_SESSION_get0_id_context LK_SSL_SESSION_get0_id_context +SSL_SESSION_get0_ocsp_response LK_SSL_SESSION_get0_ocsp_response +SSL_SESSION_get0_peer LK_SSL_SESSION_get0_peer +SSL_SESSION_get0_peer_certificates LK_SSL_SESSION_get0_peer_certificates +SSL_SESSION_get0_peer_sha256 LK_SSL_SESSION_get0_peer_sha256 +SSL_SESSION_get0_signed_cert_timestamp_list LK_SSL_SESSION_get0_signed_cert_timestamp_list +SSL_SESSION_get0_ticket LK_SSL_SESSION_get0_ticket +SSL_SESSION_get_ex_data LK_SSL_SESSION_get_ex_data +SSL_SESSION_get_ex_new_index LK_SSL_SESSION_get_ex_new_index +SSL_SESSION_get_id LK_SSL_SESSION_get_id +SSL_SESSION_get_master_key LK_SSL_SESSION_get_master_key +SSL_SESSION_get_protocol_version LK_SSL_SESSION_get_protocol_version +SSL_SESSION_get_ticket_lifetime_hint LK_SSL_SESSION_get_ticket_lifetime_hint +SSL_SESSION_get_time LK_SSL_SESSION_get_time +SSL_SESSION_get_timeout LK_SSL_SESSION_get_timeout +SSL_SESSION_get_version LK_SSL_SESSION_get_version +SSL_SESSION_has_peer_sha256 LK_SSL_SESSION_has_peer_sha256 +SSL_SESSION_has_ticket LK_SSL_SESSION_has_ticket +SSL_SESSION_is_resumable LK_SSL_SESSION_is_resumable +SSL_SESSION_new LK_SSL_SESSION_new +SSL_SESSION_set1_id LK_SSL_SESSION_set1_id +SSL_SESSION_set1_id_context LK_SSL_SESSION_set1_id_context +SSL_SESSION_set_ex_data LK_SSL_SESSION_set_ex_data +SSL_SESSION_set_protocol_version LK_SSL_SESSION_set_protocol_version +SSL_SESSION_set_ticket LK_SSL_SESSION_set_ticket +SSL_SESSION_set_time LK_SSL_SESSION_set_time +SSL_SESSION_set_timeout LK_SSL_SESSION_set_timeout +SSL_SESSION_should_be_single_use LK_SSL_SESSION_should_be_single_use +SSL_SESSION_to_bytes LK_SSL_SESSION_to_bytes +SSL_SESSION_to_bytes_for_ticket LK_SSL_SESSION_to_bytes_for_ticket +SSL_SESSION_up_ref LK_SSL_SESSION_up_ref +SSL_accept LK_SSL_accept +SSL_add0_chain_cert LK_SSL_add0_chain_cert +SSL_add1_chain_cert LK_SSL_add1_chain_cert +SSL_add1_credential LK_SSL_add1_credential +SSL_add_application_settings LK_SSL_add_application_settings +SSL_add_bio_cert_subjects_to_stack LK_SSL_add_bio_cert_subjects_to_stack +SSL_add_client_CA LK_SSL_add_client_CA +SSL_add_file_cert_subjects_to_stack LK_SSL_add_file_cert_subjects_to_stack +SSL_alert_desc_string LK_SSL_alert_desc_string +SSL_alert_desc_string_long LK_SSL_alert_desc_string_long +SSL_alert_from_verify_result LK_SSL_alert_from_verify_result +SSL_alert_type_string LK_SSL_alert_type_string +SSL_alert_type_string_long LK_SSL_alert_type_string_long +SSL_cache_hit LK_SSL_cache_hit +SSL_can_release_private_key LK_SSL_can_release_private_key +SSL_certs_clear LK_SSL_certs_clear +SSL_check_private_key LK_SSL_check_private_key +SSL_clear LK_SSL_clear +SSL_clear_chain_certs LK_SSL_clear_chain_certs +SSL_clear_mode LK_SSL_clear_mode +SSL_clear_options LK_SSL_clear_options +SSL_connect LK_SSL_connect +SSL_cutthrough_complete LK_SSL_cutthrough_complete +SSL_do_handshake LK_SSL_do_handshake +SSL_dup_CA_list LK_SSL_dup_CA_list +SSL_early_callback_ctx_extension_get LK_SSL_early_callback_ctx_extension_get +SSL_early_data_accepted LK_SSL_early_data_accepted +SSL_early_data_reason_string LK_SSL_early_data_reason_string +SSL_ech_accepted LK_SSL_ech_accepted +SSL_enable_ocsp_stapling LK_SSL_enable_ocsp_stapling +SSL_enable_signed_cert_timestamps LK_SSL_enable_signed_cert_timestamps +SSL_enable_tls_channel_id LK_SSL_enable_tls_channel_id +SSL_error_description LK_SSL_error_description +SSL_export_keying_material LK_SSL_export_keying_material +SSL_free LK_SSL_free +SSL_generate_key_block LK_SSL_generate_key_block +SSL_get0_alpn_selected LK_SSL_get0_alpn_selected +SSL_get0_certificate_types LK_SSL_get0_certificate_types +SSL_get0_chain LK_SSL_get0_chain +SSL_get0_chain_certs LK_SSL_get0_chain_certs +SSL_get0_ech_name_override LK_SSL_get0_ech_name_override +SSL_get0_ech_retry_configs LK_SSL_get0_ech_retry_configs +SSL_get0_next_proto_negotiated LK_SSL_get0_next_proto_negotiated +SSL_get0_ocsp_response LK_SSL_get0_ocsp_response +SSL_get0_param LK_SSL_get0_param +SSL_get0_peer_application_settings LK_SSL_get0_peer_application_settings +SSL_get0_peer_certificates LK_SSL_get0_peer_certificates +SSL_get0_peer_delegation_algorithms LK_SSL_get0_peer_delegation_algorithms +SSL_get0_peer_verify_algorithms LK_SSL_get0_peer_verify_algorithms +SSL_get0_selected_credential LK_SSL_get0_selected_credential +SSL_get0_server_requested_CAs LK_SSL_get0_server_requested_CAs +SSL_get0_session_id_context LK_SSL_get0_session_id_context +SSL_get0_signed_cert_timestamp_list LK_SSL_get0_signed_cert_timestamp_list +SSL_get1_session LK_SSL_get1_session +SSL_get_SSL_CTX LK_SSL_get_SSL_CTX +SSL_get_all_cipher_names LK_SSL_get_all_cipher_names +SSL_get_all_curve_names LK_SSL_get_all_curve_names +SSL_get_all_group_names LK_SSL_get_all_group_names +SSL_get_all_signature_algorithm_names LK_SSL_get_all_signature_algorithm_names +SSL_get_all_standard_cipher_names LK_SSL_get_all_standard_cipher_names +SSL_get_all_version_names LK_SSL_get_all_version_names +SSL_get_certificate LK_SSL_get_certificate +SSL_get_cipher_by_value LK_SSL_get_cipher_by_value +SSL_get_cipher_list LK_SSL_get_cipher_list +SSL_get_ciphers LK_SSL_get_ciphers +SSL_get_client_CA_list LK_SSL_get_client_CA_list +SSL_get_client_random LK_SSL_get_client_random +SSL_get_current_cipher LK_SSL_get_current_cipher +SSL_get_current_compression LK_SSL_get_current_compression +SSL_get_current_expansion LK_SSL_get_current_expansion +SSL_get_curve_id LK_SSL_get_curve_id +SSL_get_curve_name LK_SSL_get_curve_name +SSL_get_default_timeout LK_SSL_get_default_timeout +SSL_get_early_data_reason LK_SSL_get_early_data_reason +SSL_get_error LK_SSL_get_error +SSL_get_ex_data LK_SSL_get_ex_data +SSL_get_ex_data_X509_STORE_CTX_idx LK_SSL_get_ex_data_X509_STORE_CTX_idx +SSL_get_ex_new_index LK_SSL_get_ex_new_index +SSL_get_extms_support LK_SSL_get_extms_support +SSL_get_fd LK_SSL_get_fd +SSL_get_finished LK_SSL_get_finished +SSL_get_group_id LK_SSL_get_group_id +SSL_get_group_name LK_SSL_get_group_name +SSL_get_info_callback LK_SSL_get_info_callback +SSL_get_ivs LK_SSL_get_ivs +SSL_get_key_block_len LK_SSL_get_key_block_len +SSL_get_max_cert_list LK_SSL_get_max_cert_list +SSL_get_max_proto_version LK_SSL_get_max_proto_version +SSL_get_min_proto_version LK_SSL_get_min_proto_version +SSL_get_mode LK_SSL_get_mode +SSL_get_negotiated_group LK_SSL_get_negotiated_group +SSL_get_options LK_SSL_get_options +SSL_get_peer_cert_chain LK_SSL_get_peer_cert_chain +SSL_get_peer_certificate LK_SSL_get_peer_certificate +SSL_get_peer_finished LK_SSL_get_peer_finished +SSL_get_peer_full_cert_chain LK_SSL_get_peer_full_cert_chain +SSL_get_peer_quic_transport_params LK_SSL_get_peer_quic_transport_params +SSL_get_peer_signature_algorithm LK_SSL_get_peer_signature_algorithm +SSL_get_pending_cipher LK_SSL_get_pending_cipher +SSL_get_privatekey LK_SSL_get_privatekey +SSL_get_psk_identity LK_SSL_get_psk_identity +SSL_get_psk_identity_hint LK_SSL_get_psk_identity_hint +SSL_get_quiet_shutdown LK_SSL_get_quiet_shutdown +SSL_get_rbio LK_SSL_get_rbio +SSL_get_read_ahead LK_SSL_get_read_ahead +SSL_get_read_sequence LK_SSL_get_read_sequence +SSL_get_rfd LK_SSL_get_rfd +SSL_get_secure_renegotiation_support LK_SSL_get_secure_renegotiation_support +SSL_get_selected_srtp_profile LK_SSL_get_selected_srtp_profile +SSL_get_server_random LK_SSL_get_server_random +SSL_get_server_tmp_key LK_SSL_get_server_tmp_key +SSL_get_servername LK_SSL_get_servername +SSL_get_servername_type LK_SSL_get_servername_type +SSL_get_session LK_SSL_get_session +SSL_get_shared_ciphers LK_SSL_get_shared_ciphers +SSL_get_shared_sigalgs LK_SSL_get_shared_sigalgs +SSL_get_shutdown LK_SSL_get_shutdown +SSL_get_signature_algorithm_digest LK_SSL_get_signature_algorithm_digest +SSL_get_signature_algorithm_key_type LK_SSL_get_signature_algorithm_key_type +SSL_get_signature_algorithm_name LK_SSL_get_signature_algorithm_name +SSL_get_srtp_profiles LK_SSL_get_srtp_profiles +SSL_get_ticket_age_skew LK_SSL_get_ticket_age_skew +SSL_get_tls_channel_id LK_SSL_get_tls_channel_id +SSL_get_tls_unique LK_SSL_get_tls_unique +SSL_get_tlsext_status_ocsp_resp LK_SSL_get_tlsext_status_ocsp_resp +SSL_get_tlsext_status_type LK_SSL_get_tlsext_status_type +SSL_get_verify_callback LK_SSL_get_verify_callback +SSL_get_verify_depth LK_SSL_get_verify_depth +SSL_get_verify_mode LK_SSL_get_verify_mode +SSL_get_verify_result LK_SSL_get_verify_result +SSL_get_version LK_SSL_get_version +SSL_get_wbio LK_SSL_get_wbio +SSL_get_wfd LK_SSL_get_wfd +SSL_get_write_sequence LK_SSL_get_write_sequence +SSL_has_application_settings LK_SSL_has_application_settings +SSL_has_pending LK_SSL_has_pending +SSL_in_early_data LK_SSL_in_early_data +SSL_in_false_start LK_SSL_in_false_start +SSL_in_init LK_SSL_in_init +SSL_is_dtls LK_SSL_is_dtls +SSL_is_init_finished LK_SSL_is_init_finished +SSL_is_server LK_SSL_is_server +SSL_is_signature_algorithm_rsa_pss LK_SSL_is_signature_algorithm_rsa_pss +SSL_key_update LK_SSL_key_update +SSL_library_init LK_SSL_library_init +SSL_load_client_CA_file LK_SSL_load_client_CA_file +SSL_load_error_strings LK_SSL_load_error_strings +SSL_magic_pending_session_ptr LK_SSL_magic_pending_session_ptr +SSL_marshal_ech_config LK_SSL_marshal_ech_config +SSL_max_seal_overhead LK_SSL_max_seal_overhead +SSL_need_tmp_RSA LK_SSL_need_tmp_RSA +SSL_new LK_SSL_new +SSL_num_renegotiations LK_SSL_num_renegotiations +SSL_peek LK_SSL_peek +SSL_pending LK_SSL_pending +SSL_process_quic_post_handshake LK_SSL_process_quic_post_handshake +SSL_process_tls13_new_session_ticket LK_SSL_process_tls13_new_session_ticket +SSL_provide_quic_data LK_SSL_provide_quic_data +SSL_quic_max_handshake_flight_len LK_SSL_quic_max_handshake_flight_len +SSL_quic_read_level LK_SSL_quic_read_level +SSL_quic_write_level LK_SSL_quic_write_level +SSL_read LK_SSL_read +SSL_renegotiate LK_SSL_renegotiate +SSL_renegotiate_pending LK_SSL_renegotiate_pending +SSL_request_handshake_hints LK_SSL_request_handshake_hints +SSL_reset_early_data_reject LK_SSL_reset_early_data_reject +SSL_select_next_proto LK_SSL_select_next_proto +SSL_send_fatal_alert LK_SSL_send_fatal_alert +SSL_serialize_capabilities LK_SSL_serialize_capabilities +SSL_serialize_handshake_hints LK_SSL_serialize_handshake_hints +SSL_session_reused LK_SSL_session_reused +SSL_set0_chain LK_SSL_set0_chain +SSL_set0_client_CAs LK_SSL_set0_client_CAs +SSL_set0_rbio LK_SSL_set0_rbio +SSL_set0_verify_cert_store LK_SSL_set0_verify_cert_store +SSL_set0_wbio LK_SSL_set0_wbio +SSL_set1_chain LK_SSL_set1_chain +SSL_set1_curves LK_SSL_set1_curves +SSL_set1_curves_list LK_SSL_set1_curves_list +SSL_set1_ech_config_list LK_SSL_set1_ech_config_list +SSL_set1_group_ids LK_SSL_set1_group_ids +SSL_set1_groups LK_SSL_set1_groups +SSL_set1_groups_list LK_SSL_set1_groups_list +SSL_set1_host LK_SSL_set1_host +SSL_set1_param LK_SSL_set1_param +SSL_set1_sigalgs LK_SSL_set1_sigalgs +SSL_set1_sigalgs_list LK_SSL_set1_sigalgs_list +SSL_set1_tls_channel_id LK_SSL_set1_tls_channel_id +SSL_set1_verify_cert_store LK_SSL_set1_verify_cert_store +SSL_set_SSL_CTX LK_SSL_set_SSL_CTX +SSL_set_accept_state LK_SSL_set_accept_state +SSL_set_alpn_protos LK_SSL_set_alpn_protos +SSL_set_alps_use_new_codepoint LK_SSL_set_alps_use_new_codepoint +SSL_set_bio LK_SSL_set_bio +SSL_set_cert_cb LK_SSL_set_cert_cb +SSL_set_chain_and_key LK_SSL_set_chain_and_key +SSL_set_check_client_certificate_type LK_SSL_set_check_client_certificate_type +SSL_set_check_ecdsa_curve LK_SSL_set_check_ecdsa_curve +SSL_set_cipher_list LK_SSL_set_cipher_list +SSL_set_client_CA_list LK_SSL_set_client_CA_list +SSL_set_compliance_policy LK_SSL_set_compliance_policy +SSL_set_connect_state LK_SSL_set_connect_state +SSL_set_custom_verify LK_SSL_set_custom_verify +SSL_set_early_data_enabled LK_SSL_set_early_data_enabled +SSL_set_enable_ech_grease LK_SSL_set_enable_ech_grease +SSL_set_enforce_rsa_key_usage LK_SSL_set_enforce_rsa_key_usage +SSL_set_ex_data LK_SSL_set_ex_data +SSL_set_fd LK_SSL_set_fd +SSL_set_handshake_hints LK_SSL_set_handshake_hints +SSL_set_hostflags LK_SSL_set_hostflags +SSL_set_info_callback LK_SSL_set_info_callback +SSL_set_jdk11_workaround LK_SSL_set_jdk11_workaround +SSL_set_max_cert_list LK_SSL_set_max_cert_list +SSL_set_max_proto_version LK_SSL_set_max_proto_version +SSL_set_max_send_fragment LK_SSL_set_max_send_fragment +SSL_set_min_proto_version LK_SSL_set_min_proto_version +SSL_set_mode LK_SSL_set_mode +SSL_set_msg_callback LK_SSL_set_msg_callback +SSL_set_msg_callback_arg LK_SSL_set_msg_callback_arg +SSL_set_mtu LK_SSL_set_mtu +SSL_set_ocsp_response LK_SSL_set_ocsp_response +SSL_set_options LK_SSL_set_options +SSL_set_permute_extensions LK_SSL_set_permute_extensions +SSL_set_private_key_method LK_SSL_set_private_key_method +SSL_set_psk_client_callback LK_SSL_set_psk_client_callback +SSL_set_psk_server_callback LK_SSL_set_psk_server_callback +SSL_set_purpose LK_SSL_set_purpose +SSL_set_quic_early_data_context LK_SSL_set_quic_early_data_context +SSL_set_quic_method LK_SSL_set_quic_method +SSL_set_quic_transport_params LK_SSL_set_quic_transport_params +SSL_set_quic_use_legacy_codepoint LK_SSL_set_quic_use_legacy_codepoint +SSL_set_quiet_shutdown LK_SSL_set_quiet_shutdown +SSL_set_read_ahead LK_SSL_set_read_ahead +SSL_set_renegotiate_mode LK_SSL_set_renegotiate_mode +SSL_set_retain_only_sha256_of_client_certs LK_SSL_set_retain_only_sha256_of_client_certs +SSL_set_rfd LK_SSL_set_rfd +SSL_set_session LK_SSL_set_session +SSL_set_session_id_context LK_SSL_set_session_id_context +SSL_set_shed_handshake_config LK_SSL_set_shed_handshake_config +SSL_set_shutdown LK_SSL_set_shutdown +SSL_set_signed_cert_timestamp_list LK_SSL_set_signed_cert_timestamp_list +SSL_set_signing_algorithm_prefs LK_SSL_set_signing_algorithm_prefs +SSL_set_srtp_profiles LK_SSL_set_srtp_profiles +SSL_set_state LK_SSL_set_state +SSL_set_strict_cipher_list LK_SSL_set_strict_cipher_list +SSL_set_tls_channel_id_enabled LK_SSL_set_tls_channel_id_enabled +SSL_set_tlsext_host_name LK_SSL_set_tlsext_host_name +SSL_set_tlsext_status_ocsp_resp LK_SSL_set_tlsext_status_ocsp_resp +SSL_set_tlsext_status_type LK_SSL_set_tlsext_status_type +SSL_set_tlsext_use_srtp LK_SSL_set_tlsext_use_srtp +SSL_set_tmp_dh LK_SSL_set_tmp_dh +SSL_set_tmp_dh_callback LK_SSL_set_tmp_dh_callback +SSL_set_tmp_ecdh LK_SSL_set_tmp_ecdh +SSL_set_tmp_rsa LK_SSL_set_tmp_rsa +SSL_set_tmp_rsa_callback LK_SSL_set_tmp_rsa_callback +SSL_set_trust LK_SSL_set_trust +SSL_set_verify LK_SSL_set_verify +SSL_set_verify_algorithm_prefs LK_SSL_set_verify_algorithm_prefs +SSL_set_verify_depth LK_SSL_set_verify_depth +SSL_set_wfd LK_SSL_set_wfd +SSL_shutdown LK_SSL_shutdown +SSL_state LK_SSL_state +SSL_state_string LK_SSL_state_string +SSL_state_string_long LK_SSL_state_string_long +SSL_total_renegotiations LK_SSL_total_renegotiations +SSL_use_PrivateKey LK_SSL_use_PrivateKey +SSL_use_PrivateKey_ASN1 LK_SSL_use_PrivateKey_ASN1 +SSL_use_PrivateKey_file LK_SSL_use_PrivateKey_file +SSL_use_RSAPrivateKey LK_SSL_use_RSAPrivateKey +SSL_use_RSAPrivateKey_ASN1 LK_SSL_use_RSAPrivateKey_ASN1 +SSL_use_RSAPrivateKey_file LK_SSL_use_RSAPrivateKey_file +SSL_use_certificate LK_SSL_use_certificate +SSL_use_certificate_ASN1 LK_SSL_use_certificate_ASN1 +SSL_use_certificate_file LK_SSL_use_certificate_file +SSL_use_psk_identity_hint LK_SSL_use_psk_identity_hint +SSL_used_hello_retry_request LK_SSL_used_hello_retry_request +SSL_version LK_SSL_version +SSL_want LK_SSL_want +SSL_was_key_usage_invalid LK_SSL_was_key_usage_invalid +SSL_write LK_SSL_write +SSLv23_client_method LK_SSLv23_client_method +SSLv23_method LK_SSLv23_method +SSLv23_server_method LK_SSLv23_server_method +TLS_client_method LK_TLS_client_method +TLS_method LK_TLS_method +TLS_server_method LK_TLS_server_method +TLS_with_buffers_method LK_TLS_with_buffers_method +TLSv1_1_client_method LK_TLSv1_1_client_method +TLSv1_1_method LK_TLSv1_1_method +TLSv1_1_server_method LK_TLSv1_1_server_method +TLSv1_2_client_method LK_TLSv1_2_client_method +TLSv1_2_method LK_TLSv1_2_method +TLSv1_2_server_method LK_TLSv1_2_server_method +TLSv1_client_method LK_TLSv1_client_method +TLSv1_method LK_TLSv1_method +TLSv1_server_method LK_TLSv1_server_method +d2i_SSL_SESSION LK_d2i_SSL_SESSION +d2i_SSL_SESSION_bio LK_d2i_SSL_SESSION_bio +i2d_SSL_SESSION LK_i2d_SSL_SESSION +i2d_SSL_SESSION_bio LK_i2d_SSL_SESSION_bio +sk_CRYPTO_BUFFER_call_copy_func LK_sk_CRYPTO_BUFFER_call_copy_func +sk_CRYPTO_BUFFER_call_free_func LK_sk_CRYPTO_BUFFER_call_free_func +sk_CRYPTO_BUFFER_deep_copy LK_sk_CRYPTO_BUFFER_deep_copy +sk_CRYPTO_BUFFER_new_null LK_sk_CRYPTO_BUFFER_new_null +sk_CRYPTO_BUFFER_num LK_sk_CRYPTO_BUFFER_num +sk_CRYPTO_BUFFER_pop LK_sk_CRYPTO_BUFFER_pop +sk_CRYPTO_BUFFER_push LK_sk_CRYPTO_BUFFER_push +sk_CRYPTO_BUFFER_set LK_sk_CRYPTO_BUFFER_set +sk_CRYPTO_BUFFER_value LK_sk_CRYPTO_BUFFER_value +sk_SRTP_PROTECTION_PROFILE_new_null LK_sk_SRTP_PROTECTION_PROFILE_new_null +sk_SRTP_PROTECTION_PROFILE_num LK_sk_SRTP_PROTECTION_PROFILE_num +sk_SRTP_PROTECTION_PROFILE_push LK_sk_SRTP_PROTECTION_PROFILE_push +sk_SSL_CIPHER_call_cmp_func LK_sk_SSL_CIPHER_call_cmp_func +sk_SSL_CIPHER_delete LK_sk_SSL_CIPHER_delete +sk_SSL_CIPHER_dup LK_sk_SSL_CIPHER_dup +sk_SSL_CIPHER_find LK_sk_SSL_CIPHER_find +sk_SSL_CIPHER_new_null LK_sk_SSL_CIPHER_new_null +sk_SSL_CIPHER_num LK_sk_SSL_CIPHER_num +sk_SSL_CIPHER_push LK_sk_SSL_CIPHER_push +sk_SSL_CIPHER_value LK_sk_SSL_CIPHER_value +sk_X509_NAME_call_cmp_func LK_sk_X509_NAME_call_cmp_func +sk_X509_NAME_call_copy_func LK_sk_X509_NAME_call_copy_func +sk_X509_NAME_call_free_func LK_sk_X509_NAME_call_free_func +sk_X509_NAME_deep_copy LK_sk_X509_NAME_deep_copy +sk_X509_NAME_find LK_sk_X509_NAME_find +sk_X509_NAME_new LK_sk_X509_NAME_new +sk_X509_NAME_new_null LK_sk_X509_NAME_new_null +sk_X509_NAME_num LK_sk_X509_NAME_num +sk_X509_NAME_pop_free LK_sk_X509_NAME_pop_free +sk_X509_NAME_set LK_sk_X509_NAME_set +sk_X509_NAME_set_cmp_func LK_sk_X509_NAME_set_cmp_func +sk_X509_NAME_sort LK_sk_X509_NAME_sort +sk_X509_NAME_value LK_sk_X509_NAME_value +sk_X509_call_free_func LK_sk_X509_call_free_func +sk_X509_new_null LK_sk_X509_new_null +sk_X509_num LK_sk_X509_num +sk_X509_pop_free LK_sk_X509_pop_free +sk_X509_shift LK_sk_X509_shift +sk_X509_value LK_sk_X509_value +MD4 LK_MD4 +MD4_Final LK_MD4_Final +MD4_Init LK_MD4_Init +MD4_Transform LK_MD4_Transform +MD4_Update LK_MD4_Update +MD5 LK_MD5 +MD5_Final LK_MD5_Final +MD5_Init LK_MD5_Init +MD5_Transform LK_MD5_Transform +MD5_Update LK_MD5_Update +METHOD_ref LK_METHOD_ref +METHOD_unref LK_METHOD_unref +NAME_CONSTRAINTS_check LK_NAME_CONSTRAINTS_check +NAME_CONSTRAINTS_free LK_NAME_CONSTRAINTS_free +NAME_CONSTRAINTS_it LK_NAME_CONSTRAINTS_it +NAME_CONSTRAINTS_new LK_NAME_CONSTRAINTS_new +NCONF_free LK_NCONF_free +NCONF_get_section LK_NCONF_get_section +NCONF_get_string LK_NCONF_get_string +NCONF_load LK_NCONF_load +NCONF_load_bio LK_NCONF_load_bio +NCONF_new LK_NCONF_new +NETSCAPE_SPKAC_free LK_NETSCAPE_SPKAC_free +NETSCAPE_SPKAC_it LK_NETSCAPE_SPKAC_it +NETSCAPE_SPKAC_new LK_NETSCAPE_SPKAC_new +NETSCAPE_SPKI_b64_decode LK_NETSCAPE_SPKI_b64_decode +NETSCAPE_SPKI_b64_encode LK_NETSCAPE_SPKI_b64_encode +NETSCAPE_SPKI_free LK_NETSCAPE_SPKI_free +NETSCAPE_SPKI_get_pubkey LK_NETSCAPE_SPKI_get_pubkey +NETSCAPE_SPKI_it LK_NETSCAPE_SPKI_it +NETSCAPE_SPKI_new LK_NETSCAPE_SPKI_new +NETSCAPE_SPKI_set_pubkey LK_NETSCAPE_SPKI_set_pubkey +NETSCAPE_SPKI_sign LK_NETSCAPE_SPKI_sign +NETSCAPE_SPKI_verify LK_NETSCAPE_SPKI_verify +NOTICEREF_free LK_NOTICEREF_free +NOTICEREF_it LK_NOTICEREF_it +NOTICEREF_new LK_NOTICEREF_new +OBJ_NAME_do_all LK_OBJ_NAME_do_all +OBJ_NAME_do_all_sorted LK_OBJ_NAME_do_all_sorted +OBJ_cbs2nid LK_OBJ_cbs2nid +OBJ_cleanup LK_OBJ_cleanup +OBJ_cmp LK_OBJ_cmp +OBJ_create LK_OBJ_create +OBJ_dup LK_OBJ_dup +OBJ_find_sigid_algs LK_OBJ_find_sigid_algs +OBJ_find_sigid_by_algs LK_OBJ_find_sigid_by_algs +OBJ_get0_data LK_OBJ_get0_data +OBJ_get_undef LK_OBJ_get_undef +OBJ_length LK_OBJ_length +OBJ_ln2nid LK_OBJ_ln2nid +OBJ_nid2cbb LK_OBJ_nid2cbb +OBJ_nid2ln LK_OBJ_nid2ln +OBJ_nid2obj LK_OBJ_nid2obj +OBJ_nid2sn LK_OBJ_nid2sn +OBJ_obj2nid LK_OBJ_obj2nid +OBJ_obj2txt LK_OBJ_obj2txt +OBJ_sn2nid LK_OBJ_sn2nid +OBJ_txt2nid LK_OBJ_txt2nid +OBJ_txt2obj LK_OBJ_txt2obj +OPENSSL_add_all_algorithms_conf LK_OPENSSL_add_all_algorithms_conf +OPENSSL_asprintf LK_OPENSSL_asprintf +OPENSSL_calloc LK_OPENSSL_calloc +OPENSSL_cleanse LK_OPENSSL_cleanse +OPENSSL_cleanup LK_OPENSSL_cleanup +OPENSSL_clear_free LK_OPENSSL_clear_free +OPENSSL_config LK_OPENSSL_config +OPENSSL_cpuid_setup LK_OPENSSL_cpuid_setup +OPENSSL_free LK_OPENSSL_free +OPENSSL_fromxdigit LK_OPENSSL_fromxdigit +OPENSSL_get_ia32cap LK_OPENSSL_get_ia32cap +OPENSSL_gmtime LK_OPENSSL_gmtime +OPENSSL_gmtime_adj LK_OPENSSL_gmtime_adj +OPENSSL_gmtime_diff LK_OPENSSL_gmtime_diff +OPENSSL_hash32 LK_OPENSSL_hash32 +OPENSSL_ia32cap_P LK_OPENSSL_ia32cap_P +OPENSSL_init_crypto LK_OPENSSL_init_crypto +OPENSSL_isalnum LK_OPENSSL_isalnum +OPENSSL_isalpha LK_OPENSSL_isalpha +OPENSSL_isdigit LK_OPENSSL_isdigit +OPENSSL_isspace LK_OPENSSL_isspace +OPENSSL_isxdigit LK_OPENSSL_isxdigit +OPENSSL_lh_delete LK_OPENSSL_lh_delete +OPENSSL_lh_doall_arg LK_OPENSSL_lh_doall_arg +OPENSSL_lh_free LK_OPENSSL_lh_free +OPENSSL_lh_insert LK_OPENSSL_lh_insert +OPENSSL_lh_new LK_OPENSSL_lh_new +OPENSSL_lh_num_items LK_OPENSSL_lh_num_items +OPENSSL_lh_retrieve LK_OPENSSL_lh_retrieve +OPENSSL_lh_retrieve_key LK_OPENSSL_lh_retrieve_key +OPENSSL_load_builtin_modules LK_OPENSSL_load_builtin_modules +OPENSSL_malloc LK_OPENSSL_malloc +OPENSSL_malloc_init LK_OPENSSL_malloc_init +OPENSSL_memdup LK_OPENSSL_memdup +OPENSSL_no_config LK_OPENSSL_no_config +OPENSSL_posix_to_tm LK_OPENSSL_posix_to_tm +OPENSSL_realloc LK_OPENSSL_realloc +OPENSSL_secure_clear_free LK_OPENSSL_secure_clear_free +OPENSSL_secure_malloc LK_OPENSSL_secure_malloc +OPENSSL_sk_deep_copy LK_OPENSSL_sk_deep_copy +OPENSSL_sk_delete LK_OPENSSL_sk_delete +OPENSSL_sk_delete_if LK_OPENSSL_sk_delete_if +OPENSSL_sk_delete_ptr LK_OPENSSL_sk_delete_ptr +OPENSSL_sk_dup LK_OPENSSL_sk_dup +OPENSSL_sk_find LK_OPENSSL_sk_find +OPENSSL_sk_free LK_OPENSSL_sk_free +OPENSSL_sk_insert LK_OPENSSL_sk_insert +OPENSSL_sk_is_sorted LK_OPENSSL_sk_is_sorted +OPENSSL_sk_new LK_OPENSSL_sk_new +OPENSSL_sk_new_null LK_OPENSSL_sk_new_null +OPENSSL_sk_num LK_OPENSSL_sk_num +OPENSSL_sk_pop LK_OPENSSL_sk_pop +OPENSSL_sk_pop_free_ex LK_OPENSSL_sk_pop_free_ex +OPENSSL_sk_push LK_OPENSSL_sk_push +OPENSSL_sk_set LK_OPENSSL_sk_set +OPENSSL_sk_set_cmp_func LK_OPENSSL_sk_set_cmp_func +OPENSSL_sk_shift LK_OPENSSL_sk_shift +OPENSSL_sk_sort LK_OPENSSL_sk_sort +OPENSSL_sk_value LK_OPENSSL_sk_value +OPENSSL_sk_zero LK_OPENSSL_sk_zero +OPENSSL_strcasecmp LK_OPENSSL_strcasecmp +OPENSSL_strdup LK_OPENSSL_strdup +OPENSSL_strhash LK_OPENSSL_strhash +OPENSSL_strlcat LK_OPENSSL_strlcat +OPENSSL_strlcpy LK_OPENSSL_strlcpy +OPENSSL_strncasecmp LK_OPENSSL_strncasecmp +OPENSSL_strndup LK_OPENSSL_strndup +OPENSSL_strnlen LK_OPENSSL_strnlen +OPENSSL_timegm LK_OPENSSL_timegm +OPENSSL_tm_to_posix LK_OPENSSL_tm_to_posix +OPENSSL_tolower LK_OPENSSL_tolower +OPENSSL_vasprintf LK_OPENSSL_vasprintf +OPENSSL_vasprintf_internal LK_OPENSSL_vasprintf_internal +OPENSSL_zalloc LK_OPENSSL_zalloc +OTHERNAME_free LK_OTHERNAME_free +OTHERNAME_it LK_OTHERNAME_it +OTHERNAME_new LK_OTHERNAME_new +OpenSSL_add_all_algorithms LK_OpenSSL_add_all_algorithms +OpenSSL_add_all_ciphers LK_OpenSSL_add_all_ciphers +OpenSSL_add_all_digests LK_OpenSSL_add_all_digests +OpenSSL_version LK_OpenSSL_version +OpenSSL_version_num LK_OpenSSL_version_num +PEM_ASN1_read LK_PEM_ASN1_read +PEM_ASN1_read_bio LK_PEM_ASN1_read_bio +PEM_ASN1_write LK_PEM_ASN1_write +PEM_ASN1_write_bio LK_PEM_ASN1_write_bio +PEM_X509_INFO_read LK_PEM_X509_INFO_read +PEM_X509_INFO_read_bio LK_PEM_X509_INFO_read_bio +PEM_bytes_read_bio LK_PEM_bytes_read_bio +PEM_def_callback LK_PEM_def_callback +PEM_do_header LK_PEM_do_header +PEM_get_EVP_CIPHER_INFO LK_PEM_get_EVP_CIPHER_INFO +PEM_read LK_PEM_read +PEM_read_DHparams LK_PEM_read_DHparams +PEM_read_DSAPrivateKey LK_PEM_read_DSAPrivateKey +PEM_read_DSA_PUBKEY LK_PEM_read_DSA_PUBKEY +PEM_read_DSAparams LK_PEM_read_DSAparams +PEM_read_ECPrivateKey LK_PEM_read_ECPrivateKey +PEM_read_EC_PUBKEY LK_PEM_read_EC_PUBKEY +PEM_read_PKCS7 LK_PEM_read_PKCS7 +PEM_read_PKCS8 LK_PEM_read_PKCS8 +PEM_read_PKCS8_PRIV_KEY_INFO LK_PEM_read_PKCS8_PRIV_KEY_INFO +PEM_read_PUBKEY LK_PEM_read_PUBKEY +PEM_read_PrivateKey LK_PEM_read_PrivateKey +PEM_read_RSAPrivateKey LK_PEM_read_RSAPrivateKey +PEM_read_RSAPublicKey LK_PEM_read_RSAPublicKey +PEM_read_RSA_PUBKEY LK_PEM_read_RSA_PUBKEY +PEM_read_X509 LK_PEM_read_X509 +PEM_read_X509_AUX LK_PEM_read_X509_AUX +PEM_read_X509_CRL LK_PEM_read_X509_CRL +PEM_read_X509_REQ LK_PEM_read_X509_REQ +PEM_read_bio LK_PEM_read_bio +PEM_read_bio_DHparams LK_PEM_read_bio_DHparams +PEM_read_bio_DSAPrivateKey LK_PEM_read_bio_DSAPrivateKey +PEM_read_bio_DSA_PUBKEY LK_PEM_read_bio_DSA_PUBKEY +PEM_read_bio_DSAparams LK_PEM_read_bio_DSAparams +PEM_read_bio_ECPrivateKey LK_PEM_read_bio_ECPrivateKey +PEM_read_bio_EC_PUBKEY LK_PEM_read_bio_EC_PUBKEY +PEM_read_bio_PKCS7 LK_PEM_read_bio_PKCS7 +PEM_read_bio_PKCS8 LK_PEM_read_bio_PKCS8 +PEM_read_bio_PKCS8_PRIV_KEY_INFO LK_PEM_read_bio_PKCS8_PRIV_KEY_INFO +PEM_read_bio_PUBKEY LK_PEM_read_bio_PUBKEY +PEM_read_bio_PrivateKey LK_PEM_read_bio_PrivateKey +PEM_read_bio_RSAPrivateKey LK_PEM_read_bio_RSAPrivateKey +PEM_read_bio_RSAPublicKey LK_PEM_read_bio_RSAPublicKey +PEM_read_bio_RSA_PUBKEY LK_PEM_read_bio_RSA_PUBKEY +PEM_read_bio_X509 LK_PEM_read_bio_X509 +PEM_read_bio_X509_AUX LK_PEM_read_bio_X509_AUX +PEM_read_bio_X509_CRL LK_PEM_read_bio_X509_CRL +PEM_read_bio_X509_REQ LK_PEM_read_bio_X509_REQ +PEM_write LK_PEM_write +PEM_write_DHparams LK_PEM_write_DHparams +PEM_write_DSAPrivateKey LK_PEM_write_DSAPrivateKey +PEM_write_DSA_PUBKEY LK_PEM_write_DSA_PUBKEY +PEM_write_DSAparams LK_PEM_write_DSAparams +PEM_write_ECPrivateKey LK_PEM_write_ECPrivateKey +PEM_write_EC_PUBKEY LK_PEM_write_EC_PUBKEY +PEM_write_PKCS7 LK_PEM_write_PKCS7 +PEM_write_PKCS8 LK_PEM_write_PKCS8 +PEM_write_PKCS8PrivateKey LK_PEM_write_PKCS8PrivateKey +PEM_write_PKCS8PrivateKey_nid LK_PEM_write_PKCS8PrivateKey_nid +PEM_write_PKCS8_PRIV_KEY_INFO LK_PEM_write_PKCS8_PRIV_KEY_INFO +PEM_write_PUBKEY LK_PEM_write_PUBKEY +PEM_write_PrivateKey LK_PEM_write_PrivateKey +PEM_write_RSAPrivateKey LK_PEM_write_RSAPrivateKey +PEM_write_RSAPublicKey LK_PEM_write_RSAPublicKey +PEM_write_RSA_PUBKEY LK_PEM_write_RSA_PUBKEY +PEM_write_X509 LK_PEM_write_X509 +PEM_write_X509_AUX LK_PEM_write_X509_AUX +PEM_write_X509_CRL LK_PEM_write_X509_CRL +PEM_write_X509_REQ LK_PEM_write_X509_REQ +PEM_write_X509_REQ_NEW LK_PEM_write_X509_REQ_NEW +PEM_write_bio LK_PEM_write_bio +PEM_write_bio_DHparams LK_PEM_write_bio_DHparams +PEM_write_bio_DSAPrivateKey LK_PEM_write_bio_DSAPrivateKey +PEM_write_bio_DSA_PUBKEY LK_PEM_write_bio_DSA_PUBKEY +PEM_write_bio_DSAparams LK_PEM_write_bio_DSAparams +PEM_write_bio_ECPrivateKey LK_PEM_write_bio_ECPrivateKey +PEM_write_bio_EC_PUBKEY LK_PEM_write_bio_EC_PUBKEY +PEM_write_bio_PKCS7 LK_PEM_write_bio_PKCS7 +PEM_write_bio_PKCS8 LK_PEM_write_bio_PKCS8 +PEM_write_bio_PKCS8PrivateKey LK_PEM_write_bio_PKCS8PrivateKey +PEM_write_bio_PKCS8PrivateKey_nid LK_PEM_write_bio_PKCS8PrivateKey_nid +PEM_write_bio_PKCS8_PRIV_KEY_INFO LK_PEM_write_bio_PKCS8_PRIV_KEY_INFO +PEM_write_bio_PUBKEY LK_PEM_write_bio_PUBKEY +PEM_write_bio_PrivateKey LK_PEM_write_bio_PrivateKey +PEM_write_bio_RSAPrivateKey LK_PEM_write_bio_RSAPrivateKey +PEM_write_bio_RSAPublicKey LK_PEM_write_bio_RSAPublicKey +PEM_write_bio_RSA_PUBKEY LK_PEM_write_bio_RSA_PUBKEY +PEM_write_bio_X509 LK_PEM_write_bio_X509 +PEM_write_bio_X509_AUX LK_PEM_write_bio_X509_AUX +PEM_write_bio_X509_CRL LK_PEM_write_bio_X509_CRL +PEM_write_bio_X509_REQ LK_PEM_write_bio_X509_REQ +PEM_write_bio_X509_REQ_NEW LK_PEM_write_bio_X509_REQ_NEW +PKCS12_PBE_add LK_PKCS12_PBE_add +PKCS12_create LK_PKCS12_create +PKCS12_free LK_PKCS12_free +PKCS12_get_key_and_certs LK_PKCS12_get_key_and_certs +PKCS12_parse LK_PKCS12_parse +PKCS12_verify_mac LK_PKCS12_verify_mac +PKCS1_MGF1 LK_PKCS1_MGF1 +PKCS5_PBKDF2_HMAC LK_PKCS5_PBKDF2_HMAC +PKCS5_PBKDF2_HMAC_SHA1 LK_PKCS5_PBKDF2_HMAC_SHA1 +PKCS5_pbe2_decrypt_init LK_PKCS5_pbe2_decrypt_init +PKCS5_pbe2_encrypt_init LK_PKCS5_pbe2_encrypt_init +PKCS7_bundle_CRLs LK_PKCS7_bundle_CRLs +PKCS7_bundle_certificates LK_PKCS7_bundle_certificates +PKCS7_bundle_raw_certificates LK_PKCS7_bundle_raw_certificates +PKCS7_free LK_PKCS7_free +PKCS7_get_CRLs LK_PKCS7_get_CRLs +PKCS7_get_PEM_CRLs LK_PKCS7_get_PEM_CRLs +PKCS7_get_PEM_certificates LK_PKCS7_get_PEM_certificates +PKCS7_get_certificates LK_PKCS7_get_certificates +PKCS7_get_raw_certificates LK_PKCS7_get_raw_certificates +PKCS7_sign LK_PKCS7_sign +PKCS7_type_is_data LK_PKCS7_type_is_data +PKCS7_type_is_digest LK_PKCS7_type_is_digest +PKCS7_type_is_encrypted LK_PKCS7_type_is_encrypted +PKCS7_type_is_enveloped LK_PKCS7_type_is_enveloped +PKCS7_type_is_signed LK_PKCS7_type_is_signed +PKCS7_type_is_signedAndEnveloped LK_PKCS7_type_is_signedAndEnveloped +PKCS8_PRIV_KEY_INFO_free LK_PKCS8_PRIV_KEY_INFO_free +PKCS8_PRIV_KEY_INFO_it LK_PKCS8_PRIV_KEY_INFO_it +PKCS8_PRIV_KEY_INFO_new LK_PKCS8_PRIV_KEY_INFO_new +PKCS8_decrypt LK_PKCS8_decrypt +PKCS8_encrypt LK_PKCS8_encrypt +PKCS8_marshal_encrypted_private_key LK_PKCS8_marshal_encrypted_private_key +PKCS8_parse_encrypted_private_key LK_PKCS8_parse_encrypted_private_key +POLICYINFO_free LK_POLICYINFO_free +POLICYINFO_it LK_POLICYINFO_it +POLICYINFO_new LK_POLICYINFO_new +POLICYQUALINFO_free LK_POLICYQUALINFO_free +POLICYQUALINFO_it LK_POLICYQUALINFO_it +POLICYQUALINFO_new LK_POLICYQUALINFO_new +POLICY_CONSTRAINTS_free LK_POLICY_CONSTRAINTS_free +POLICY_CONSTRAINTS_it LK_POLICY_CONSTRAINTS_it +POLICY_CONSTRAINTS_new LK_POLICY_CONSTRAINTS_new +POLICY_MAPPINGS_it LK_POLICY_MAPPINGS_it +POLICY_MAPPING_free LK_POLICY_MAPPING_free +POLICY_MAPPING_it LK_POLICY_MAPPING_it +POLICY_MAPPING_new LK_POLICY_MAPPING_new +RAND_OpenSSL LK_RAND_OpenSSL +RAND_SSLeay LK_RAND_SSLeay +RAND_add LK_RAND_add +RAND_bytes LK_RAND_bytes +RAND_bytes_with_additional_data LK_RAND_bytes_with_additional_data +RAND_cleanup LK_RAND_cleanup +RAND_disable_fork_unsafe_buffering LK_RAND_disable_fork_unsafe_buffering +RAND_egd LK_RAND_egd +RAND_enable_fork_unsafe_buffering LK_RAND_enable_fork_unsafe_buffering +RAND_file_name LK_RAND_file_name +RAND_get_rand_method LK_RAND_get_rand_method +RAND_get_system_entropy_for_custom_prng LK_RAND_get_system_entropy_for_custom_prng +RAND_load_file LK_RAND_load_file +RAND_poll LK_RAND_poll +RAND_pseudo_bytes LK_RAND_pseudo_bytes +RAND_seed LK_RAND_seed +RAND_set_rand_method LK_RAND_set_rand_method +RAND_status LK_RAND_status +RC4 LK_RC4 +RC4_options LK_RC4_options +RC4_set_key LK_RC4_set_key +RIPEMD160 LK_RIPEMD160 +RIPEMD160_Final LK_RIPEMD160_Final +RIPEMD160_Init LK_RIPEMD160_Init +RIPEMD160_Transform LK_RIPEMD160_Transform +RIPEMD160_Update LK_RIPEMD160_Update +RSAPrivateKey_dup LK_RSAPrivateKey_dup +RSAPublicKey_dup LK_RSAPublicKey_dup +RSAZ_1024_mod_exp_avx2 LK_RSAZ_1024_mod_exp_avx2 +RSA_PSS_PARAMS_free LK_RSA_PSS_PARAMS_free +RSA_PSS_PARAMS_it LK_RSA_PSS_PARAMS_it +RSA_PSS_PARAMS_new LK_RSA_PSS_PARAMS_new +RSA_add_pkcs1_prefix LK_RSA_add_pkcs1_prefix +RSA_bits LK_RSA_bits +RSA_blinding_on LK_RSA_blinding_on +RSA_check_fips LK_RSA_check_fips +RSA_check_key LK_RSA_check_key +RSA_decrypt LK_RSA_decrypt +RSA_default_method LK_RSA_default_method +RSA_encrypt LK_RSA_encrypt +RSA_flags LK_RSA_flags +RSA_free LK_RSA_free +RSA_generate_key LK_RSA_generate_key +RSA_generate_key_ex LK_RSA_generate_key_ex +RSA_generate_key_fips LK_RSA_generate_key_fips +RSA_get0_crt_params LK_RSA_get0_crt_params +RSA_get0_d LK_RSA_get0_d +RSA_get0_dmp1 LK_RSA_get0_dmp1 +RSA_get0_dmq1 LK_RSA_get0_dmq1 +RSA_get0_e LK_RSA_get0_e +RSA_get0_factors LK_RSA_get0_factors +RSA_get0_iqmp LK_RSA_get0_iqmp +RSA_get0_key LK_RSA_get0_key +RSA_get0_n LK_RSA_get0_n +RSA_get0_p LK_RSA_get0_p +RSA_get0_pss_params LK_RSA_get0_pss_params +RSA_get0_q LK_RSA_get0_q +RSA_get_ex_data LK_RSA_get_ex_data +RSA_get_ex_new_index LK_RSA_get_ex_new_index +RSA_is_opaque LK_RSA_is_opaque +RSA_marshal_private_key LK_RSA_marshal_private_key +RSA_marshal_public_key LK_RSA_marshal_public_key +RSA_new LK_RSA_new +RSA_new_method LK_RSA_new_method +RSA_new_method_no_e LK_RSA_new_method_no_e +RSA_new_private_key LK_RSA_new_private_key +RSA_new_private_key_large_e LK_RSA_new_private_key_large_e +RSA_new_private_key_no_crt LK_RSA_new_private_key_no_crt +RSA_new_private_key_no_e LK_RSA_new_private_key_no_e +RSA_new_public_key LK_RSA_new_public_key +RSA_new_public_key_large_e LK_RSA_new_public_key_large_e +RSA_padding_add_PKCS1_OAEP LK_RSA_padding_add_PKCS1_OAEP +RSA_padding_add_PKCS1_OAEP_mgf1 LK_RSA_padding_add_PKCS1_OAEP_mgf1 +RSA_padding_add_PKCS1_PSS LK_RSA_padding_add_PKCS1_PSS +RSA_padding_add_PKCS1_PSS_mgf1 LK_RSA_padding_add_PKCS1_PSS_mgf1 +RSA_padding_add_PKCS1_type_1 LK_RSA_padding_add_PKCS1_type_1 +RSA_padding_add_none LK_RSA_padding_add_none +RSA_padding_check_PKCS1_OAEP_mgf1 LK_RSA_padding_check_PKCS1_OAEP_mgf1 +RSA_padding_check_PKCS1_type_1 LK_RSA_padding_check_PKCS1_type_1 +RSA_parse_private_key LK_RSA_parse_private_key +RSA_parse_public_key LK_RSA_parse_public_key +RSA_print LK_RSA_print +RSA_private_decrypt LK_RSA_private_decrypt +RSA_private_encrypt LK_RSA_private_encrypt +RSA_private_key_from_bytes LK_RSA_private_key_from_bytes +RSA_private_key_to_bytes LK_RSA_private_key_to_bytes +RSA_public_decrypt LK_RSA_public_decrypt +RSA_public_encrypt LK_RSA_public_encrypt +RSA_public_key_from_bytes LK_RSA_public_key_from_bytes +RSA_public_key_to_bytes LK_RSA_public_key_to_bytes +RSA_set0_crt_params LK_RSA_set0_crt_params +RSA_set0_factors LK_RSA_set0_factors +RSA_set0_key LK_RSA_set0_key +RSA_set_ex_data LK_RSA_set_ex_data +RSA_sign LK_RSA_sign +RSA_sign_pss_mgf1 LK_RSA_sign_pss_mgf1 +RSA_sign_raw LK_RSA_sign_raw +RSA_size LK_RSA_size +RSA_test_flags LK_RSA_test_flags +RSA_up_ref LK_RSA_up_ref +RSA_verify LK_RSA_verify +RSA_verify_PKCS1_PSS LK_RSA_verify_PKCS1_PSS +RSA_verify_PKCS1_PSS_mgf1 LK_RSA_verify_PKCS1_PSS_mgf1 +RSA_verify_pss_mgf1 LK_RSA_verify_pss_mgf1 +RSA_verify_raw LK_RSA_verify_raw +SHA1 LK_SHA1 +SHA1_Final LK_SHA1_Final +SHA1_Init LK_SHA1_Init +SHA1_Transform LK_SHA1_Transform +SHA1_Update LK_SHA1_Update +SHA224 LK_SHA224 +SHA224_Final LK_SHA224_Final +SHA224_Init LK_SHA224_Init +SHA224_Update LK_SHA224_Update +SHA256 LK_SHA256 +SHA256_Final LK_SHA256_Final +SHA256_Init LK_SHA256_Init +SHA256_Transform LK_SHA256_Transform +SHA256_TransformBlocks LK_SHA256_TransformBlocks +SHA256_Update LK_SHA256_Update +SHA384 LK_SHA384 +SHA384_Final LK_SHA384_Final +SHA384_Init LK_SHA384_Init +SHA384_Update LK_SHA384_Update +SHA512 LK_SHA512 +SHA512_256 LK_SHA512_256 +SHA512_256_Final LK_SHA512_256_Final +SHA512_256_Init LK_SHA512_256_Init +SHA512_256_Update LK_SHA512_256_Update +SHA512_Final LK_SHA512_Final +SHA512_Init LK_SHA512_Init +SHA512_Transform LK_SHA512_Transform +SHA512_Update LK_SHA512_Update +SIPHASH_24 LK_SIPHASH_24 +SPAKE2_CTX_free LK_SPAKE2_CTX_free +SPAKE2_CTX_new LK_SPAKE2_CTX_new +SPAKE2_generate_msg LK_SPAKE2_generate_msg +SPAKE2_process_msg LK_SPAKE2_process_msg +SPX_generate_key LK_SPX_generate_key +SPX_generate_key_from_seed LK_SPX_generate_key_from_seed +SPX_sign LK_SPX_sign +SPX_verify LK_SPX_verify +SSL_add_dir_cert_subjects_to_stack LK_SSL_add_dir_cert_subjects_to_stack +SSLeay LK_SSLeay +SSLeay_version LK_SSLeay_version +TRUST_TOKEN_CLIENT_add_key LK_TRUST_TOKEN_CLIENT_add_key +TRUST_TOKEN_CLIENT_begin_issuance LK_TRUST_TOKEN_CLIENT_begin_issuance +TRUST_TOKEN_CLIENT_begin_issuance_over_message LK_TRUST_TOKEN_CLIENT_begin_issuance_over_message +TRUST_TOKEN_CLIENT_begin_redemption LK_TRUST_TOKEN_CLIENT_begin_redemption +TRUST_TOKEN_CLIENT_finish_issuance LK_TRUST_TOKEN_CLIENT_finish_issuance +TRUST_TOKEN_CLIENT_finish_redemption LK_TRUST_TOKEN_CLIENT_finish_redemption +TRUST_TOKEN_CLIENT_free LK_TRUST_TOKEN_CLIENT_free +TRUST_TOKEN_CLIENT_new LK_TRUST_TOKEN_CLIENT_new +TRUST_TOKEN_CLIENT_set_srr_key LK_TRUST_TOKEN_CLIENT_set_srr_key +TRUST_TOKEN_ISSUER_add_key LK_TRUST_TOKEN_ISSUER_add_key +TRUST_TOKEN_ISSUER_free LK_TRUST_TOKEN_ISSUER_free +TRUST_TOKEN_ISSUER_issue LK_TRUST_TOKEN_ISSUER_issue +TRUST_TOKEN_ISSUER_new LK_TRUST_TOKEN_ISSUER_new +TRUST_TOKEN_ISSUER_redeem LK_TRUST_TOKEN_ISSUER_redeem +TRUST_TOKEN_ISSUER_redeem_over_message LK_TRUST_TOKEN_ISSUER_redeem_over_message +TRUST_TOKEN_ISSUER_set_metadata_key LK_TRUST_TOKEN_ISSUER_set_metadata_key +TRUST_TOKEN_ISSUER_set_srr_key LK_TRUST_TOKEN_ISSUER_set_srr_key +TRUST_TOKEN_PRETOKEN_free LK_TRUST_TOKEN_PRETOKEN_free +TRUST_TOKEN_decode_private_metadata LK_TRUST_TOKEN_decode_private_metadata +TRUST_TOKEN_derive_key_from_secret LK_TRUST_TOKEN_derive_key_from_secret +TRUST_TOKEN_experiment_v1 LK_TRUST_TOKEN_experiment_v1 +TRUST_TOKEN_experiment_v2_pmb LK_TRUST_TOKEN_experiment_v2_pmb +TRUST_TOKEN_experiment_v2_voprf LK_TRUST_TOKEN_experiment_v2_voprf +TRUST_TOKEN_free LK_TRUST_TOKEN_free +TRUST_TOKEN_generate_key LK_TRUST_TOKEN_generate_key +TRUST_TOKEN_new LK_TRUST_TOKEN_new +TRUST_TOKEN_pst_v1_pmb LK_TRUST_TOKEN_pst_v1_pmb +TRUST_TOKEN_pst_v1_voprf LK_TRUST_TOKEN_pst_v1_voprf +USERNOTICE_free LK_USERNOTICE_free +USERNOTICE_it LK_USERNOTICE_it +USERNOTICE_new LK_USERNOTICE_new +X25519 LK_X25519 +X25519_keypair LK_X25519_keypair +X25519_public_from_private LK_X25519_public_from_private +X509V3_EXT_CRL_add_nconf LK_X509V3_EXT_CRL_add_nconf +X509V3_EXT_REQ_add_nconf LK_X509V3_EXT_REQ_add_nconf +X509V3_EXT_add LK_X509V3_EXT_add +X509V3_EXT_add_alias LK_X509V3_EXT_add_alias +X509V3_EXT_add_nconf LK_X509V3_EXT_add_nconf +X509V3_EXT_add_nconf_sk LK_X509V3_EXT_add_nconf_sk +X509V3_EXT_conf_nid LK_X509V3_EXT_conf_nid +X509V3_EXT_d2i LK_X509V3_EXT_d2i +X509V3_EXT_free LK_X509V3_EXT_free +X509V3_EXT_get LK_X509V3_EXT_get +X509V3_EXT_get_nid LK_X509V3_EXT_get_nid +X509V3_EXT_i2d LK_X509V3_EXT_i2d +X509V3_EXT_nconf LK_X509V3_EXT_nconf +X509V3_EXT_nconf_nid LK_X509V3_EXT_nconf_nid +X509V3_EXT_print LK_X509V3_EXT_print +X509V3_EXT_print_fp LK_X509V3_EXT_print_fp +X509V3_NAME_from_section LK_X509V3_NAME_from_section +X509V3_add1_i2d LK_X509V3_add1_i2d +X509V3_add_standard_extensions LK_X509V3_add_standard_extensions +X509V3_add_value LK_X509V3_add_value +X509V3_add_value_bool LK_X509V3_add_value_bool +X509V3_add_value_int LK_X509V3_add_value_int +X509V3_bool_from_string LK_X509V3_bool_from_string +X509V3_conf_free LK_X509V3_conf_free +X509V3_extensions_print LK_X509V3_extensions_print +X509V3_get_d2i LK_X509V3_get_d2i +X509V3_get_section LK_X509V3_get_section +X509V3_get_value_bool LK_X509V3_get_value_bool +X509V3_get_value_int LK_X509V3_get_value_int +X509V3_parse_list LK_X509V3_parse_list +X509V3_set_ctx LK_X509V3_set_ctx +X509V3_set_nconf LK_X509V3_set_nconf +X509_ALGOR_cmp LK_X509_ALGOR_cmp +X509_ALGOR_dup LK_X509_ALGOR_dup +X509_ALGOR_free LK_X509_ALGOR_free +X509_ALGOR_get0 LK_X509_ALGOR_get0 +X509_ALGOR_it LK_X509_ALGOR_it +X509_ALGOR_new LK_X509_ALGOR_new +X509_ALGOR_set0 LK_X509_ALGOR_set0 +X509_ALGOR_set_md LK_X509_ALGOR_set_md +X509_ATTRIBUTE_count LK_X509_ATTRIBUTE_count +X509_ATTRIBUTE_create LK_X509_ATTRIBUTE_create +X509_ATTRIBUTE_create_by_NID LK_X509_ATTRIBUTE_create_by_NID +X509_ATTRIBUTE_create_by_OBJ LK_X509_ATTRIBUTE_create_by_OBJ +X509_ATTRIBUTE_create_by_txt LK_X509_ATTRIBUTE_create_by_txt +X509_ATTRIBUTE_dup LK_X509_ATTRIBUTE_dup +X509_ATTRIBUTE_free LK_X509_ATTRIBUTE_free +X509_ATTRIBUTE_get0_data LK_X509_ATTRIBUTE_get0_data +X509_ATTRIBUTE_get0_object LK_X509_ATTRIBUTE_get0_object +X509_ATTRIBUTE_get0_type LK_X509_ATTRIBUTE_get0_type +X509_ATTRIBUTE_it LK_X509_ATTRIBUTE_it +X509_ATTRIBUTE_new LK_X509_ATTRIBUTE_new +X509_ATTRIBUTE_set1_data LK_X509_ATTRIBUTE_set1_data +X509_ATTRIBUTE_set1_object LK_X509_ATTRIBUTE_set1_object +X509_CERT_AUX_free LK_X509_CERT_AUX_free +X509_CERT_AUX_it LK_X509_CERT_AUX_it +X509_CERT_AUX_new LK_X509_CERT_AUX_new +X509_CERT_AUX_print LK_X509_CERT_AUX_print +X509_CINF_free LK_X509_CINF_free +X509_CINF_it LK_X509_CINF_it +X509_CINF_new LK_X509_CINF_new +X509_CRL_INFO_free LK_X509_CRL_INFO_free +X509_CRL_INFO_it LK_X509_CRL_INFO_it +X509_CRL_INFO_new LK_X509_CRL_INFO_new +X509_CRL_add0_revoked LK_X509_CRL_add0_revoked +X509_CRL_add1_ext_i2d LK_X509_CRL_add1_ext_i2d +X509_CRL_add_ext LK_X509_CRL_add_ext +X509_CRL_cmp LK_X509_CRL_cmp +X509_CRL_delete_ext LK_X509_CRL_delete_ext +X509_CRL_digest LK_X509_CRL_digest +X509_CRL_dup LK_X509_CRL_dup +X509_CRL_free LK_X509_CRL_free +X509_CRL_get0_by_cert LK_X509_CRL_get0_by_cert +X509_CRL_get0_by_serial LK_X509_CRL_get0_by_serial +X509_CRL_get0_extensions LK_X509_CRL_get0_extensions +X509_CRL_get0_lastUpdate LK_X509_CRL_get0_lastUpdate +X509_CRL_get0_nextUpdate LK_X509_CRL_get0_nextUpdate +X509_CRL_get0_signature LK_X509_CRL_get0_signature +X509_CRL_get_REVOKED LK_X509_CRL_get_REVOKED +X509_CRL_get_ext LK_X509_CRL_get_ext +X509_CRL_get_ext_by_NID LK_X509_CRL_get_ext_by_NID +X509_CRL_get_ext_by_OBJ LK_X509_CRL_get_ext_by_OBJ +X509_CRL_get_ext_by_critical LK_X509_CRL_get_ext_by_critical +X509_CRL_get_ext_count LK_X509_CRL_get_ext_count +X509_CRL_get_ext_d2i LK_X509_CRL_get_ext_d2i +X509_CRL_get_issuer LK_X509_CRL_get_issuer +X509_CRL_get_lastUpdate LK_X509_CRL_get_lastUpdate +X509_CRL_get_nextUpdate LK_X509_CRL_get_nextUpdate +X509_CRL_get_signature_nid LK_X509_CRL_get_signature_nid +X509_CRL_get_version LK_X509_CRL_get_version +X509_CRL_it LK_X509_CRL_it +X509_CRL_match LK_X509_CRL_match +X509_CRL_new LK_X509_CRL_new +X509_CRL_print LK_X509_CRL_print +X509_CRL_print_fp LK_X509_CRL_print_fp +X509_CRL_set1_lastUpdate LK_X509_CRL_set1_lastUpdate +X509_CRL_set1_nextUpdate LK_X509_CRL_set1_nextUpdate +X509_CRL_set1_signature_algo LK_X509_CRL_set1_signature_algo +X509_CRL_set1_signature_value LK_X509_CRL_set1_signature_value +X509_CRL_set_issuer_name LK_X509_CRL_set_issuer_name +X509_CRL_set_version LK_X509_CRL_set_version +X509_CRL_sign LK_X509_CRL_sign +X509_CRL_sign_ctx LK_X509_CRL_sign_ctx +X509_CRL_sort LK_X509_CRL_sort +X509_CRL_up_ref LK_X509_CRL_up_ref +X509_CRL_verify LK_X509_CRL_verify +X509_EXTENSIONS_it LK_X509_EXTENSIONS_it +X509_EXTENSION_create_by_NID LK_X509_EXTENSION_create_by_NID +X509_EXTENSION_create_by_OBJ LK_X509_EXTENSION_create_by_OBJ +X509_EXTENSION_dup LK_X509_EXTENSION_dup +X509_EXTENSION_free LK_X509_EXTENSION_free +X509_EXTENSION_get_critical LK_X509_EXTENSION_get_critical +X509_EXTENSION_get_data LK_X509_EXTENSION_get_data +X509_EXTENSION_get_object LK_X509_EXTENSION_get_object +X509_EXTENSION_it LK_X509_EXTENSION_it +X509_EXTENSION_new LK_X509_EXTENSION_new +X509_EXTENSION_set_critical LK_X509_EXTENSION_set_critical +X509_EXTENSION_set_data LK_X509_EXTENSION_set_data +X509_EXTENSION_set_object LK_X509_EXTENSION_set_object +X509_INFO_free LK_X509_INFO_free +X509_LOOKUP_add_dir LK_X509_LOOKUP_add_dir +X509_LOOKUP_ctrl LK_X509_LOOKUP_ctrl +X509_LOOKUP_file LK_X509_LOOKUP_file +X509_LOOKUP_free LK_X509_LOOKUP_free +X509_LOOKUP_hash_dir LK_X509_LOOKUP_hash_dir +X509_LOOKUP_load_file LK_X509_LOOKUP_load_file +X509_NAME_ENTRIES_it LK_X509_NAME_ENTRIES_it +X509_NAME_ENTRY_create_by_NID LK_X509_NAME_ENTRY_create_by_NID +X509_NAME_ENTRY_create_by_OBJ LK_X509_NAME_ENTRY_create_by_OBJ +X509_NAME_ENTRY_create_by_txt LK_X509_NAME_ENTRY_create_by_txt +X509_NAME_ENTRY_dup LK_X509_NAME_ENTRY_dup +X509_NAME_ENTRY_free LK_X509_NAME_ENTRY_free +X509_NAME_ENTRY_get_data LK_X509_NAME_ENTRY_get_data +X509_NAME_ENTRY_get_object LK_X509_NAME_ENTRY_get_object +X509_NAME_ENTRY_it LK_X509_NAME_ENTRY_it +X509_NAME_ENTRY_new LK_X509_NAME_ENTRY_new +X509_NAME_ENTRY_set LK_X509_NAME_ENTRY_set +X509_NAME_ENTRY_set_data LK_X509_NAME_ENTRY_set_data +X509_NAME_ENTRY_set_object LK_X509_NAME_ENTRY_set_object +X509_NAME_INTERNAL_it LK_X509_NAME_INTERNAL_it +X509_NAME_add_entry LK_X509_NAME_add_entry +X509_NAME_add_entry_by_NID LK_X509_NAME_add_entry_by_NID +X509_NAME_add_entry_by_OBJ LK_X509_NAME_add_entry_by_OBJ +X509_NAME_add_entry_by_txt LK_X509_NAME_add_entry_by_txt +X509_NAME_cmp LK_X509_NAME_cmp +X509_NAME_delete_entry LK_X509_NAME_delete_entry +X509_NAME_digest LK_X509_NAME_digest +X509_NAME_dup LK_X509_NAME_dup +X509_NAME_entry_count LK_X509_NAME_entry_count +X509_NAME_free LK_X509_NAME_free +X509_NAME_get0_der LK_X509_NAME_get0_der +X509_NAME_get_entry LK_X509_NAME_get_entry +X509_NAME_get_index_by_NID LK_X509_NAME_get_index_by_NID +X509_NAME_get_index_by_OBJ LK_X509_NAME_get_index_by_OBJ +X509_NAME_get_text_by_NID LK_X509_NAME_get_text_by_NID +X509_NAME_get_text_by_OBJ LK_X509_NAME_get_text_by_OBJ +X509_NAME_hash LK_X509_NAME_hash +X509_NAME_hash_old LK_X509_NAME_hash_old +X509_NAME_it LK_X509_NAME_it +X509_NAME_new LK_X509_NAME_new +X509_NAME_oneline LK_X509_NAME_oneline +X509_NAME_print LK_X509_NAME_print +X509_NAME_print_ex LK_X509_NAME_print_ex +X509_NAME_print_ex_fp LK_X509_NAME_print_ex_fp +X509_NAME_set LK_X509_NAME_set +X509_OBJECT_free LK_X509_OBJECT_free +X509_OBJECT_free_contents LK_X509_OBJECT_free_contents +X509_OBJECT_get0_X509 LK_X509_OBJECT_get0_X509 +X509_OBJECT_get_type LK_X509_OBJECT_get_type +X509_OBJECT_new LK_X509_OBJECT_new +X509_PUBKEY_free LK_X509_PUBKEY_free +X509_PUBKEY_get LK_X509_PUBKEY_get +X509_PUBKEY_get0 LK_X509_PUBKEY_get0 +X509_PUBKEY_get0_param LK_X509_PUBKEY_get0_param +X509_PUBKEY_get0_public_key LK_X509_PUBKEY_get0_public_key +X509_PUBKEY_it LK_X509_PUBKEY_it +X509_PUBKEY_new LK_X509_PUBKEY_new +X509_PUBKEY_set LK_X509_PUBKEY_set +X509_PUBKEY_set0_param LK_X509_PUBKEY_set0_param +X509_PURPOSE_get0 LK_X509_PURPOSE_get0 +X509_PURPOSE_get_by_sname LK_X509_PURPOSE_get_by_sname +X509_PURPOSE_get_id LK_X509_PURPOSE_get_id +X509_PURPOSE_get_trust LK_X509_PURPOSE_get_trust +X509_REQ_INFO_free LK_X509_REQ_INFO_free +X509_REQ_INFO_it LK_X509_REQ_INFO_it +X509_REQ_INFO_new LK_X509_REQ_INFO_new +X509_REQ_add1_attr LK_X509_REQ_add1_attr +X509_REQ_add1_attr_by_NID LK_X509_REQ_add1_attr_by_NID +X509_REQ_add1_attr_by_OBJ LK_X509_REQ_add1_attr_by_OBJ +X509_REQ_add1_attr_by_txt LK_X509_REQ_add1_attr_by_txt +X509_REQ_add_extensions LK_X509_REQ_add_extensions +X509_REQ_add_extensions_nid LK_X509_REQ_add_extensions_nid +X509_REQ_check_private_key LK_X509_REQ_check_private_key +X509_REQ_delete_attr LK_X509_REQ_delete_attr +X509_REQ_digest LK_X509_REQ_digest +X509_REQ_dup LK_X509_REQ_dup +X509_REQ_extension_nid LK_X509_REQ_extension_nid +X509_REQ_free LK_X509_REQ_free +X509_REQ_get0_pubkey LK_X509_REQ_get0_pubkey +X509_REQ_get0_signature LK_X509_REQ_get0_signature +X509_REQ_get1_email LK_X509_REQ_get1_email +X509_REQ_get_attr LK_X509_REQ_get_attr +X509_REQ_get_attr_by_NID LK_X509_REQ_get_attr_by_NID +X509_REQ_get_attr_by_OBJ LK_X509_REQ_get_attr_by_OBJ +X509_REQ_get_attr_count LK_X509_REQ_get_attr_count +X509_REQ_get_extensions LK_X509_REQ_get_extensions +X509_REQ_get_pubkey LK_X509_REQ_get_pubkey +X509_REQ_get_signature_nid LK_X509_REQ_get_signature_nid +X509_REQ_get_subject_name LK_X509_REQ_get_subject_name +X509_REQ_get_version LK_X509_REQ_get_version +X509_REQ_it LK_X509_REQ_it +X509_REQ_new LK_X509_REQ_new +X509_REQ_print LK_X509_REQ_print +X509_REQ_print_ex LK_X509_REQ_print_ex +X509_REQ_print_fp LK_X509_REQ_print_fp +X509_REQ_set1_signature_algo LK_X509_REQ_set1_signature_algo +X509_REQ_set1_signature_value LK_X509_REQ_set1_signature_value +X509_REQ_set_pubkey LK_X509_REQ_set_pubkey +X509_REQ_set_subject_name LK_X509_REQ_set_subject_name +X509_REQ_set_version LK_X509_REQ_set_version +X509_REQ_sign LK_X509_REQ_sign +X509_REQ_sign_ctx LK_X509_REQ_sign_ctx +X509_REQ_verify LK_X509_REQ_verify +X509_REVOKED_add1_ext_i2d LK_X509_REVOKED_add1_ext_i2d +X509_REVOKED_add_ext LK_X509_REVOKED_add_ext +X509_REVOKED_delete_ext LK_X509_REVOKED_delete_ext +X509_REVOKED_dup LK_X509_REVOKED_dup +X509_REVOKED_free LK_X509_REVOKED_free +X509_REVOKED_get0_extensions LK_X509_REVOKED_get0_extensions +X509_REVOKED_get0_revocationDate LK_X509_REVOKED_get0_revocationDate +X509_REVOKED_get0_serialNumber LK_X509_REVOKED_get0_serialNumber +X509_REVOKED_get_ext LK_X509_REVOKED_get_ext +X509_REVOKED_get_ext_by_NID LK_X509_REVOKED_get_ext_by_NID +X509_REVOKED_get_ext_by_OBJ LK_X509_REVOKED_get_ext_by_OBJ +X509_REVOKED_get_ext_by_critical LK_X509_REVOKED_get_ext_by_critical +X509_REVOKED_get_ext_count LK_X509_REVOKED_get_ext_count +X509_REVOKED_get_ext_d2i LK_X509_REVOKED_get_ext_d2i +X509_REVOKED_it LK_X509_REVOKED_it +X509_REVOKED_new LK_X509_REVOKED_new +X509_REVOKED_set_revocationDate LK_X509_REVOKED_set_revocationDate +X509_REVOKED_set_serialNumber LK_X509_REVOKED_set_serialNumber +X509_SIG_free LK_X509_SIG_free +X509_SIG_get0 LK_X509_SIG_get0 +X509_SIG_getm LK_X509_SIG_getm +X509_SIG_it LK_X509_SIG_it +X509_SIG_new LK_X509_SIG_new +X509_STORE_CTX_cleanup LK_X509_STORE_CTX_cleanup +X509_STORE_CTX_free LK_X509_STORE_CTX_free +X509_STORE_CTX_get0_cert LK_X509_STORE_CTX_get0_cert +X509_STORE_CTX_get0_chain LK_X509_STORE_CTX_get0_chain +X509_STORE_CTX_get0_current_crl LK_X509_STORE_CTX_get0_current_crl +X509_STORE_CTX_get0_param LK_X509_STORE_CTX_get0_param +X509_STORE_CTX_get0_parent_ctx LK_X509_STORE_CTX_get0_parent_ctx +X509_STORE_CTX_get0_store LK_X509_STORE_CTX_get0_store +X509_STORE_CTX_get0_untrusted LK_X509_STORE_CTX_get0_untrusted +X509_STORE_CTX_get1_certs LK_X509_STORE_CTX_get1_certs +X509_STORE_CTX_get1_chain LK_X509_STORE_CTX_get1_chain +X509_STORE_CTX_get1_crls LK_X509_STORE_CTX_get1_crls +X509_STORE_CTX_get1_issuer LK_X509_STORE_CTX_get1_issuer +X509_STORE_CTX_get_by_subject LK_X509_STORE_CTX_get_by_subject +X509_STORE_CTX_get_chain LK_X509_STORE_CTX_get_chain +X509_STORE_CTX_get_current_cert LK_X509_STORE_CTX_get_current_cert +X509_STORE_CTX_get_error LK_X509_STORE_CTX_get_error +X509_STORE_CTX_get_error_depth LK_X509_STORE_CTX_get_error_depth +X509_STORE_CTX_get_ex_data LK_X509_STORE_CTX_get_ex_data +X509_STORE_CTX_get_ex_new_index LK_X509_STORE_CTX_get_ex_new_index +X509_STORE_CTX_init LK_X509_STORE_CTX_init +X509_STORE_CTX_new LK_X509_STORE_CTX_new +X509_STORE_CTX_set0_crls LK_X509_STORE_CTX_set0_crls +X509_STORE_CTX_set0_param LK_X509_STORE_CTX_set0_param +X509_STORE_CTX_set0_trusted_stack LK_X509_STORE_CTX_set0_trusted_stack +X509_STORE_CTX_set_chain LK_X509_STORE_CTX_set_chain +X509_STORE_CTX_set_default LK_X509_STORE_CTX_set_default +X509_STORE_CTX_set_depth LK_X509_STORE_CTX_set_depth +X509_STORE_CTX_set_error LK_X509_STORE_CTX_set_error +X509_STORE_CTX_set_ex_data LK_X509_STORE_CTX_set_ex_data +X509_STORE_CTX_set_flags LK_X509_STORE_CTX_set_flags +X509_STORE_CTX_set_purpose LK_X509_STORE_CTX_set_purpose +X509_STORE_CTX_set_time LK_X509_STORE_CTX_set_time +X509_STORE_CTX_set_time_posix LK_X509_STORE_CTX_set_time_posix +X509_STORE_CTX_set_trust LK_X509_STORE_CTX_set_trust +X509_STORE_CTX_set_verify_cb LK_X509_STORE_CTX_set_verify_cb +X509_STORE_CTX_trusted_stack LK_X509_STORE_CTX_trusted_stack +X509_STORE_add_cert LK_X509_STORE_add_cert +X509_STORE_add_crl LK_X509_STORE_add_crl +X509_STORE_add_lookup LK_X509_STORE_add_lookup +X509_STORE_free LK_X509_STORE_free +X509_STORE_get0_objects LK_X509_STORE_get0_objects +X509_STORE_get0_param LK_X509_STORE_get0_param +X509_STORE_get1_objects LK_X509_STORE_get1_objects +X509_STORE_load_locations LK_X509_STORE_load_locations +X509_STORE_new LK_X509_STORE_new +X509_STORE_set1_param LK_X509_STORE_set1_param +X509_STORE_set_check_crl LK_X509_STORE_set_check_crl +X509_STORE_set_default_paths LK_X509_STORE_set_default_paths +X509_STORE_set_depth LK_X509_STORE_set_depth +X509_STORE_set_flags LK_X509_STORE_set_flags +X509_STORE_set_get_crl LK_X509_STORE_set_get_crl +X509_STORE_set_purpose LK_X509_STORE_set_purpose +X509_STORE_set_trust LK_X509_STORE_set_trust +X509_STORE_set_verify_cb LK_X509_STORE_set_verify_cb +X509_STORE_up_ref LK_X509_STORE_up_ref +X509_VAL_free LK_X509_VAL_free +X509_VAL_it LK_X509_VAL_it +X509_VAL_new LK_X509_VAL_new +X509_VERIFY_PARAM_add0_policy LK_X509_VERIFY_PARAM_add0_policy +X509_VERIFY_PARAM_add1_host LK_X509_VERIFY_PARAM_add1_host +X509_VERIFY_PARAM_clear_flags LK_X509_VERIFY_PARAM_clear_flags +X509_VERIFY_PARAM_free LK_X509_VERIFY_PARAM_free +X509_VERIFY_PARAM_get_depth LK_X509_VERIFY_PARAM_get_depth +X509_VERIFY_PARAM_get_flags LK_X509_VERIFY_PARAM_get_flags +X509_VERIFY_PARAM_inherit LK_X509_VERIFY_PARAM_inherit +X509_VERIFY_PARAM_lookup LK_X509_VERIFY_PARAM_lookup +X509_VERIFY_PARAM_new LK_X509_VERIFY_PARAM_new +X509_VERIFY_PARAM_set1 LK_X509_VERIFY_PARAM_set1 +X509_VERIFY_PARAM_set1_email LK_X509_VERIFY_PARAM_set1_email +X509_VERIFY_PARAM_set1_host LK_X509_VERIFY_PARAM_set1_host +X509_VERIFY_PARAM_set1_ip LK_X509_VERIFY_PARAM_set1_ip +X509_VERIFY_PARAM_set1_ip_asc LK_X509_VERIFY_PARAM_set1_ip_asc +X509_VERIFY_PARAM_set1_policies LK_X509_VERIFY_PARAM_set1_policies +X509_VERIFY_PARAM_set_depth LK_X509_VERIFY_PARAM_set_depth +X509_VERIFY_PARAM_set_flags LK_X509_VERIFY_PARAM_set_flags +X509_VERIFY_PARAM_set_hostflags LK_X509_VERIFY_PARAM_set_hostflags +X509_VERIFY_PARAM_set_purpose LK_X509_VERIFY_PARAM_set_purpose +X509_VERIFY_PARAM_set_time LK_X509_VERIFY_PARAM_set_time +X509_VERIFY_PARAM_set_time_posix LK_X509_VERIFY_PARAM_set_time_posix +X509_VERIFY_PARAM_set_trust LK_X509_VERIFY_PARAM_set_trust +X509_add1_ext_i2d LK_X509_add1_ext_i2d +X509_add1_reject_object LK_X509_add1_reject_object +X509_add1_trust_object LK_X509_add1_trust_object +X509_add_ext LK_X509_add_ext +X509_alias_get0 LK_X509_alias_get0 +X509_alias_set1 LK_X509_alias_set1 +X509_chain_up_ref LK_X509_chain_up_ref +X509_check_akid LK_X509_check_akid +X509_check_ca LK_X509_check_ca +X509_check_email LK_X509_check_email +X509_check_host LK_X509_check_host +X509_check_ip LK_X509_check_ip +X509_check_ip_asc LK_X509_check_ip_asc +X509_check_issued LK_X509_check_issued +X509_check_private_key LK_X509_check_private_key +X509_check_purpose LK_X509_check_purpose +X509_check_trust LK_X509_check_trust +X509_cmp LK_X509_cmp +X509_cmp_current_time LK_X509_cmp_current_time +X509_cmp_time LK_X509_cmp_time +X509_cmp_time_posix LK_X509_cmp_time_posix +X509_delete_ext LK_X509_delete_ext +X509_digest LK_X509_digest +X509_dup LK_X509_dup +X509_email_free LK_X509_email_free +X509_find_by_issuer_and_serial LK_X509_find_by_issuer_and_serial +X509_find_by_subject LK_X509_find_by_subject +X509_free LK_X509_free +X509_get0_authority_issuer LK_X509_get0_authority_issuer +X509_get0_authority_key_id LK_X509_get0_authority_key_id +X509_get0_authority_serial LK_X509_get0_authority_serial +X509_get0_extensions LK_X509_get0_extensions +X509_get0_notAfter LK_X509_get0_notAfter +X509_get0_notBefore LK_X509_get0_notBefore +X509_get0_pubkey LK_X509_get0_pubkey +X509_get0_pubkey_bitstr LK_X509_get0_pubkey_bitstr +X509_get0_serialNumber LK_X509_get0_serialNumber +X509_get0_signature LK_X509_get0_signature +X509_get0_subject_key_id LK_X509_get0_subject_key_id +X509_get0_tbs_sigalg LK_X509_get0_tbs_sigalg +X509_get0_uids LK_X509_get0_uids +X509_get1_email LK_X509_get1_email +X509_get1_ocsp LK_X509_get1_ocsp +X509_get_X509_PUBKEY LK_X509_get_X509_PUBKEY +X509_get_default_cert_area LK_X509_get_default_cert_area +X509_get_default_cert_dir LK_X509_get_default_cert_dir +X509_get_default_cert_dir_env LK_X509_get_default_cert_dir_env +X509_get_default_cert_file LK_X509_get_default_cert_file +X509_get_default_cert_file_env LK_X509_get_default_cert_file_env +X509_get_default_private_dir LK_X509_get_default_private_dir +X509_get_ex_data LK_X509_get_ex_data +X509_get_ex_new_index LK_X509_get_ex_new_index +X509_get_ext LK_X509_get_ext +X509_get_ext_by_NID LK_X509_get_ext_by_NID +X509_get_ext_by_OBJ LK_X509_get_ext_by_OBJ +X509_get_ext_by_critical LK_X509_get_ext_by_critical +X509_get_ext_count LK_X509_get_ext_count +X509_get_ext_d2i LK_X509_get_ext_d2i +X509_get_extended_key_usage LK_X509_get_extended_key_usage +X509_get_extension_flags LK_X509_get_extension_flags +X509_get_issuer_name LK_X509_get_issuer_name +X509_get_key_usage LK_X509_get_key_usage +X509_get_notAfter LK_X509_get_notAfter +X509_get_notBefore LK_X509_get_notBefore +X509_get_pathlen LK_X509_get_pathlen +X509_get_pubkey LK_X509_get_pubkey +X509_get_serialNumber LK_X509_get_serialNumber +X509_get_signature_nid LK_X509_get_signature_nid +X509_get_subject_name LK_X509_get_subject_name +X509_get_version LK_X509_get_version +X509_getm_notAfter LK_X509_getm_notAfter +X509_getm_notBefore LK_X509_getm_notBefore +X509_gmtime_adj LK_X509_gmtime_adj +X509_is_valid_trust_id LK_X509_is_valid_trust_id +X509_issuer_name_cmp LK_X509_issuer_name_cmp +X509_issuer_name_hash LK_X509_issuer_name_hash +X509_issuer_name_hash_old LK_X509_issuer_name_hash_old +X509_it LK_X509_it +X509_keyid_get0 LK_X509_keyid_get0 +X509_keyid_set1 LK_X509_keyid_set1 +X509_load_cert_crl_file LK_X509_load_cert_crl_file +X509_load_cert_file LK_X509_load_cert_file +X509_load_crl_file LK_X509_load_crl_file +X509_new LK_X509_new +X509_parse_from_buffer LK_X509_parse_from_buffer +X509_policy_check LK_X509_policy_check +X509_print LK_X509_print +X509_print_ex LK_X509_print_ex +X509_print_ex_fp LK_X509_print_ex_fp +X509_print_fp LK_X509_print_fp +X509_pubkey_digest LK_X509_pubkey_digest +X509_reject_clear LK_X509_reject_clear +X509_set1_notAfter LK_X509_set1_notAfter +X509_set1_notBefore LK_X509_set1_notBefore +X509_set1_signature_algo LK_X509_set1_signature_algo +X509_set1_signature_value LK_X509_set1_signature_value +X509_set_ex_data LK_X509_set_ex_data +X509_set_issuer_name LK_X509_set_issuer_name +X509_set_notAfter LK_X509_set_notAfter +X509_set_notBefore LK_X509_set_notBefore +X509_set_pubkey LK_X509_set_pubkey +X509_set_serialNumber LK_X509_set_serialNumber +X509_set_subject_name LK_X509_set_subject_name +X509_set_version LK_X509_set_version +X509_sign LK_X509_sign +X509_sign_ctx LK_X509_sign_ctx +X509_signature_dump LK_X509_signature_dump +X509_signature_print LK_X509_signature_print +X509_subject_name_cmp LK_X509_subject_name_cmp +X509_subject_name_hash LK_X509_subject_name_hash +X509_subject_name_hash_old LK_X509_subject_name_hash_old +X509_supported_extension LK_X509_supported_extension +X509_time_adj LK_X509_time_adj +X509_time_adj_ex LK_X509_time_adj_ex +X509_trust_clear LK_X509_trust_clear +X509_up_ref LK_X509_up_ref +X509_verify LK_X509_verify +X509_verify_cert LK_X509_verify_cert +X509_verify_cert_error_string LK_X509_verify_cert_error_string +X509v3_add_ext LK_X509v3_add_ext +X509v3_delete_ext LK_X509v3_delete_ext +X509v3_get_ext LK_X509v3_get_ext +X509v3_get_ext_by_NID LK_X509v3_get_ext_by_NID +X509v3_get_ext_by_OBJ LK_X509v3_get_ext_by_OBJ +X509v3_get_ext_by_critical LK_X509v3_get_ext_by_critical +X509v3_get_ext_count LK_X509v3_get_ext_count +__clang_call_terminate LK___clang_call_terminate +a2i_IPADDRESS LK_a2i_IPADDRESS +a2i_IPADDRESS_NC LK_a2i_IPADDRESS_NC +abi_test_bad_unwind_temporary LK_abi_test_bad_unwind_temporary +abi_test_bad_unwind_wrong_register LK_abi_test_bad_unwind_wrong_register +abi_test_clobber_r10 LK_abi_test_clobber_r10 +abi_test_clobber_r11 LK_abi_test_clobber_r11 +abi_test_clobber_r12 LK_abi_test_clobber_r12 +abi_test_clobber_r13 LK_abi_test_clobber_r13 +abi_test_clobber_r14 LK_abi_test_clobber_r14 +abi_test_clobber_r15 LK_abi_test_clobber_r15 +abi_test_clobber_r8 LK_abi_test_clobber_r8 +abi_test_clobber_r9 LK_abi_test_clobber_r9 +abi_test_clobber_rax LK_abi_test_clobber_rax +abi_test_clobber_rbp LK_abi_test_clobber_rbp +abi_test_clobber_rbx LK_abi_test_clobber_rbx +abi_test_clobber_rcx LK_abi_test_clobber_rcx +abi_test_clobber_rdi LK_abi_test_clobber_rdi +abi_test_clobber_rdx LK_abi_test_clobber_rdx +abi_test_clobber_rsi LK_abi_test_clobber_rsi +abi_test_clobber_xmm0 LK_abi_test_clobber_xmm0 +abi_test_clobber_xmm1 LK_abi_test_clobber_xmm1 +abi_test_clobber_xmm10 LK_abi_test_clobber_xmm10 +abi_test_clobber_xmm11 LK_abi_test_clobber_xmm11 +abi_test_clobber_xmm12 LK_abi_test_clobber_xmm12 +abi_test_clobber_xmm13 LK_abi_test_clobber_xmm13 +abi_test_clobber_xmm14 LK_abi_test_clobber_xmm14 +abi_test_clobber_xmm15 LK_abi_test_clobber_xmm15 +abi_test_clobber_xmm2 LK_abi_test_clobber_xmm2 +abi_test_clobber_xmm3 LK_abi_test_clobber_xmm3 +abi_test_clobber_xmm4 LK_abi_test_clobber_xmm4 +abi_test_clobber_xmm5 LK_abi_test_clobber_xmm5 +abi_test_clobber_xmm6 LK_abi_test_clobber_xmm6 +abi_test_clobber_xmm7 LK_abi_test_clobber_xmm7 +abi_test_clobber_xmm8 LK_abi_test_clobber_xmm8 +abi_test_clobber_xmm9 LK_abi_test_clobber_xmm9 +abi_test_get_and_clear_direction_flag LK_abi_test_get_and_clear_direction_flag +abi_test_set_direction_flag LK_abi_test_set_direction_flag +abi_test_trampoline LK_abi_test_trampoline +abi_test_unwind_return LK_abi_test_unwind_return +abi_test_unwind_start LK_abi_test_unwind_start +abi_test_unwind_stop LK_abi_test_unwind_stop +aes128gcmsiv_aes_ks LK_aes128gcmsiv_aes_ks +aes128gcmsiv_aes_ks_enc_x1 LK_aes128gcmsiv_aes_ks_enc_x1 +aes128gcmsiv_dec LK_aes128gcmsiv_dec +aes128gcmsiv_ecb_enc_block LK_aes128gcmsiv_ecb_enc_block +aes128gcmsiv_enc_msg_x4 LK_aes128gcmsiv_enc_msg_x4 +aes128gcmsiv_enc_msg_x8 LK_aes128gcmsiv_enc_msg_x8 +aes128gcmsiv_kdf LK_aes128gcmsiv_kdf +aes256gcmsiv_aes_ks LK_aes256gcmsiv_aes_ks +aes256gcmsiv_aes_ks_enc_x1 LK_aes256gcmsiv_aes_ks_enc_x1 +aes256gcmsiv_dec LK_aes256gcmsiv_dec +aes256gcmsiv_ecb_enc_block LK_aes256gcmsiv_ecb_enc_block +aes256gcmsiv_enc_msg_x4 LK_aes256gcmsiv_enc_msg_x4 +aes256gcmsiv_enc_msg_x8 LK_aes256gcmsiv_enc_msg_x8 +aes256gcmsiv_kdf LK_aes256gcmsiv_kdf +aes_ctr_set_key LK_aes_ctr_set_key +aes_hw_cbc_encrypt LK_aes_hw_cbc_encrypt +aes_hw_ctr32_encrypt_blocks LK_aes_hw_ctr32_encrypt_blocks +aes_hw_decrypt LK_aes_hw_decrypt +aes_hw_ecb_encrypt LK_aes_hw_ecb_encrypt +aes_hw_encrypt LK_aes_hw_encrypt +aes_hw_set_decrypt_key LK_aes_hw_set_decrypt_key +aes_hw_set_encrypt_key LK_aes_hw_set_encrypt_key +aes_nohw_cbc_encrypt LK_aes_nohw_cbc_encrypt +aes_nohw_ctr32_encrypt_blocks LK_aes_nohw_ctr32_encrypt_blocks +aes_nohw_decrypt LK_aes_nohw_decrypt +aes_nohw_encrypt LK_aes_nohw_encrypt +aes_nohw_set_decrypt_key LK_aes_nohw_set_decrypt_key +aes_nohw_set_encrypt_key LK_aes_nohw_set_encrypt_key +aesgcmsiv_htable6_init LK_aesgcmsiv_htable6_init +aesgcmsiv_htable_init LK_aesgcmsiv_htable_init +aesgcmsiv_htable_polyval LK_aesgcmsiv_htable_polyval +aesgcmsiv_polyval_horner LK_aesgcmsiv_polyval_horner +aesni_gcm_decrypt LK_aesni_gcm_decrypt +aesni_gcm_encrypt LK_aesni_gcm_encrypt +asn1_bit_string_length LK_asn1_bit_string_length +asn1_do_adb LK_asn1_do_adb +asn1_enc_free LK_asn1_enc_free +asn1_enc_init LK_asn1_enc_init +asn1_enc_restore LK_asn1_enc_restore +asn1_enc_save LK_asn1_enc_save +asn1_encoding_clear LK_asn1_encoding_clear +asn1_generalizedtime_to_tm LK_asn1_generalizedtime_to_tm +asn1_get_choice_selector LK_asn1_get_choice_selector +asn1_get_field_ptr LK_asn1_get_field_ptr +asn1_get_string_table_for_testing LK_asn1_get_string_table_for_testing +asn1_is_printable LK_asn1_is_printable +asn1_refcount_dec_and_test_zero LK_asn1_refcount_dec_and_test_zero +asn1_refcount_set_one LK_asn1_refcount_set_one +asn1_set_choice_selector LK_asn1_set_choice_selector +asn1_type_cleanup LK_asn1_type_cleanup +asn1_type_set0_string LK_asn1_type_set0_string +asn1_type_value_as_pointer LK_asn1_type_value_as_pointer +asn1_utctime_to_tm LK_asn1_utctime_to_tm +beeu_mod_inverse_vartime LK_beeu_mod_inverse_vartime +bio_clear_socket_error LK_bio_clear_socket_error +bio_errno_should_retry LK_bio_errno_should_retry +bio_ip_and_port_to_socket_and_addr LK_bio_ip_and_port_to_socket_and_addr +bio_sock_error LK_bio_sock_error +bio_socket_nbio LK_bio_socket_nbio +bio_socket_should_retry LK_bio_socket_should_retry +bn_abs_sub_consttime LK_bn_abs_sub_consttime +bn_add_words LK_bn_add_words +bn_assert_fits_in_bytes LK_bn_assert_fits_in_bytes +bn_big_endian_to_words LK_bn_big_endian_to_words +bn_copy_words LK_bn_copy_words +bn_div_consttime LK_bn_div_consttime +bn_expand LK_bn_expand +bn_fits_in_words LK_bn_fits_in_words +bn_from_montgomery_small LK_bn_from_montgomery_small +bn_gather5 LK_bn_gather5 +bn_in_range_words LK_bn_in_range_words +bn_is_bit_set_words LK_bn_is_bit_set_words +bn_is_relatively_prime LK_bn_is_relatively_prime +bn_jacobi LK_bn_jacobi +bn_lcm_consttime LK_bn_lcm_consttime +bn_less_than_montgomery_R LK_bn_less_than_montgomery_R +bn_less_than_words LK_bn_less_than_words +bn_miller_rabin_init LK_bn_miller_rabin_init +bn_miller_rabin_iteration LK_bn_miller_rabin_iteration +bn_minimal_width LK_bn_minimal_width +bn_mod_add_consttime LK_bn_mod_add_consttime +bn_mod_add_words LK_bn_mod_add_words +bn_mod_exp_mont_small LK_bn_mod_exp_mont_small +bn_mod_inverse0_prime_mont_small LK_bn_mod_inverse0_prime_mont_small +bn_mod_inverse_consttime LK_bn_mod_inverse_consttime +bn_mod_inverse_prime LK_bn_mod_inverse_prime +bn_mod_inverse_secret_prime LK_bn_mod_inverse_secret_prime +bn_mod_lshift1_consttime LK_bn_mod_lshift1_consttime +bn_mod_lshift_consttime LK_bn_mod_lshift_consttime +bn_mod_mul_montgomery_small LK_bn_mod_mul_montgomery_small +bn_mod_sub_consttime LK_bn_mod_sub_consttime +bn_mod_sub_words LK_bn_mod_sub_words +bn_mod_u16_consttime LK_bn_mod_u16_consttime +bn_mont_ctx_cleanup LK_bn_mont_ctx_cleanup +bn_mont_ctx_init LK_bn_mont_ctx_init +bn_mont_ctx_set_RR_consttime LK_bn_mont_ctx_set_RR_consttime +bn_mont_n0 LK_bn_mont_n0 +bn_mul4x_mont LK_bn_mul4x_mont +bn_mul_add_words LK_bn_mul_add_words +bn_mul_comba4 LK_bn_mul_comba4 +bn_mul_comba8 LK_bn_mul_comba8 +bn_mul_consttime LK_bn_mul_consttime +bn_mul_mont LK_bn_mul_mont +bn_mul_mont_gather5 LK_bn_mul_mont_gather5 +bn_mul_mont_nohw LK_bn_mul_mont_nohw +bn_mul_small LK_bn_mul_small +bn_mul_words LK_bn_mul_words +bn_mulx4x_mont LK_bn_mulx4x_mont +bn_odd_number_is_obviously_composite LK_bn_odd_number_is_obviously_composite +bn_one_to_montgomery LK_bn_one_to_montgomery +bn_power5 LK_bn_power5 +bn_rand_range_words LK_bn_rand_range_words +bn_rand_secret_range LK_bn_rand_secret_range +bn_reduce_once LK_bn_reduce_once +bn_reduce_once_in_place LK_bn_reduce_once_in_place +bn_resize_words LK_bn_resize_words +bn_rshift1_words LK_bn_rshift1_words +bn_rshift_secret_shift LK_bn_rshift_secret_shift +bn_rshift_words LK_bn_rshift_words +bn_scatter5 LK_bn_scatter5 +bn_select_words LK_bn_select_words +bn_set_minimal_width LK_bn_set_minimal_width +bn_set_static_words LK_bn_set_static_words +bn_set_words LK_bn_set_words +bn_sqr8x_internal LK_bn_sqr8x_internal +bn_sqr8x_mont LK_bn_sqr8x_mont +bn_sqr_comba4 LK_bn_sqr_comba4 +bn_sqr_comba8 LK_bn_sqr_comba8 +bn_sqr_consttime LK_bn_sqr_consttime +bn_sqr_small LK_bn_sqr_small +bn_sqr_words LK_bn_sqr_words +bn_sqrx8x_internal LK_bn_sqrx8x_internal +bn_sub_words LK_bn_sub_words +bn_to_montgomery_small LK_bn_to_montgomery_small +bn_uadd_consttime LK_bn_uadd_consttime +bn_usub_consttime LK_bn_usub_consttime +bn_wexpand LK_bn_wexpand +bn_words_to_big_endian LK_bn_words_to_big_endian +boringssl_self_test_hmac_sha256 LK_boringssl_self_test_hmac_sha256 +boringssl_self_test_sha256 LK_boringssl_self_test_sha256 +boringssl_self_test_sha512 LK_boringssl_self_test_sha512 +c2i_ASN1_BIT_STRING LK_c2i_ASN1_BIT_STRING +c2i_ASN1_INTEGER LK_c2i_ASN1_INTEGER +c2i_ASN1_OBJECT LK_c2i_ASN1_OBJECT +chacha20_poly1305_open LK_chacha20_poly1305_open +chacha20_poly1305_seal LK_chacha20_poly1305_seal +crypto_gcm_clmul_enabled LK_crypto_gcm_clmul_enabled +d2i_ASN1_BIT_STRING LK_d2i_ASN1_BIT_STRING +d2i_ASN1_BMPSTRING LK_d2i_ASN1_BMPSTRING +d2i_ASN1_BOOLEAN LK_d2i_ASN1_BOOLEAN +d2i_ASN1_ENUMERATED LK_d2i_ASN1_ENUMERATED +d2i_ASN1_GENERALIZEDTIME LK_d2i_ASN1_GENERALIZEDTIME +d2i_ASN1_GENERALSTRING LK_d2i_ASN1_GENERALSTRING +d2i_ASN1_IA5STRING LK_d2i_ASN1_IA5STRING +d2i_ASN1_INTEGER LK_d2i_ASN1_INTEGER +d2i_ASN1_NULL LK_d2i_ASN1_NULL +d2i_ASN1_OBJECT LK_d2i_ASN1_OBJECT +d2i_ASN1_OCTET_STRING LK_d2i_ASN1_OCTET_STRING +d2i_ASN1_PRINTABLE LK_d2i_ASN1_PRINTABLE +d2i_ASN1_PRINTABLESTRING LK_d2i_ASN1_PRINTABLESTRING +d2i_ASN1_SEQUENCE_ANY LK_d2i_ASN1_SEQUENCE_ANY +d2i_ASN1_SET_ANY LK_d2i_ASN1_SET_ANY +d2i_ASN1_T61STRING LK_d2i_ASN1_T61STRING +d2i_ASN1_TIME LK_d2i_ASN1_TIME +d2i_ASN1_TYPE LK_d2i_ASN1_TYPE +d2i_ASN1_UNIVERSALSTRING LK_d2i_ASN1_UNIVERSALSTRING +d2i_ASN1_UTCTIME LK_d2i_ASN1_UTCTIME +d2i_ASN1_UTF8STRING LK_d2i_ASN1_UTF8STRING +d2i_ASN1_VISIBLESTRING LK_d2i_ASN1_VISIBLESTRING +d2i_AUTHORITY_INFO_ACCESS LK_d2i_AUTHORITY_INFO_ACCESS +d2i_AUTHORITY_KEYID LK_d2i_AUTHORITY_KEYID +d2i_AutoPrivateKey LK_d2i_AutoPrivateKey +d2i_BASIC_CONSTRAINTS LK_d2i_BASIC_CONSTRAINTS +d2i_CERTIFICATEPOLICIES LK_d2i_CERTIFICATEPOLICIES +d2i_CRL_DIST_POINTS LK_d2i_CRL_DIST_POINTS +d2i_DHparams LK_d2i_DHparams +d2i_DHparams_bio LK_d2i_DHparams_bio +d2i_DIRECTORYSTRING LK_d2i_DIRECTORYSTRING +d2i_DISPLAYTEXT LK_d2i_DISPLAYTEXT +d2i_DSAPrivateKey LK_d2i_DSAPrivateKey +d2i_DSAPrivateKey_bio LK_d2i_DSAPrivateKey_bio +d2i_DSAPrivateKey_fp LK_d2i_DSAPrivateKey_fp +d2i_DSAPublicKey LK_d2i_DSAPublicKey +d2i_DSA_PUBKEY LK_d2i_DSA_PUBKEY +d2i_DSA_PUBKEY_bio LK_d2i_DSA_PUBKEY_bio +d2i_DSA_PUBKEY_fp LK_d2i_DSA_PUBKEY_fp +d2i_DSA_SIG LK_d2i_DSA_SIG +d2i_DSAparams LK_d2i_DSAparams +d2i_ECDSA_SIG LK_d2i_ECDSA_SIG +d2i_ECParameters LK_d2i_ECParameters +d2i_ECPrivateKey LK_d2i_ECPrivateKey +d2i_ECPrivateKey_bio LK_d2i_ECPrivateKey_bio +d2i_ECPrivateKey_fp LK_d2i_ECPrivateKey_fp +d2i_EC_PUBKEY LK_d2i_EC_PUBKEY +d2i_EC_PUBKEY_bio LK_d2i_EC_PUBKEY_bio +d2i_EC_PUBKEY_fp LK_d2i_EC_PUBKEY_fp +d2i_EXTENDED_KEY_USAGE LK_d2i_EXTENDED_KEY_USAGE +d2i_GENERAL_NAME LK_d2i_GENERAL_NAME +d2i_GENERAL_NAMES LK_d2i_GENERAL_NAMES +d2i_ISSUING_DIST_POINT LK_d2i_ISSUING_DIST_POINT +d2i_NETSCAPE_SPKAC LK_d2i_NETSCAPE_SPKAC +d2i_NETSCAPE_SPKI LK_d2i_NETSCAPE_SPKI +d2i_PKCS12 LK_d2i_PKCS12 +d2i_PKCS12_bio LK_d2i_PKCS12_bio +d2i_PKCS12_fp LK_d2i_PKCS12_fp +d2i_PKCS7 LK_d2i_PKCS7 +d2i_PKCS7_bio LK_d2i_PKCS7_bio +d2i_PKCS8PrivateKey_bio LK_d2i_PKCS8PrivateKey_bio +d2i_PKCS8PrivateKey_fp LK_d2i_PKCS8PrivateKey_fp +d2i_PKCS8_PRIV_KEY_INFO LK_d2i_PKCS8_PRIV_KEY_INFO +d2i_PKCS8_PRIV_KEY_INFO_bio LK_d2i_PKCS8_PRIV_KEY_INFO_bio +d2i_PKCS8_PRIV_KEY_INFO_fp LK_d2i_PKCS8_PRIV_KEY_INFO_fp +d2i_PKCS8_bio LK_d2i_PKCS8_bio +d2i_PKCS8_fp LK_d2i_PKCS8_fp +d2i_PUBKEY LK_d2i_PUBKEY +d2i_PUBKEY_bio LK_d2i_PUBKEY_bio +d2i_PUBKEY_fp LK_d2i_PUBKEY_fp +d2i_PrivateKey LK_d2i_PrivateKey +d2i_PrivateKey_bio LK_d2i_PrivateKey_bio +d2i_PrivateKey_fp LK_d2i_PrivateKey_fp +d2i_PublicKey LK_d2i_PublicKey +d2i_RSAPrivateKey LK_d2i_RSAPrivateKey +d2i_RSAPrivateKey_bio LK_d2i_RSAPrivateKey_bio +d2i_RSAPrivateKey_fp LK_d2i_RSAPrivateKey_fp +d2i_RSAPublicKey LK_d2i_RSAPublicKey +d2i_RSAPublicKey_bio LK_d2i_RSAPublicKey_bio +d2i_RSAPublicKey_fp LK_d2i_RSAPublicKey_fp +d2i_RSA_PSS_PARAMS LK_d2i_RSA_PSS_PARAMS +d2i_RSA_PUBKEY LK_d2i_RSA_PUBKEY +d2i_RSA_PUBKEY_bio LK_d2i_RSA_PUBKEY_bio +d2i_RSA_PUBKEY_fp LK_d2i_RSA_PUBKEY_fp +d2i_X509 LK_d2i_X509 +d2i_X509_ALGOR LK_d2i_X509_ALGOR +d2i_X509_ATTRIBUTE LK_d2i_X509_ATTRIBUTE +d2i_X509_AUX LK_d2i_X509_AUX +d2i_X509_CERT_AUX LK_d2i_X509_CERT_AUX +d2i_X509_CINF LK_d2i_X509_CINF +d2i_X509_CRL LK_d2i_X509_CRL +d2i_X509_CRL_INFO LK_d2i_X509_CRL_INFO +d2i_X509_CRL_bio LK_d2i_X509_CRL_bio +d2i_X509_CRL_fp LK_d2i_X509_CRL_fp +d2i_X509_EXTENSION LK_d2i_X509_EXTENSION +d2i_X509_EXTENSIONS LK_d2i_X509_EXTENSIONS +d2i_X509_NAME LK_d2i_X509_NAME +d2i_X509_PUBKEY LK_d2i_X509_PUBKEY +d2i_X509_REQ LK_d2i_X509_REQ +d2i_X509_REQ_INFO LK_d2i_X509_REQ_INFO +d2i_X509_REQ_bio LK_d2i_X509_REQ_bio +d2i_X509_REQ_fp LK_d2i_X509_REQ_fp +d2i_X509_REVOKED LK_d2i_X509_REVOKED +d2i_X509_SIG LK_d2i_X509_SIG +d2i_X509_VAL LK_d2i_X509_VAL +d2i_X509_bio LK_d2i_X509_bio +d2i_X509_fp LK_d2i_X509_fp +dh_asn1_meth LK_dh_asn1_meth +dh_check_params_fast LK_dh_check_params_fast +dh_compute_key_padded_no_self_test LK_dh_compute_key_padded_no_self_test +dh_pkey_meth LK_dh_pkey_meth +dsa_asn1_meth LK_dsa_asn1_meth +dsa_check_key LK_dsa_check_key +ec_GFp_mont_add LK_ec_GFp_mont_add +ec_GFp_mont_dbl LK_ec_GFp_mont_dbl +ec_GFp_mont_felem_exp LK_ec_GFp_mont_felem_exp +ec_GFp_mont_felem_from_bytes LK_ec_GFp_mont_felem_from_bytes +ec_GFp_mont_felem_mul LK_ec_GFp_mont_felem_mul +ec_GFp_mont_felem_reduce LK_ec_GFp_mont_felem_reduce +ec_GFp_mont_felem_sqr LK_ec_GFp_mont_felem_sqr +ec_GFp_mont_felem_to_bytes LK_ec_GFp_mont_felem_to_bytes +ec_GFp_mont_init_precomp LK_ec_GFp_mont_init_precomp +ec_GFp_mont_mul LK_ec_GFp_mont_mul +ec_GFp_mont_mul_base LK_ec_GFp_mont_mul_base +ec_GFp_mont_mul_batch LK_ec_GFp_mont_mul_batch +ec_GFp_mont_mul_precomp LK_ec_GFp_mont_mul_precomp +ec_GFp_mont_mul_public_batch LK_ec_GFp_mont_mul_public_batch +ec_GFp_nistp_recode_scalar_bits LK_ec_GFp_nistp_recode_scalar_bits +ec_GFp_simple_cmp_x_coordinate LK_ec_GFp_simple_cmp_x_coordinate +ec_GFp_simple_felem_from_bytes LK_ec_GFp_simple_felem_from_bytes +ec_GFp_simple_felem_to_bytes LK_ec_GFp_simple_felem_to_bytes +ec_GFp_simple_group_get_curve LK_ec_GFp_simple_group_get_curve +ec_GFp_simple_group_set_curve LK_ec_GFp_simple_group_set_curve +ec_GFp_simple_invert LK_ec_GFp_simple_invert +ec_GFp_simple_is_at_infinity LK_ec_GFp_simple_is_at_infinity +ec_GFp_simple_is_on_curve LK_ec_GFp_simple_is_on_curve +ec_GFp_simple_point_copy LK_ec_GFp_simple_point_copy +ec_GFp_simple_point_init LK_ec_GFp_simple_point_init +ec_GFp_simple_point_set_to_infinity LK_ec_GFp_simple_point_set_to_infinity +ec_GFp_simple_points_equal LK_ec_GFp_simple_points_equal +ec_affine_jacobian_equal LK_ec_affine_jacobian_equal +ec_affine_select LK_ec_affine_select +ec_affine_to_jacobian LK_ec_affine_to_jacobian +ec_asn1_meth LK_ec_asn1_meth +ec_bignum_to_felem LK_ec_bignum_to_felem +ec_bignum_to_scalar LK_ec_bignum_to_scalar +ec_cmp_x_coordinate LK_ec_cmp_x_coordinate +ec_compute_wNAF LK_ec_compute_wNAF +ec_felem_add LK_ec_felem_add +ec_felem_equal LK_ec_felem_equal +ec_felem_from_bytes LK_ec_felem_from_bytes +ec_felem_neg LK_ec_felem_neg +ec_felem_non_zero_mask LK_ec_felem_non_zero_mask +ec_felem_one LK_ec_felem_one +ec_felem_select LK_ec_felem_select +ec_felem_sub LK_ec_felem_sub +ec_felem_to_bignum LK_ec_felem_to_bignum +ec_felem_to_bytes LK_ec_felem_to_bytes +ec_get_x_coordinate_as_bytes LK_ec_get_x_coordinate_as_bytes +ec_get_x_coordinate_as_scalar LK_ec_get_x_coordinate_as_scalar +ec_hash_to_curve_p256_xmd_sha256_sswu LK_ec_hash_to_curve_p256_xmd_sha256_sswu +ec_hash_to_curve_p384_xmd_sha384_sswu LK_ec_hash_to_curve_p384_xmd_sha384_sswu +ec_hash_to_curve_p384_xmd_sha512_sswu_draft07 LK_ec_hash_to_curve_p384_xmd_sha512_sswu_draft07 +ec_hash_to_scalar_p384_xmd_sha384 LK_ec_hash_to_scalar_p384_xmd_sha384 +ec_hash_to_scalar_p384_xmd_sha512_draft07 LK_ec_hash_to_scalar_p384_xmd_sha512_draft07 +ec_init_precomp LK_ec_init_precomp +ec_jacobian_to_affine LK_ec_jacobian_to_affine +ec_jacobian_to_affine_batch LK_ec_jacobian_to_affine_batch +ec_pkey_meth LK_ec_pkey_meth +ec_point_byte_len LK_ec_point_byte_len +ec_point_from_uncompressed LK_ec_point_from_uncompressed +ec_point_mul_no_self_test LK_ec_point_mul_no_self_test +ec_point_mul_scalar LK_ec_point_mul_scalar +ec_point_mul_scalar_base LK_ec_point_mul_scalar_base +ec_point_mul_scalar_batch LK_ec_point_mul_scalar_batch +ec_point_mul_scalar_precomp LK_ec_point_mul_scalar_precomp +ec_point_mul_scalar_public LK_ec_point_mul_scalar_public +ec_point_mul_scalar_public_batch LK_ec_point_mul_scalar_public_batch +ec_point_select LK_ec_point_select +ec_point_set_affine_coordinates LK_ec_point_set_affine_coordinates +ec_point_to_bytes LK_ec_point_to_bytes +ec_precomp_select LK_ec_precomp_select +ec_random_nonzero_scalar LK_ec_random_nonzero_scalar +ec_scalar_add LK_ec_scalar_add +ec_scalar_equal_vartime LK_ec_scalar_equal_vartime +ec_scalar_from_bytes LK_ec_scalar_from_bytes +ec_scalar_from_montgomery LK_ec_scalar_from_montgomery +ec_scalar_inv0_montgomery LK_ec_scalar_inv0_montgomery +ec_scalar_is_zero LK_ec_scalar_is_zero +ec_scalar_mul_montgomery LK_ec_scalar_mul_montgomery +ec_scalar_neg LK_ec_scalar_neg +ec_scalar_reduce LK_ec_scalar_reduce +ec_scalar_select LK_ec_scalar_select +ec_scalar_sub LK_ec_scalar_sub +ec_scalar_to_bytes LK_ec_scalar_to_bytes +ec_scalar_to_montgomery LK_ec_scalar_to_montgomery +ec_scalar_to_montgomery_inv_vartime LK_ec_scalar_to_montgomery_inv_vartime +ec_set_to_safe_point LK_ec_set_to_safe_point +ec_simple_scalar_inv0_montgomery LK_ec_simple_scalar_inv0_montgomery +ec_simple_scalar_to_montgomery_inv_vartime LK_ec_simple_scalar_to_montgomery_inv_vartime +ecdsa_do_verify_no_self_test LK_ecdsa_do_verify_no_self_test +ecdsa_sign_with_nonce_for_known_answer_test LK_ecdsa_sign_with_nonce_for_known_answer_test +ecp_nistz256_avx2_select_w7 LK_ecp_nistz256_avx2_select_w7 +ecp_nistz256_mul_mont LK_ecp_nistz256_mul_mont +ecp_nistz256_neg LK_ecp_nistz256_neg +ecp_nistz256_ord_mul_mont LK_ecp_nistz256_ord_mul_mont +ecp_nistz256_ord_sqr_mont LK_ecp_nistz256_ord_sqr_mont +ecp_nistz256_point_add LK_ecp_nistz256_point_add +ecp_nistz256_point_add_affine LK_ecp_nistz256_point_add_affine +ecp_nistz256_point_double LK_ecp_nistz256_point_double +ecp_nistz256_select_w5 LK_ecp_nistz256_select_w5 +ecp_nistz256_select_w7 LK_ecp_nistz256_select_w7 +ecp_nistz256_sqr_mont LK_ecp_nistz256_sqr_mont +ed25519_asn1_meth LK_ed25519_asn1_meth +ed25519_pkey_meth LK_ed25519_pkey_meth +evp_pkey_set_method LK_evp_pkey_set_method +fiat_curve25519_adx_mul LK_fiat_curve25519_adx_mul +fiat_curve25519_adx_square LK_fiat_curve25519_adx_square +fiat_p256_adx_mul LK_fiat_p256_adx_mul +fiat_p256_adx_sqr LK_fiat_p256_adx_sqr +gcm_ghash_avx LK_gcm_ghash_avx +gcm_ghash_clmul LK_gcm_ghash_clmul +gcm_ghash_nohw LK_gcm_ghash_nohw +gcm_ghash_ssse3 LK_gcm_ghash_ssse3 +gcm_gmult_avx LK_gcm_gmult_avx +gcm_gmult_clmul LK_gcm_gmult_clmul +gcm_gmult_nohw LK_gcm_gmult_nohw +gcm_gmult_ssse3 LK_gcm_gmult_ssse3 +gcm_init_avx LK_gcm_init_avx +gcm_init_clmul LK_gcm_init_clmul +gcm_init_nohw LK_gcm_init_nohw +gcm_init_ssse3 LK_gcm_init_ssse3 +hkdf_pkey_meth LK_hkdf_pkey_meth +i2a_ASN1_ENUMERATED LK_i2a_ASN1_ENUMERATED +i2a_ASN1_INTEGER LK_i2a_ASN1_INTEGER +i2a_ASN1_OBJECT LK_i2a_ASN1_OBJECT +i2a_ASN1_STRING LK_i2a_ASN1_STRING +i2c_ASN1_BIT_STRING LK_i2c_ASN1_BIT_STRING +i2c_ASN1_INTEGER LK_i2c_ASN1_INTEGER +i2d_ASN1_BIT_STRING LK_i2d_ASN1_BIT_STRING +i2d_ASN1_BMPSTRING LK_i2d_ASN1_BMPSTRING +i2d_ASN1_BOOLEAN LK_i2d_ASN1_BOOLEAN +i2d_ASN1_ENUMERATED LK_i2d_ASN1_ENUMERATED +i2d_ASN1_GENERALIZEDTIME LK_i2d_ASN1_GENERALIZEDTIME +i2d_ASN1_GENERALSTRING LK_i2d_ASN1_GENERALSTRING +i2d_ASN1_IA5STRING LK_i2d_ASN1_IA5STRING +i2d_ASN1_INTEGER LK_i2d_ASN1_INTEGER +i2d_ASN1_NULL LK_i2d_ASN1_NULL +i2d_ASN1_OBJECT LK_i2d_ASN1_OBJECT +i2d_ASN1_OCTET_STRING LK_i2d_ASN1_OCTET_STRING +i2d_ASN1_PRINTABLE LK_i2d_ASN1_PRINTABLE +i2d_ASN1_PRINTABLESTRING LK_i2d_ASN1_PRINTABLESTRING +i2d_ASN1_SEQUENCE_ANY LK_i2d_ASN1_SEQUENCE_ANY +i2d_ASN1_SET_ANY LK_i2d_ASN1_SET_ANY +i2d_ASN1_T61STRING LK_i2d_ASN1_T61STRING +i2d_ASN1_TIME LK_i2d_ASN1_TIME +i2d_ASN1_TYPE LK_i2d_ASN1_TYPE +i2d_ASN1_UNIVERSALSTRING LK_i2d_ASN1_UNIVERSALSTRING +i2d_ASN1_UTCTIME LK_i2d_ASN1_UTCTIME +i2d_ASN1_UTF8STRING LK_i2d_ASN1_UTF8STRING +i2d_ASN1_VISIBLESTRING LK_i2d_ASN1_VISIBLESTRING +i2d_AUTHORITY_INFO_ACCESS LK_i2d_AUTHORITY_INFO_ACCESS +i2d_AUTHORITY_KEYID LK_i2d_AUTHORITY_KEYID +i2d_BASIC_CONSTRAINTS LK_i2d_BASIC_CONSTRAINTS +i2d_CERTIFICATEPOLICIES LK_i2d_CERTIFICATEPOLICIES +i2d_CRL_DIST_POINTS LK_i2d_CRL_DIST_POINTS +i2d_DHparams LK_i2d_DHparams +i2d_DHparams_bio LK_i2d_DHparams_bio +i2d_DIRECTORYSTRING LK_i2d_DIRECTORYSTRING +i2d_DISPLAYTEXT LK_i2d_DISPLAYTEXT +i2d_DSAPrivateKey LK_i2d_DSAPrivateKey +i2d_DSAPrivateKey_bio LK_i2d_DSAPrivateKey_bio +i2d_DSAPrivateKey_fp LK_i2d_DSAPrivateKey_fp +i2d_DSAPublicKey LK_i2d_DSAPublicKey +i2d_DSA_PUBKEY LK_i2d_DSA_PUBKEY +i2d_DSA_PUBKEY_bio LK_i2d_DSA_PUBKEY_bio +i2d_DSA_PUBKEY_fp LK_i2d_DSA_PUBKEY_fp +i2d_DSA_SIG LK_i2d_DSA_SIG +i2d_DSAparams LK_i2d_DSAparams +i2d_ECDSA_SIG LK_i2d_ECDSA_SIG +i2d_ECParameters LK_i2d_ECParameters +i2d_ECPrivateKey LK_i2d_ECPrivateKey +i2d_ECPrivateKey_bio LK_i2d_ECPrivateKey_bio +i2d_ECPrivateKey_fp LK_i2d_ECPrivateKey_fp +i2d_EC_PUBKEY LK_i2d_EC_PUBKEY +i2d_EC_PUBKEY_bio LK_i2d_EC_PUBKEY_bio +i2d_EC_PUBKEY_fp LK_i2d_EC_PUBKEY_fp +i2d_EXTENDED_KEY_USAGE LK_i2d_EXTENDED_KEY_USAGE +i2d_GENERAL_NAME LK_i2d_GENERAL_NAME +i2d_GENERAL_NAMES LK_i2d_GENERAL_NAMES +i2d_ISSUING_DIST_POINT LK_i2d_ISSUING_DIST_POINT +i2d_NETSCAPE_SPKAC LK_i2d_NETSCAPE_SPKAC +i2d_NETSCAPE_SPKI LK_i2d_NETSCAPE_SPKI +i2d_PKCS12 LK_i2d_PKCS12 +i2d_PKCS12_bio LK_i2d_PKCS12_bio +i2d_PKCS12_fp LK_i2d_PKCS12_fp +i2d_PKCS7 LK_i2d_PKCS7 +i2d_PKCS7_bio LK_i2d_PKCS7_bio +i2d_PKCS8PrivateKeyInfo_bio LK_i2d_PKCS8PrivateKeyInfo_bio +i2d_PKCS8PrivateKeyInfo_fp LK_i2d_PKCS8PrivateKeyInfo_fp +i2d_PKCS8PrivateKey_bio LK_i2d_PKCS8PrivateKey_bio +i2d_PKCS8PrivateKey_fp LK_i2d_PKCS8PrivateKey_fp +i2d_PKCS8PrivateKey_nid_bio LK_i2d_PKCS8PrivateKey_nid_bio +i2d_PKCS8PrivateKey_nid_fp LK_i2d_PKCS8PrivateKey_nid_fp +i2d_PKCS8_PRIV_KEY_INFO LK_i2d_PKCS8_PRIV_KEY_INFO +i2d_PKCS8_PRIV_KEY_INFO_bio LK_i2d_PKCS8_PRIV_KEY_INFO_bio +i2d_PKCS8_PRIV_KEY_INFO_fp LK_i2d_PKCS8_PRIV_KEY_INFO_fp +i2d_PKCS8_bio LK_i2d_PKCS8_bio +i2d_PKCS8_fp LK_i2d_PKCS8_fp +i2d_PUBKEY LK_i2d_PUBKEY +i2d_PUBKEY_bio LK_i2d_PUBKEY_bio +i2d_PUBKEY_fp LK_i2d_PUBKEY_fp +i2d_PrivateKey LK_i2d_PrivateKey +i2d_PrivateKey_bio LK_i2d_PrivateKey_bio +i2d_PrivateKey_fp LK_i2d_PrivateKey_fp +i2d_PublicKey LK_i2d_PublicKey +i2d_RSAPrivateKey LK_i2d_RSAPrivateKey +i2d_RSAPrivateKey_bio LK_i2d_RSAPrivateKey_bio +i2d_RSAPrivateKey_fp LK_i2d_RSAPrivateKey_fp +i2d_RSAPublicKey LK_i2d_RSAPublicKey +i2d_RSAPublicKey_bio LK_i2d_RSAPublicKey_bio +i2d_RSAPublicKey_fp LK_i2d_RSAPublicKey_fp +i2d_RSA_PSS_PARAMS LK_i2d_RSA_PSS_PARAMS +i2d_RSA_PUBKEY LK_i2d_RSA_PUBKEY +i2d_RSA_PUBKEY_bio LK_i2d_RSA_PUBKEY_bio +i2d_RSA_PUBKEY_fp LK_i2d_RSA_PUBKEY_fp +i2d_X509 LK_i2d_X509 +i2d_X509_ALGOR LK_i2d_X509_ALGOR +i2d_X509_ATTRIBUTE LK_i2d_X509_ATTRIBUTE +i2d_X509_AUX LK_i2d_X509_AUX +i2d_X509_CERT_AUX LK_i2d_X509_CERT_AUX +i2d_X509_CINF LK_i2d_X509_CINF +i2d_X509_CRL LK_i2d_X509_CRL +i2d_X509_CRL_INFO LK_i2d_X509_CRL_INFO +i2d_X509_CRL_bio LK_i2d_X509_CRL_bio +i2d_X509_CRL_fp LK_i2d_X509_CRL_fp +i2d_X509_CRL_tbs LK_i2d_X509_CRL_tbs +i2d_X509_EXTENSION LK_i2d_X509_EXTENSION +i2d_X509_EXTENSIONS LK_i2d_X509_EXTENSIONS +i2d_X509_NAME LK_i2d_X509_NAME +i2d_X509_PUBKEY LK_i2d_X509_PUBKEY +i2d_X509_REQ LK_i2d_X509_REQ +i2d_X509_REQ_INFO LK_i2d_X509_REQ_INFO +i2d_X509_REQ_bio LK_i2d_X509_REQ_bio +i2d_X509_REQ_fp LK_i2d_X509_REQ_fp +i2d_X509_REVOKED LK_i2d_X509_REVOKED +i2d_X509_SIG LK_i2d_X509_SIG +i2d_X509_VAL LK_i2d_X509_VAL +i2d_X509_bio LK_i2d_X509_bio +i2d_X509_fp LK_i2d_X509_fp +i2d_X509_tbs LK_i2d_X509_tbs +i2d_re_X509_CRL_tbs LK_i2d_re_X509_CRL_tbs +i2d_re_X509_REQ_tbs LK_i2d_re_X509_REQ_tbs +i2d_re_X509_tbs LK_i2d_re_X509_tbs +i2o_ECPublicKey LK_i2o_ECPublicKey +i2s_ASN1_ENUMERATED LK_i2s_ASN1_ENUMERATED +i2s_ASN1_INTEGER LK_i2s_ASN1_INTEGER +i2s_ASN1_OCTET_STRING LK_i2s_ASN1_OCTET_STRING +i2t_ASN1_OBJECT LK_i2t_ASN1_OBJECT +i2v_GENERAL_NAME LK_i2v_GENERAL_NAME +i2v_GENERAL_NAMES LK_i2v_GENERAL_NAMES +k25519Precomp LK_k25519Precomp +kBoringSSLRSASqrtTwo LK_kBoringSSLRSASqrtTwo +kBoringSSLRSASqrtTwoLen LK_kBoringSSLRSASqrtTwoLen +kOpenSSLReasonStringData LK_kOpenSSLReasonStringData +kOpenSSLReasonValues LK_kOpenSSLReasonValues +kOpenSSLReasonValuesLen LK_kOpenSSLReasonValuesLen +md4_block_data_order LK_md4_block_data_order +md5_block_asm_data_order LK_md5_block_asm_data_order +o2i_ECPublicKey LK_o2i_ECPublicKey +pkcs12_iterations_acceptable LK_pkcs12_iterations_acceptable +pkcs12_key_gen LK_pkcs12_key_gen +pkcs12_pbe_encrypt_init LK_pkcs12_pbe_encrypt_init +pkcs7_add_signed_data LK_pkcs7_add_signed_data +pkcs7_parse_header LK_pkcs7_parse_header +pkcs8_pbe_decrypt LK_pkcs8_pbe_decrypt +pmbtoken_exp1_blind LK_pmbtoken_exp1_blind +pmbtoken_exp1_client_key_from_bytes LK_pmbtoken_exp1_client_key_from_bytes +pmbtoken_exp1_derive_key_from_secret LK_pmbtoken_exp1_derive_key_from_secret +pmbtoken_exp1_generate_key LK_pmbtoken_exp1_generate_key +pmbtoken_exp1_get_h_for_testing LK_pmbtoken_exp1_get_h_for_testing +pmbtoken_exp1_issuer_key_from_bytes LK_pmbtoken_exp1_issuer_key_from_bytes +pmbtoken_exp1_read LK_pmbtoken_exp1_read +pmbtoken_exp1_sign LK_pmbtoken_exp1_sign +pmbtoken_exp1_unblind LK_pmbtoken_exp1_unblind +pmbtoken_exp2_blind LK_pmbtoken_exp2_blind +pmbtoken_exp2_client_key_from_bytes LK_pmbtoken_exp2_client_key_from_bytes +pmbtoken_exp2_derive_key_from_secret LK_pmbtoken_exp2_derive_key_from_secret +pmbtoken_exp2_generate_key LK_pmbtoken_exp2_generate_key +pmbtoken_exp2_get_h_for_testing LK_pmbtoken_exp2_get_h_for_testing +pmbtoken_exp2_issuer_key_from_bytes LK_pmbtoken_exp2_issuer_key_from_bytes +pmbtoken_exp2_read LK_pmbtoken_exp2_read +pmbtoken_exp2_sign LK_pmbtoken_exp2_sign +pmbtoken_exp2_unblind LK_pmbtoken_exp2_unblind +pmbtoken_pst1_blind LK_pmbtoken_pst1_blind +pmbtoken_pst1_client_key_from_bytes LK_pmbtoken_pst1_client_key_from_bytes +pmbtoken_pst1_derive_key_from_secret LK_pmbtoken_pst1_derive_key_from_secret +pmbtoken_pst1_generate_key LK_pmbtoken_pst1_generate_key +pmbtoken_pst1_get_h_for_testing LK_pmbtoken_pst1_get_h_for_testing +pmbtoken_pst1_issuer_key_from_bytes LK_pmbtoken_pst1_issuer_key_from_bytes +pmbtoken_pst1_read LK_pmbtoken_pst1_read +pmbtoken_pst1_sign LK_pmbtoken_pst1_sign +pmbtoken_pst1_unblind LK_pmbtoken_pst1_unblind +poly_Rq_mul LK_poly_Rq_mul +rand_fork_unsafe_buffering_enabled LK_rand_fork_unsafe_buffering_enabled +rsa_asn1_meth LK_rsa_asn1_meth +rsa_check_public_key LK_rsa_check_public_key +rsa_default_private_transform LK_rsa_default_private_transform +rsa_default_sign_raw LK_rsa_default_sign_raw +rsa_default_size LK_rsa_default_size +rsa_invalidate_key LK_rsa_invalidate_key +rsa_pkey_meth LK_rsa_pkey_meth +rsa_private_transform LK_rsa_private_transform +rsa_private_transform_no_self_test LK_rsa_private_transform_no_self_test +rsa_sign_no_self_test LK_rsa_sign_no_self_test +rsa_verify_no_self_test LK_rsa_verify_no_self_test +rsa_verify_raw_no_self_test LK_rsa_verify_raw_no_self_test +rsaz_1024_gather5_avx2 LK_rsaz_1024_gather5_avx2 +rsaz_1024_mul_avx2 LK_rsaz_1024_mul_avx2 +rsaz_1024_norm2red_avx2 LK_rsaz_1024_norm2red_avx2 +rsaz_1024_red2norm_avx2 LK_rsaz_1024_red2norm_avx2 +rsaz_1024_scatter5_avx2 LK_rsaz_1024_scatter5_avx2 +rsaz_1024_sqr_avx2 LK_rsaz_1024_sqr_avx2 +s2i_ASN1_INTEGER LK_s2i_ASN1_INTEGER +s2i_ASN1_OCTET_STRING LK_s2i_ASN1_OCTET_STRING +sha1_block_data_order_avx LK_sha1_block_data_order_avx +sha1_block_data_order_avx2 LK_sha1_block_data_order_avx2 +sha1_block_data_order_hw LK_sha1_block_data_order_hw +sha1_block_data_order_nohw LK_sha1_block_data_order_nohw +sha1_block_data_order_ssse3 LK_sha1_block_data_order_ssse3 +sha256_block_data_order_avx LK_sha256_block_data_order_avx +sha256_block_data_order_hw LK_sha256_block_data_order_hw +sha256_block_data_order_nohw LK_sha256_block_data_order_nohw +sha256_block_data_order_ssse3 LK_sha256_block_data_order_ssse3 +sha512_block_data_order_avx LK_sha512_block_data_order_avx +sha512_block_data_order_nohw LK_sha512_block_data_order_nohw +sk_free LK_sk_free +sk_new_null LK_sk_new_null +sk_num LK_sk_num +sk_pop LK_sk_pop +sk_pop_free LK_sk_pop_free +sk_pop_free_ex LK_sk_pop_free_ex +sk_push LK_sk_push +sk_value LK_sk_value +spx_base_b LK_spx_base_b +spx_copy_keypair_addr LK_spx_copy_keypair_addr +spx_fors_pk_from_sig LK_spx_fors_pk_from_sig +spx_fors_sign LK_spx_fors_sign +spx_fors_sk_gen LK_spx_fors_sk_gen +spx_fors_treehash LK_spx_fors_treehash +spx_get_tree_index LK_spx_get_tree_index +spx_ht_sign LK_spx_ht_sign +spx_ht_verify LK_spx_ht_verify +spx_set_chain_addr LK_spx_set_chain_addr +spx_set_hash_addr LK_spx_set_hash_addr +spx_set_keypair_addr LK_spx_set_keypair_addr +spx_set_layer_addr LK_spx_set_layer_addr +spx_set_tree_addr LK_spx_set_tree_addr +spx_set_tree_height LK_spx_set_tree_height +spx_set_tree_index LK_spx_set_tree_index +spx_set_type LK_spx_set_type +spx_thash_f LK_spx_thash_f +spx_thash_h LK_spx_thash_h +spx_thash_hmsg LK_spx_thash_hmsg +spx_thash_prf LK_spx_thash_prf +spx_thash_prfmsg LK_spx_thash_prfmsg +spx_thash_tk LK_spx_thash_tk +spx_thash_tl LK_spx_thash_tl +spx_to_uint64 LK_spx_to_uint64 +spx_treehash LK_spx_treehash +spx_uint64_to_len_bytes LK_spx_uint64_to_len_bytes +spx_wots_pk_from_sig LK_spx_wots_pk_from_sig +spx_wots_pk_gen LK_spx_wots_pk_gen +spx_wots_sign LK_spx_wots_sign +spx_xmss_pk_from_sig LK_spx_xmss_pk_from_sig +spx_xmss_sign LK_spx_xmss_sign +v2i_GENERAL_NAME LK_v2i_GENERAL_NAME +v2i_GENERAL_NAMES LK_v2i_GENERAL_NAMES +v2i_GENERAL_NAME_ex LK_v2i_GENERAL_NAME_ex +v3_akey_id LK_v3_akey_id +v3_alt LK_v3_alt +v3_bcons LK_v3_bcons +v3_cpols LK_v3_cpols +v3_crl_invdate LK_v3_crl_invdate +v3_crl_num LK_v3_crl_num +v3_crl_reason LK_v3_crl_reason +v3_crld LK_v3_crld +v3_delta_crl LK_v3_delta_crl +v3_ext_ku LK_v3_ext_ku +v3_freshest_crl LK_v3_freshest_crl +v3_idp LK_v3_idp +v3_info LK_v3_info +v3_inhibit_anyp LK_v3_inhibit_anyp +v3_key_usage LK_v3_key_usage +v3_name_constraints LK_v3_name_constraints +v3_ns_ia5_list LK_v3_ns_ia5_list +v3_nscert LK_v3_nscert +v3_ocsp_accresp LK_v3_ocsp_accresp +v3_ocsp_nocheck LK_v3_ocsp_nocheck +v3_policy_constraints LK_v3_policy_constraints +v3_policy_mappings LK_v3_policy_mappings +v3_sinfo LK_v3_sinfo +v3_skey_id LK_v3_skey_id +voprf_exp2_blind LK_voprf_exp2_blind +voprf_exp2_client_key_from_bytes LK_voprf_exp2_client_key_from_bytes +voprf_exp2_derive_key_from_secret LK_voprf_exp2_derive_key_from_secret +voprf_exp2_generate_key LK_voprf_exp2_generate_key +voprf_exp2_issuer_key_from_bytes LK_voprf_exp2_issuer_key_from_bytes +voprf_exp2_read LK_voprf_exp2_read +voprf_exp2_sign LK_voprf_exp2_sign +voprf_exp2_unblind LK_voprf_exp2_unblind +voprf_pst1_blind LK_voprf_pst1_blind +voprf_pst1_client_key_from_bytes LK_voprf_pst1_client_key_from_bytes +voprf_pst1_derive_key_from_secret LK_voprf_pst1_derive_key_from_secret +voprf_pst1_generate_key LK_voprf_pst1_generate_key +voprf_pst1_issuer_key_from_bytes LK_voprf_pst1_issuer_key_from_bytes +voprf_pst1_read LK_voprf_pst1_read +voprf_pst1_sign LK_voprf_pst1_sign +voprf_pst1_sign_with_proof_scalar_for_testing LK_voprf_pst1_sign_with_proof_scalar_for_testing +voprf_pst1_unblind LK_voprf_pst1_unblind +vpaes_cbc_encrypt LK_vpaes_cbc_encrypt +vpaes_ctr32_encrypt_blocks LK_vpaes_ctr32_encrypt_blocks +vpaes_decrypt LK_vpaes_decrypt +vpaes_encrypt LK_vpaes_encrypt +vpaes_set_decrypt_key LK_vpaes_set_decrypt_key +vpaes_set_encrypt_key LK_vpaes_set_encrypt_key +x25519_asn1_meth LK_x25519_asn1_meth +x25519_ge_add LK_x25519_ge_add +x25519_ge_frombytes_vartime LK_x25519_ge_frombytes_vartime +x25519_ge_p1p1_to_p2 LK_x25519_ge_p1p1_to_p2 +x25519_ge_p1p1_to_p3 LK_x25519_ge_p1p1_to_p3 +x25519_ge_p3_to_cached LK_x25519_ge_p3_to_cached +x25519_ge_scalarmult LK_x25519_ge_scalarmult +x25519_ge_scalarmult_base LK_x25519_ge_scalarmult_base +x25519_ge_scalarmult_base_adx LK_x25519_ge_scalarmult_base_adx +x25519_ge_scalarmult_small_precomp LK_x25519_ge_scalarmult_small_precomp +x25519_ge_sub LK_x25519_ge_sub +x25519_ge_tobytes LK_x25519_ge_tobytes +x25519_pkey_meth LK_x25519_pkey_meth +x25519_sc_reduce LK_x25519_sc_reduce +x25519_scalar_mult_adx LK_x25519_scalar_mult_adx +x509V3_add_value_asn1_string LK_x509V3_add_value_asn1_string +x509_check_issued_with_callback LK_x509_check_issued_with_callback +x509_digest_sign_algorithm LK_x509_digest_sign_algorithm +x509_digest_verify_init LK_x509_digest_verify_init +x509_print_rsa_pss_params LK_x509_print_rsa_pss_params +x509_rsa_ctx_to_pss LK_x509_rsa_ctx_to_pss +x509_rsa_pss_to_ctx LK_x509_rsa_pss_to_ctx +x509v3_a2i_ipadd LK_x509v3_a2i_ipadd +x509v3_bytes_to_hex LK_x509v3_bytes_to_hex +x509v3_cache_extensions LK_x509v3_cache_extensions +x509v3_conf_name_matches LK_x509v3_conf_name_matches +x509v3_hex_to_bytes LK_x509v3_hex_to_bytes +x509v3_looks_like_dns_name LK_x509v3_looks_like_dns_name diff --git a/webrtc-sys/libwebrtc/build_linux.sh b/webrtc-sys/libwebrtc/build_linux.sh index 1322a828f..5f6002425 100755 --- a/webrtc-sys/libwebrtc/build_linux.sh +++ b/webrtc-sys/libwebrtc/build_linux.sh @@ -67,9 +67,13 @@ then fi cd src +git fetch origin m114_release +git checkout m114_release + git apply "$COMMAND_DIR/patches/add_licenses.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn git apply "$COMMAND_DIR/patches/ssl_verify_callback_with_native_handle.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn git apply "$COMMAND_DIR/patches/add_deps.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn +git apply "$COMMAND_DIR/patches/fix_m114.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn cd .. mkdir -p "$ARTIFACTS_DIR/lib" @@ -116,6 +120,7 @@ ninja -C "$OUTPUT_DIR" :default # make libwebrtc.a # don't include nasm ar -rc "$ARTIFACTS_DIR/lib/libwebrtc.a" `find "$OUTPUT_DIR/obj" -name '*.o' -not -path "*/third_party/nasm/*"` +objcopy --redefine-syms="$COMMAND_DIR/boringssl_prefix_symbols.txt" "$ARTIFACTS_DIR/lib/libwebrtc.a" python3 "./src/tools_webrtc/libs/generate_licenses.py" \ --target :default "$OUTPUT_DIR" "$OUTPUT_DIR" diff --git a/webrtc-sys/libwebrtc/patches/fix_m114.patch b/webrtc-sys/libwebrtc/patches/fix_m114.patch new file mode 100644 index 000000000..18ff65254 --- /dev/null +++ b/webrtc-sys/libwebrtc/patches/fix_m114.patch @@ -0,0 +1,54 @@ +diff --git a/modules/congestion_controller/goog_cc/loss_based_bwe_v2.cc b/modules/congestion_controller/goog_cc/loss_based_bwe_v2.cc +index 4c0f5fc5ee..bdf18d0e13 100644 +--- a/modules/congestion_controller/goog_cc/loss_based_bwe_v2.cc ++++ b/modules/congestion_controller/goog_cc/loss_based_bwe_v2.cc +@@ -1096,4 +1096,6 @@ bool LossBasedBweV2::IsRequestingProbe() const { + return current_state_ == LossBasedState::kIncreasing; + } + ++LossBasedBweV2::Config::Config() = default; ++ + } // namespace webrtc +diff --git a/modules/congestion_controller/goog_cc/loss_based_bwe_v2.h b/modules/congestion_controller/goog_cc/loss_based_bwe_v2.h +index f5a6396de2..3d5cbc9acf 100644 +--- a/modules/congestion_controller/goog_cc/loss_based_bwe_v2.h ++++ b/modules/congestion_controller/goog_cc/loss_based_bwe_v2.h +@@ -79,6 +79,7 @@ class LossBasedBweV2 { + }; + + struct Config { ++ Config(); + double bandwidth_rampup_upper_bound_factor = 0.0; + double rampup_acceleration_max_factor = 0.0; + TimeDelta rampup_acceleration_maxout_time = TimeDelta::Zero(); +diff --git a/modules/video_coding/codecs/h264/h264_decoder_impl.cc b/modules/video_coding/codecs/h264/h264_decoder_impl.cc +index f67718cb23..8852837225 100644 +--- a/modules/video_coding/codecs/h264/h264_decoder_impl.cc ++++ b/modules/video_coding/codecs/h264/h264_decoder_impl.cc +@@ -229,7 +229,7 @@ int H264DecoderImpl::AVGetBuffer2(AVCodecContext* context, + int total_size = y_size + 2 * uv_size; + + av_frame->format = context->pix_fmt; +- av_frame->reordered_opaque = context->reordered_opaque; ++ av_frame->opaque = context->opaque; + + // Create a VideoFrame object, to keep a reference to the buffer. + // TODO(nisse): The VideoFrame's timestamp and rotation info is not used. +@@ -378,7 +378,7 @@ int32_t H264DecoderImpl::Decode(const EncodedImage& input_image, + } + packet->size = static_cast(input_image.size()); + int64_t frame_timestamp_us = input_image.ntp_time_ms_ * 1000; // ms -> μs +- av_context_->reordered_opaque = frame_timestamp_us; ++ av_context_->opaque = (void*)frame_timestamp_us; + + int result = avcodec_send_packet(av_context_.get(), packet.get()); + +@@ -397,7 +397,7 @@ int32_t H264DecoderImpl::Decode(const EncodedImage& input_image, + + // We don't expect reordering. Decoded frame timestamp should match + // the input one. +- RTC_DCHECK_EQ(av_frame_->reordered_opaque, frame_timestamp_us); ++ RTC_DCHECK_EQ(av_frame_->opaque, (void*)frame_timestamp_us); + + // TODO(sakal): Maybe it is possible to get QP directly from FFmpeg. + h264_bitstream_parser_.ParseBitstream(input_image); From b9cde5c9b3800f73c18282f24466e1136579ae6f Mon Sep 17 00:00:00 2001 From: aoife cassidy Date: Wed, 14 Aug 2024 02:38:39 -0700 Subject: [PATCH 007/274] webrtc-sys: use 00c1ca1 (#390) --- webrtc-sys/build/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webrtc-sys/build/src/lib.rs b/webrtc-sys/build/src/lib.rs index c99c2ae56..248405cb2 100644 --- a/webrtc-sys/build/src/lib.rs +++ b/webrtc-sys/build/src/lib.rs @@ -26,7 +26,7 @@ use regex::Regex; use reqwest::StatusCode; pub const SCRATH_PATH: &str = "livekit_webrtc"; -pub const WEBRTC_TAG: &str = "webrtc-b951613-4"; +pub const WEBRTC_TAG: &str = "webrtc-00c1ca1-2"; pub const IGNORE_DEFINES: [&str; 2] = ["CR_CLANG_REVISION", "CR_XCODE_VERSION"]; pub fn target_os() -> String { From 1534a179534e2ea094ce5281df16a4d6fbd1834a Mon Sep 17 00:00:00 2001 From: aoife cassidy Date: Thu, 15 Aug 2024 11:36:19 -0700 Subject: [PATCH 008/274] webrtc-sys: checkout m114 for everything (#392) --- webrtc-sys/libwebrtc/build_android.sh | 3 +++ webrtc-sys/libwebrtc/build_ios.sh | 3 +++ webrtc-sys/libwebrtc/build_macos.sh | 3 +++ webrtc-sys/libwebrtc/build_windows.cmd | 3 +++ 4 files changed, 12 insertions(+) diff --git a/webrtc-sys/libwebrtc/build_android.sh b/webrtc-sys/libwebrtc/build_android.sh index 01778b206..a7246fe8c 100755 --- a/webrtc-sys/libwebrtc/build_android.sh +++ b/webrtc-sys/libwebrtc/build_android.sh @@ -67,6 +67,9 @@ then fi cd src +git fetch origin m114_release +git checkout m114_release + # git apply "$COMMAND_DIR/patches/add_licenses.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn git apply "$COMMAND_DIR/patches/ssl_verify_callback_with_native_handle.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn git apply "$COMMAND_DIR/patches/add_deps.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn diff --git a/webrtc-sys/libwebrtc/build_ios.sh b/webrtc-sys/libwebrtc/build_ios.sh index 3bed8f7f8..294f68e13 100755 --- a/webrtc-sys/libwebrtc/build_ios.sh +++ b/webrtc-sys/libwebrtc/build_ios.sh @@ -78,6 +78,9 @@ then fi cd src +git fetch origin m114_release +git checkout m114_release + # git apply "$COMMAND_DIR/patches/add_licenses.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn git apply "$COMMAND_DIR/patches/ssl_verify_callback_with_native_handle.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn git apply "$COMMAND_DIR/patches/add_deps.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn diff --git a/webrtc-sys/libwebrtc/build_macos.sh b/webrtc-sys/libwebrtc/build_macos.sh index 4498b2eb0..847a6e906 100755 --- a/webrtc-sys/libwebrtc/build_macos.sh +++ b/webrtc-sys/libwebrtc/build_macos.sh @@ -67,6 +67,9 @@ then fi cd src +git fetch origin m114_release +git checkout m114_release + git apply "$COMMAND_DIR/patches/add_licenses.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn git apply "$COMMAND_DIR/patches/ssl_verify_callback_with_native_handle.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn git apply "$COMMAND_DIR/patches/add_deps.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn diff --git a/webrtc-sys/libwebrtc/build_windows.cmd b/webrtc-sys/libwebrtc/build_windows.cmd index 47ca9845b..5e20a28b1 100644 --- a/webrtc-sys/libwebrtc/build_windows.cmd +++ b/webrtc-sys/libwebrtc/build_windows.cmd @@ -50,6 +50,9 @@ if not exist src ( ) cd src +call git fetch origin m114_release +call git checkout m114_release + call git apply "%COMMAND_DIR%/patches/add_licenses.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn call git apply "%COMMAND_DIR%/patches/add_deps.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn call git apply "%COMMAND_DIR%/patches/windows_silence_warnings.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn From 89e039d018af470cdd93d977c6acb4575cfe3e6c Mon Sep 17 00:00:00 2001 From: aoife cassidy Date: Thu, 15 Aug 2024 12:45:23 -0700 Subject: [PATCH 009/274] use gclient to sync instead of manual checkout (#393) --- webrtc-sys/libwebrtc/.gclient | 4 +- webrtc-sys/libwebrtc/build_android.sh | 3 -- webrtc-sys/libwebrtc/build_ios.sh | 3 -- webrtc-sys/libwebrtc/build_linux.sh | 4 -- webrtc-sys/libwebrtc/build_macos.sh | 3 -- webrtc-sys/libwebrtc/build_windows.cmd | 3 -- webrtc-sys/libwebrtc/patches/fix_m114.patch | 54 --------------------- 7 files changed, 2 insertions(+), 72 deletions(-) delete mode 100644 webrtc-sys/libwebrtc/patches/fix_m114.patch diff --git a/webrtc-sys/libwebrtc/.gclient b/webrtc-sys/libwebrtc/.gclient index 95f44786e..5f2dc30af 100644 --- a/webrtc-sys/libwebrtc/.gclient +++ b/webrtc-sys/libwebrtc/.gclient @@ -1,9 +1,9 @@ solutions = [ { "name": 'src', - "url": 'https://github.com/webrtc-sdk/webrtc.git', + "url": 'https://github.com/webrtc-sdk/webrtc.git@m114_release', "custom_deps": {}, "deps_file": "DEPS", "managed": False, }, -] \ No newline at end of file +] diff --git a/webrtc-sys/libwebrtc/build_android.sh b/webrtc-sys/libwebrtc/build_android.sh index a7246fe8c..01778b206 100755 --- a/webrtc-sys/libwebrtc/build_android.sh +++ b/webrtc-sys/libwebrtc/build_android.sh @@ -67,9 +67,6 @@ then fi cd src -git fetch origin m114_release -git checkout m114_release - # git apply "$COMMAND_DIR/patches/add_licenses.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn git apply "$COMMAND_DIR/patches/ssl_verify_callback_with_native_handle.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn git apply "$COMMAND_DIR/patches/add_deps.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn diff --git a/webrtc-sys/libwebrtc/build_ios.sh b/webrtc-sys/libwebrtc/build_ios.sh index 294f68e13..3bed8f7f8 100755 --- a/webrtc-sys/libwebrtc/build_ios.sh +++ b/webrtc-sys/libwebrtc/build_ios.sh @@ -78,9 +78,6 @@ then fi cd src -git fetch origin m114_release -git checkout m114_release - # git apply "$COMMAND_DIR/patches/add_licenses.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn git apply "$COMMAND_DIR/patches/ssl_verify_callback_with_native_handle.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn git apply "$COMMAND_DIR/patches/add_deps.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn diff --git a/webrtc-sys/libwebrtc/build_linux.sh b/webrtc-sys/libwebrtc/build_linux.sh index 5f6002425..5f1af2212 100755 --- a/webrtc-sys/libwebrtc/build_linux.sh +++ b/webrtc-sys/libwebrtc/build_linux.sh @@ -67,13 +67,9 @@ then fi cd src -git fetch origin m114_release -git checkout m114_release - git apply "$COMMAND_DIR/patches/add_licenses.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn git apply "$COMMAND_DIR/patches/ssl_verify_callback_with_native_handle.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn git apply "$COMMAND_DIR/patches/add_deps.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn -git apply "$COMMAND_DIR/patches/fix_m114.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn cd .. mkdir -p "$ARTIFACTS_DIR/lib" diff --git a/webrtc-sys/libwebrtc/build_macos.sh b/webrtc-sys/libwebrtc/build_macos.sh index 847a6e906..4498b2eb0 100755 --- a/webrtc-sys/libwebrtc/build_macos.sh +++ b/webrtc-sys/libwebrtc/build_macos.sh @@ -67,9 +67,6 @@ then fi cd src -git fetch origin m114_release -git checkout m114_release - git apply "$COMMAND_DIR/patches/add_licenses.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn git apply "$COMMAND_DIR/patches/ssl_verify_callback_with_native_handle.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn git apply "$COMMAND_DIR/patches/add_deps.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn diff --git a/webrtc-sys/libwebrtc/build_windows.cmd b/webrtc-sys/libwebrtc/build_windows.cmd index 5e20a28b1..47ca9845b 100644 --- a/webrtc-sys/libwebrtc/build_windows.cmd +++ b/webrtc-sys/libwebrtc/build_windows.cmd @@ -50,9 +50,6 @@ if not exist src ( ) cd src -call git fetch origin m114_release -call git checkout m114_release - call git apply "%COMMAND_DIR%/patches/add_licenses.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn call git apply "%COMMAND_DIR%/patches/add_deps.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn call git apply "%COMMAND_DIR%/patches/windows_silence_warnings.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn diff --git a/webrtc-sys/libwebrtc/patches/fix_m114.patch b/webrtc-sys/libwebrtc/patches/fix_m114.patch deleted file mode 100644 index 18ff65254..000000000 --- a/webrtc-sys/libwebrtc/patches/fix_m114.patch +++ /dev/null @@ -1,54 +0,0 @@ -diff --git a/modules/congestion_controller/goog_cc/loss_based_bwe_v2.cc b/modules/congestion_controller/goog_cc/loss_based_bwe_v2.cc -index 4c0f5fc5ee..bdf18d0e13 100644 ---- a/modules/congestion_controller/goog_cc/loss_based_bwe_v2.cc -+++ b/modules/congestion_controller/goog_cc/loss_based_bwe_v2.cc -@@ -1096,4 +1096,6 @@ bool LossBasedBweV2::IsRequestingProbe() const { - return current_state_ == LossBasedState::kIncreasing; - } - -+LossBasedBweV2::Config::Config() = default; -+ - } // namespace webrtc -diff --git a/modules/congestion_controller/goog_cc/loss_based_bwe_v2.h b/modules/congestion_controller/goog_cc/loss_based_bwe_v2.h -index f5a6396de2..3d5cbc9acf 100644 ---- a/modules/congestion_controller/goog_cc/loss_based_bwe_v2.h -+++ b/modules/congestion_controller/goog_cc/loss_based_bwe_v2.h -@@ -79,6 +79,7 @@ class LossBasedBweV2 { - }; - - struct Config { -+ Config(); - double bandwidth_rampup_upper_bound_factor = 0.0; - double rampup_acceleration_max_factor = 0.0; - TimeDelta rampup_acceleration_maxout_time = TimeDelta::Zero(); -diff --git a/modules/video_coding/codecs/h264/h264_decoder_impl.cc b/modules/video_coding/codecs/h264/h264_decoder_impl.cc -index f67718cb23..8852837225 100644 ---- a/modules/video_coding/codecs/h264/h264_decoder_impl.cc -+++ b/modules/video_coding/codecs/h264/h264_decoder_impl.cc -@@ -229,7 +229,7 @@ int H264DecoderImpl::AVGetBuffer2(AVCodecContext* context, - int total_size = y_size + 2 * uv_size; - - av_frame->format = context->pix_fmt; -- av_frame->reordered_opaque = context->reordered_opaque; -+ av_frame->opaque = context->opaque; - - // Create a VideoFrame object, to keep a reference to the buffer. - // TODO(nisse): The VideoFrame's timestamp and rotation info is not used. -@@ -378,7 +378,7 @@ int32_t H264DecoderImpl::Decode(const EncodedImage& input_image, - } - packet->size = static_cast(input_image.size()); - int64_t frame_timestamp_us = input_image.ntp_time_ms_ * 1000; // ms -> μs -- av_context_->reordered_opaque = frame_timestamp_us; -+ av_context_->opaque = (void*)frame_timestamp_us; - - int result = avcodec_send_packet(av_context_.get(), packet.get()); - -@@ -397,7 +397,7 @@ int32_t H264DecoderImpl::Decode(const EncodedImage& input_image, - - // We don't expect reordering. Decoded frame timestamp should match - // the input one. -- RTC_DCHECK_EQ(av_frame_->reordered_opaque, frame_timestamp_us); -+ RTC_DCHECK_EQ(av_frame_->opaque, (void*)frame_timestamp_us); - - // TODO(sakal): Maybe it is possible to get QP directly from FFmpeg. - h264_bitstream_parser_.ParseBitstream(input_image); From 16895453107ed653564b9b40b903a9c8b8b1ad93 Mon Sep 17 00:00:00 2001 From: aoife cassidy Date: Thu, 15 Aug 2024 13:48:08 -0700 Subject: [PATCH 010/274] bump webrtc (#391) --- webrtc-sys/build/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webrtc-sys/build/src/lib.rs b/webrtc-sys/build/src/lib.rs index 248405cb2..2625b8f80 100644 --- a/webrtc-sys/build/src/lib.rs +++ b/webrtc-sys/build/src/lib.rs @@ -26,7 +26,7 @@ use regex::Regex; use reqwest::StatusCode; pub const SCRATH_PATH: &str = "livekit_webrtc"; -pub const WEBRTC_TAG: &str = "webrtc-00c1ca1-2"; +pub const WEBRTC_TAG: &str = "webrtc-dac8015-3"; pub const IGNORE_DEFINES: [&str; 2] = ["CR_CLANG_REVISION", "CR_XCODE_VERSION"]; pub fn target_os() -> String { From 1abf092970e2e5dfe2bfb0bf6ca2d5f55c63c1ce Mon Sep 17 00:00:00 2001 From: aoife cassidy Date: Tue, 20 Aug 2024 13:02:10 -0700 Subject: [PATCH 011/274] Update protocol to v15 (#394) - add support for LocalTrackSubscribed - add support for RequestResponse - add support for new leave_request::Action - add support for sync_stream ID --- livekit-api/src/signal_client/mod.rs | 16 +- livekit-ffi/protocol/room.proto | 36 +- livekit-ffi/src/conversion/room.rs | 1 + livekit-ffi/src/livekit.proto.rs | 40 +- livekit-ffi/src/server/room.rs | 5 + livekit-protocol/protocol | 2 +- livekit-protocol/src/livekit.rs | 87 +- livekit-protocol/src/livekit.serde.rs | 1320 +++++++++++++---- livekit/src/room/mod.rs | 28 +- livekit/src/room/options.rs | 2 + .../src/room/participant/local_participant.rs | 129 +- livekit/src/rtc_engine/mod.rs | 41 +- livekit/src/rtc_engine/rtc_session.rs | 49 +- 13 files changed, 1327 insertions(+), 429 deletions(-) diff --git a/livekit-api/src/signal_client/mod.rs b/livekit-api/src/signal_client/mod.rs index 922728caf..73ac1f916 100644 --- a/livekit-api/src/signal_client/mod.rs +++ b/livekit-api/src/signal_client/mod.rs @@ -16,7 +16,7 @@ use std::{ borrow::Cow, fmt::Debug, sync::{ - atomic::{AtomicBool, Ordering}, + atomic::{AtomicBool, AtomicU32, Ordering}, Arc, }, time::{Duration, SystemTime, UNIX_EPOCH}, @@ -44,7 +44,7 @@ pub type SignalEvents = mpsc::UnboundedReceiver; pub type SignalResult = Result; pub const JOIN_RESPONSE_TIMEOUT: Duration = Duration::from_secs(5); -pub const PROTOCOL_VERSION: u32 = 9; +pub const PROTOCOL_VERSION: u32 = 15; #[derive(Error, Debug)] pub enum SignalError { @@ -92,6 +92,7 @@ struct SignalInner { url: String, options: SignalOptions, join_response: proto::JoinResponse, + request_id: AtomicU32, } pub struct SignalClient { @@ -178,6 +179,11 @@ impl SignalClient { pub fn token(&self) -> String { self.inner.token.lock().clone() } + + /// Increment request_id for user-initiated requests and [`RequestResponse`][`proto::RequestResponse`]s + pub fn next_request_id(&self) -> u32 { + self.inner.next_request_id().clone() + } } impl SignalInner { @@ -213,6 +219,7 @@ impl SignalInner { options, url: url.to_string(), join_response: join_response.clone(), + request_id: AtomicU32::new(1), }); Ok((inner, join_response, events)) @@ -315,6 +322,11 @@ impl SignalInner { } } } + + /// Increment request_id for user-initiated requests and [`RequestResponse`][`proto::RequestResponse`]s + pub fn next_request_id(&self) -> u32 { + self.request_id.fetch_add(1, Ordering::SeqCst) + } } /// Middleware task to receive SignalStream events and handle SignalClient specific logic diff --git a/livekit-ffi/protocol/room.proto b/livekit-ffi/protocol/room.proto index 738280e14..1cdbb977a 100644 --- a/livekit-ffi/protocol/room.proto +++ b/livekit-ffi/protocol/room.proto @@ -212,6 +212,7 @@ message TrackPublishOptions { bool red = 5; bool simulcast = 6; TrackSource source = 7; + string stream = 8; } enum IceTransportType { @@ -318,21 +319,22 @@ message RoomEvent { ParticipantDisconnected participant_disconnected = 3; LocalTrackPublished local_track_published = 4; LocalTrackUnpublished local_track_unpublished = 5; - TrackPublished track_published = 6; - TrackUnpublished track_unpublished = 7; - TrackSubscribed track_subscribed = 8; - TrackUnsubscribed track_unsubscribed = 9; - TrackSubscriptionFailed track_subscription_failed = 10; - TrackMuted track_muted = 11; - TrackUnmuted track_unmuted = 12; - ActiveSpeakersChanged active_speakers_changed = 13; - RoomMetadataChanged room_metadata_changed = 14; - RoomSidChanged room_sid_changed = 15; - ParticipantMetadataChanged participant_metadata_changed = 16; - ParticipantNameChanged participant_name_changed = 17; - ParticipantAttributesChanged participant_attributes_changed = 18; - ConnectionQualityChanged connection_quality_changed = 19; - ConnectionStateChanged connection_state_changed = 20; + LocalTrackSubscribed local_track_subscribed = 6; + TrackPublished track_published = 7; + TrackUnpublished track_unpublished = 8; + TrackSubscribed track_subscribed = 9; + TrackUnsubscribed track_unsubscribed = 10; + TrackSubscriptionFailed track_subscription_failed = 11; + TrackMuted track_muted = 12; + TrackUnmuted track_unmuted = 13; + ActiveSpeakersChanged active_speakers_changed = 14; + RoomMetadataChanged room_metadata_changed = 15; + RoomSidChanged room_sid_changed = 16; + ParticipantMetadataChanged participant_metadata_changed = 17; + ParticipantNameChanged participant_name_changed = 18; + ParticipantAttributesChanged participant_attributes_changed = 19; + ConnectionQualityChanged connection_quality_changed = 20; + ConnectionStateChanged connection_state_changed = 21; // Connected connected = 21; Disconnected disconnected = 22; Reconnecting reconnecting = 23; @@ -371,6 +373,10 @@ message LocalTrackUnpublished { string publication_sid = 1; } +message LocalTrackSubscribed { + string track_sid = 2; +} + message TrackPublished { string participant_identity = 1; OwnedTrackPublication publication = 2; diff --git a/livekit-ffi/src/conversion/room.rs b/livekit-ffi/src/conversion/room.rs index 8fa7f7e09..a42d58fcb 100644 --- a/livekit-ffi/src/conversion/room.rs +++ b/livekit-ffi/src/conversion/room.rs @@ -214,6 +214,7 @@ impl From for TrackPublishOptions { dtx: opts.dtx, red: opts.red, simulcast: opts.simulcast, + stream: opts.stream, } } } diff --git a/livekit-ffi/src/livekit.proto.rs b/livekit-ffi/src/livekit.proto.rs index 6a3c8a3d1..5151fdbf1 100644 --- a/livekit-ffi/src/livekit.proto.rs +++ b/livekit-ffi/src/livekit.proto.rs @@ -2240,6 +2240,8 @@ pub struct TrackPublishOptions { pub simulcast: bool, #[prost(enumeration="TrackSource", tag="7")] pub source: i32, + #[prost(string, tag="8")] + pub stream: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -2316,7 +2318,7 @@ pub struct OwnedBuffer { pub struct RoomEvent { #[prost(uint64, tag="1")] pub room_handle: u64, - #[prost(oneof="room_event::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 22, 23, 24, 25, 26, 27, 28")] + #[prost(oneof="room_event::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28")] pub message: ::core::option::Option, } /// Nested message and enum types in `RoomEvent`. @@ -2333,34 +2335,36 @@ pub mod room_event { #[prost(message, tag="5")] LocalTrackUnpublished(super::LocalTrackUnpublished), #[prost(message, tag="6")] - TrackPublished(super::TrackPublished), + LocalTrackSubscribed(super::LocalTrackSubscribed), #[prost(message, tag="7")] - TrackUnpublished(super::TrackUnpublished), + TrackPublished(super::TrackPublished), #[prost(message, tag="8")] - TrackSubscribed(super::TrackSubscribed), + TrackUnpublished(super::TrackUnpublished), #[prost(message, tag="9")] - TrackUnsubscribed(super::TrackUnsubscribed), + TrackSubscribed(super::TrackSubscribed), #[prost(message, tag="10")] - TrackSubscriptionFailed(super::TrackSubscriptionFailed), + TrackUnsubscribed(super::TrackUnsubscribed), #[prost(message, tag="11")] - TrackMuted(super::TrackMuted), + TrackSubscriptionFailed(super::TrackSubscriptionFailed), #[prost(message, tag="12")] - TrackUnmuted(super::TrackUnmuted), + TrackMuted(super::TrackMuted), #[prost(message, tag="13")] - ActiveSpeakersChanged(super::ActiveSpeakersChanged), + TrackUnmuted(super::TrackUnmuted), #[prost(message, tag="14")] - RoomMetadataChanged(super::RoomMetadataChanged), + ActiveSpeakersChanged(super::ActiveSpeakersChanged), #[prost(message, tag="15")] - RoomSidChanged(super::RoomSidChanged), + RoomMetadataChanged(super::RoomMetadataChanged), #[prost(message, tag="16")] - ParticipantMetadataChanged(super::ParticipantMetadataChanged), + RoomSidChanged(super::RoomSidChanged), #[prost(message, tag="17")] - ParticipantNameChanged(super::ParticipantNameChanged), + ParticipantMetadataChanged(super::ParticipantMetadataChanged), #[prost(message, tag="18")] - ParticipantAttributesChanged(super::ParticipantAttributesChanged), + ParticipantNameChanged(super::ParticipantNameChanged), #[prost(message, tag="19")] - ConnectionQualityChanged(super::ConnectionQualityChanged), + ParticipantAttributesChanged(super::ParticipantAttributesChanged), #[prost(message, tag="20")] + ConnectionQualityChanged(super::ConnectionQualityChanged), + #[prost(message, tag="21")] ConnectionStateChanged(super::ConnectionStateChanged), /// Connected connected = 21; #[prost(message, tag="22")] @@ -2426,6 +2430,12 @@ pub struct LocalTrackUnpublished { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct LocalTrackSubscribed { + #[prost(string, tag="2")] + pub track_sid: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackPublished { #[prost(string, tag="1")] pub participant_identity: ::prost::alloc::string::String, diff --git a/livekit-ffi/src/server/room.rs b/livekit-ffi/src/server/room.rs index 9785d879c..700142ba9 100644 --- a/livekit-ffi/src/server/room.rs +++ b/livekit-ffi/src/server/room.rs @@ -773,6 +773,11 @@ async fn forward_event( inner.pending_unpublished_tracks.lock().insert(publication.sid()); } + RoomEvent::LocalTrackSubscribed { track } => { + let _ = send_event(proto::room_event::Message::LocalTrackSubscribed( + proto::LocalTrackSubscribed { track_sid: track.sid().to_string() }, + )); + } RoomEvent::TrackPublished { publication, participant } => { let handle_id = server.next_id(); let ffi_publication = FfiPublication { diff --git a/livekit-protocol/protocol b/livekit-protocol/protocol index 5a524703e..5c7350d25 160000 --- a/livekit-protocol/protocol +++ b/livekit-protocol/protocol @@ -1 +1 @@ -Subproject commit 5a524703ead68a9946efc6e2a4d62203ec688fbe +Subproject commit 5c7350d25904ed8fd8163e91ff47f0577ca6afad diff --git a/livekit-protocol/src/livekit.rs b/livekit-protocol/src/livekit.rs index f815e547f..39426bdef 100644 --- a/livekit-protocol/src/livekit.rs +++ b/livekit-protocol/src/livekit.rs @@ -112,6 +112,8 @@ pub struct ParticipantInfo { pub kind: i32, #[prost(map="string, string", tag="15")] pub attributes: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + #[prost(enumeration="DisconnectReason", tag="16")] + pub disconnect_reason: i32, } /// Nested message and enum types in `ParticipantInfo`. pub mod participant_info { @@ -738,6 +740,67 @@ pub struct RtpStats { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct RtpForwarderState { + #[prost(bool, tag="1")] + pub started: bool, + #[prost(int32, tag="2")] + pub reference_layer_spatial: i32, + #[prost(int64, tag="3")] + pub pre_start_time: i64, + #[prost(uint64, tag="4")] + pub ext_first_timestamp: u64, + #[prost(uint64, tag="5")] + pub dummy_start_timestamp_offset: u64, + #[prost(message, optional, tag="6")] + pub rtp_munger: ::core::option::Option, + #[prost(oneof="rtp_forwarder_state::CodecMunger", tags="7")] + pub codec_munger: ::core::option::Option, +} +/// Nested message and enum types in `RTPForwarderState`. +pub mod rtp_forwarder_state { + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum CodecMunger { + #[prost(message, tag="7")] + Vp8Munger(super::Vp8MungerState), + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RtpMungerState { + #[prost(uint64, tag="1")] + pub ext_last_sequence_number: u64, + #[prost(uint64, tag="2")] + pub ext_second_last_sequence_number: u64, + #[prost(uint64, tag="3")] + pub ext_last_timestamp: u64, + #[prost(uint64, tag="4")] + pub ext_second_last_timestamp: u64, + #[prost(bool, tag="5")] + pub last_marker: bool, + #[prost(bool, tag="6")] + pub second_last_marker: bool, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Vp8MungerState { + #[prost(int32, tag="1")] + pub ext_last_picture_id: i32, + #[prost(bool, tag="2")] + pub picture_id_used: bool, + #[prost(uint32, tag="3")] + pub last_tl0_pic_idx: u32, + #[prost(bool, tag="4")] + pub tl0_pic_idx_used: bool, + #[prost(bool, tag="5")] + pub tid_used: bool, + #[prost(uint32, tag="6")] + pub last_key_idx: u32, + #[prost(bool, tag="7")] + pub key_idx_used: bool, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct TimedVersion { #[prost(int64, tag="1")] pub unix_micro: i64, @@ -2274,9 +2337,9 @@ pub mod signal_response { /// Subscription response, client should not expect any media from this subscription if it fails #[prost(message, tag="21")] SubscriptionResponse(super::SubscriptionResponse), - /// Errors relating to user inititated requests that carry a `request_id` + /// Response relating to user inititated requests that carry a `request_id` #[prost(message, tag="22")] - ErrorResponse(super::ErrorResponse), + RequestResponse(super::RequestResponse), /// notify to the publisher when a published track has been subscribed for the first time #[prost(message, tag="23")] TrackSubscribed(super::TrackSubscribed), @@ -2778,20 +2841,20 @@ pub struct SubscriptionResponse { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] -pub struct ErrorResponse { +pub struct RequestResponse { #[prost(uint32, tag="1")] pub request_id: u32, - #[prost(enumeration="error_response::Reason", tag="2")] + #[prost(enumeration="request_response::Reason", tag="2")] pub reason: i32, #[prost(string, tag="3")] pub message: ::prost::alloc::string::String, } -/// Nested message and enum types in `ErrorResponse`. -pub mod error_response { +/// Nested message and enum types in `RequestResponse`. +pub mod request_response { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] pub enum Reason { - Unknown = 0, + Ok = 0, NotFound = 1, NotAllowed = 2, LimitExceeded = 3, @@ -2803,7 +2866,7 @@ pub mod error_response { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Reason::Unknown => "UNKNOWN", + Reason::Ok => "OK", Reason::NotFound => "NOT_FOUND", Reason::NotAllowed => "NOT_ALLOWED", Reason::LimitExceeded => "LIMIT_EXCEEDED", @@ -2812,7 +2875,7 @@ pub mod error_response { /// Creates an enum from field names used in the ProtoBuf definition. pub fn from_str_name(value: &str) -> ::core::option::Option { match value { - "UNKNOWN" => Some(Self::Unknown), + "OK" => Some(Self::Ok), "NOT_FOUND" => Some(Self::NotFound), "NOT_ALLOWED" => Some(Self::NotAllowed), "LIMIT_EXCEEDED" => Some(Self::LimitExceeded), @@ -2944,6 +3007,8 @@ pub struct JobState { pub ended_at: i64, #[prost(int64, tag="5")] pub updated_at: i64, + #[prost(string, tag="6")] + pub participant_identity: ::prost::alloc::string::String, } /// from Worker to Server #[allow(clippy::derive_partial_eq_without_eq)] @@ -3234,10 +3299,12 @@ pub struct RoomAgentDispatch { pub struct DeleteAgentDispatchRequest { #[prost(string, tag="1")] pub dispatch_id: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub room: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] -pub struct ListAgentDispatchRequesst { +pub struct ListAgentDispatchRequest { /// if set, only the dispatch whose id is given will be returned #[prost(string, tag="1")] pub dispatch_id: ::prost::alloc::string::String, diff --git a/livekit-protocol/src/livekit.serde.rs b/livekit-protocol/src/livekit.serde.rs index d162333eb..c89e30ca6 100644 --- a/livekit-protocol/src/livekit.serde.rs +++ b/livekit-protocol/src/livekit.serde.rs @@ -4982,10 +4982,16 @@ impl serde::Serialize for DeleteAgentDispatchRequest { if !self.dispatch_id.is_empty() { len += 1; } + if !self.room.is_empty() { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.DeleteAgentDispatchRequest", len)?; if !self.dispatch_id.is_empty() { struct_ser.serialize_field("dispatchId", &self.dispatch_id)?; } + if !self.room.is_empty() { + struct_ser.serialize_field("room", &self.room)?; + } struct_ser.end() } } @@ -4998,11 +5004,13 @@ impl<'de> serde::Deserialize<'de> for DeleteAgentDispatchRequest { const FIELDS: &[&str] = &[ "dispatch_id", "dispatchId", + "room", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { DispatchId, + Room, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -5026,6 +5034,7 @@ impl<'de> serde::Deserialize<'de> for DeleteAgentDispatchRequest { { match value { "dispatchId" | "dispatch_id" => Ok(GeneratedField::DispatchId), + "room" => Ok(GeneratedField::Room), _ => Ok(GeneratedField::__SkipField__), } } @@ -5046,6 +5055,7 @@ impl<'de> serde::Deserialize<'de> for DeleteAgentDispatchRequest { V: serde::de::MapAccess<'de>, { let mut dispatch_id__ = None; + let mut room__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::DispatchId => { @@ -5054,6 +5064,12 @@ impl<'de> serde::Deserialize<'de> for DeleteAgentDispatchRequest { } dispatch_id__ = Some(map_.next_value()?); } + GeneratedField::Room => { + if room__.is_some() { + return Err(serde::de::Error::duplicate_field("room")); + } + room__ = Some(map_.next_value()?); + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -5061,6 +5077,7 @@ impl<'de> serde::Deserialize<'de> for DeleteAgentDispatchRequest { } Ok(DeleteAgentDispatchRequest { dispatch_id: dispatch_id__.unwrap_or_default(), + room: room__.unwrap_or_default(), }) } } @@ -7273,217 +7290,6 @@ impl<'de> serde::Deserialize<'de> for encryption::Type { deserializer.deserialize_any(GeneratedVisitor) } } -impl serde::Serialize for ErrorResponse { - #[allow(deprecated)] - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::Serializer, - { - use serde::ser::SerializeStruct; - let mut len = 0; - if self.request_id != 0 { - len += 1; - } - if self.reason != 0 { - len += 1; - } - if !self.message.is_empty() { - len += 1; - } - let mut struct_ser = serializer.serialize_struct("livekit.ErrorResponse", len)?; - if self.request_id != 0 { - struct_ser.serialize_field("requestId", &self.request_id)?; - } - if self.reason != 0 { - let v = error_response::Reason::try_from(self.reason) - .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.reason)))?; - struct_ser.serialize_field("reason", &v)?; - } - if !self.message.is_empty() { - struct_ser.serialize_field("message", &self.message)?; - } - struct_ser.end() - } -} -impl<'de> serde::Deserialize<'de> for ErrorResponse { - #[allow(deprecated)] - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - const FIELDS: &[&str] = &[ - "request_id", - "requestId", - "reason", - "message", - ]; - - #[allow(clippy::enum_variant_names)] - enum GeneratedField { - RequestId, - Reason, - Message, - __SkipField__, - } - impl<'de> serde::Deserialize<'de> for GeneratedField { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - struct GeneratedVisitor; - - impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = GeneratedField; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(formatter, "expected one of: {:?}", &FIELDS) - } - - #[allow(unused_variables)] - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - match value { - "requestId" | "request_id" => Ok(GeneratedField::RequestId), - "reason" => Ok(GeneratedField::Reason), - "message" => Ok(GeneratedField::Message), - _ => Ok(GeneratedField::__SkipField__), - } - } - } - deserializer.deserialize_identifier(GeneratedVisitor) - } - } - struct GeneratedVisitor; - impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = ErrorResponse; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.ErrorResponse") - } - - fn visit_map(self, mut map_: V) -> std::result::Result - where - V: serde::de::MapAccess<'de>, - { - let mut request_id__ = None; - let mut reason__ = None; - let mut message__ = None; - while let Some(k) = map_.next_key()? { - match k { - GeneratedField::RequestId => { - if request_id__.is_some() { - return Err(serde::de::Error::duplicate_field("requestId")); - } - request_id__ = - Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; - } - GeneratedField::Reason => { - if reason__.is_some() { - return Err(serde::de::Error::duplicate_field("reason")); - } - reason__ = Some(map_.next_value::()? as i32); - } - GeneratedField::Message => { - if message__.is_some() { - return Err(serde::de::Error::duplicate_field("message")); - } - message__ = Some(map_.next_value()?); - } - GeneratedField::__SkipField__ => { - let _ = map_.next_value::()?; - } - } - } - Ok(ErrorResponse { - request_id: request_id__.unwrap_or_default(), - reason: reason__.unwrap_or_default(), - message: message__.unwrap_or_default(), - }) - } - } - deserializer.deserialize_struct("livekit.ErrorResponse", FIELDS, GeneratedVisitor) - } -} -impl serde::Serialize for error_response::Reason { - #[allow(deprecated)] - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::Serializer, - { - let variant = match self { - Self::Unknown => "UNKNOWN", - Self::NotFound => "NOT_FOUND", - Self::NotAllowed => "NOT_ALLOWED", - Self::LimitExceeded => "LIMIT_EXCEEDED", - }; - serializer.serialize_str(variant) - } -} -impl<'de> serde::Deserialize<'de> for error_response::Reason { - #[allow(deprecated)] - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - const FIELDS: &[&str] = &[ - "UNKNOWN", - "NOT_FOUND", - "NOT_ALLOWED", - "LIMIT_EXCEEDED", - ]; - - struct GeneratedVisitor; - - impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = error_response::Reason; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(formatter, "expected one of: {:?}", &FIELDS) - } - - fn visit_i64(self, v: i64) -> std::result::Result - where - E: serde::de::Error, - { - i32::try_from(v) - .ok() - .and_then(|x| x.try_into().ok()) - .ok_or_else(|| { - serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self) - }) - } - - fn visit_u64(self, v: u64) -> std::result::Result - where - E: serde::de::Error, - { - i32::try_from(v) - .ok() - .and_then(|x| x.try_into().ok()) - .ok_or_else(|| { - serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(v), &self) - }) - } - - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - match value { - "UNKNOWN" => Ok(error_response::Reason::Unknown), - "NOT_FOUND" => Ok(error_response::Reason::NotFound), - "NOT_ALLOWED" => Ok(error_response::Reason::NotAllowed), - "LIMIT_EXCEEDED" => Ok(error_response::Reason::LimitExceeded), - _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), - } - } - } - deserializer.deserialize_any(GeneratedVisitor) - } -} impl serde::Serialize for FileInfo { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -10742,6 +10548,9 @@ impl serde::Serialize for JobState { if self.updated_at != 0 { len += 1; } + if !self.participant_identity.is_empty() { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.JobState", len)?; if self.status != 0 { let v = JobStatus::try_from(self.status) @@ -10763,6 +10572,9 @@ impl serde::Serialize for JobState { #[allow(clippy::needless_borrow)] struct_ser.serialize_field("updatedAt", ToString::to_string(&self.updated_at).as_str())?; } + if !self.participant_identity.is_empty() { + struct_ser.serialize_field("participantIdentity", &self.participant_identity)?; + } struct_ser.end() } } @@ -10781,6 +10593,8 @@ impl<'de> serde::Deserialize<'de> for JobState { "endedAt", "updated_at", "updatedAt", + "participant_identity", + "participantIdentity", ]; #[allow(clippy::enum_variant_names)] @@ -10790,6 +10604,7 @@ impl<'de> serde::Deserialize<'de> for JobState { StartedAt, EndedAt, UpdatedAt, + ParticipantIdentity, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -10817,6 +10632,7 @@ impl<'de> serde::Deserialize<'de> for JobState { "startedAt" | "started_at" => Ok(GeneratedField::StartedAt), "endedAt" | "ended_at" => Ok(GeneratedField::EndedAt), "updatedAt" | "updated_at" => Ok(GeneratedField::UpdatedAt), + "participantIdentity" | "participant_identity" => Ok(GeneratedField::ParticipantIdentity), _ => Ok(GeneratedField::__SkipField__), } } @@ -10841,6 +10657,7 @@ impl<'de> serde::Deserialize<'de> for JobState { let mut started_at__ = None; let mut ended_at__ = None; let mut updated_at__ = None; + let mut participant_identity__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::Status => { @@ -10879,6 +10696,12 @@ impl<'de> serde::Deserialize<'de> for JobState { Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) ; } + GeneratedField::ParticipantIdentity => { + if participant_identity__.is_some() { + return Err(serde::de::Error::duplicate_field("participantIdentity")); + } + participant_identity__ = Some(map_.next_value()?); + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -10890,6 +10713,7 @@ impl<'de> serde::Deserialize<'de> for JobState { started_at: started_at__.unwrap_or_default(), ended_at: ended_at__.unwrap_or_default(), updated_at: updated_at__.unwrap_or_default(), + participant_identity: participant_identity__.unwrap_or_default(), }) } } @@ -11682,7 +11506,7 @@ impl<'de> serde::Deserialize<'de> for leave_request::Action { deserializer.deserialize_any(GeneratedVisitor) } } -impl serde::Serialize for ListAgentDispatchRequesst { +impl serde::Serialize for ListAgentDispatchRequest { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -11696,7 +11520,7 @@ impl serde::Serialize for ListAgentDispatchRequesst { if !self.room.is_empty() { len += 1; } - let mut struct_ser = serializer.serialize_struct("livekit.ListAgentDispatchRequesst", len)?; + let mut struct_ser = serializer.serialize_struct("livekit.ListAgentDispatchRequest", len)?; if !self.dispatch_id.is_empty() { struct_ser.serialize_field("dispatchId", &self.dispatch_id)?; } @@ -11706,7 +11530,7 @@ impl serde::Serialize for ListAgentDispatchRequesst { struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for ListAgentDispatchRequesst { +impl<'de> serde::Deserialize<'de> for ListAgentDispatchRequest { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where @@ -11755,13 +11579,13 @@ impl<'de> serde::Deserialize<'de> for ListAgentDispatchRequesst { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = ListAgentDispatchRequesst; + type Value = ListAgentDispatchRequest; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.ListAgentDispatchRequesst") + formatter.write_str("struct livekit.ListAgentDispatchRequest") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { @@ -11786,13 +11610,13 @@ impl<'de> serde::Deserialize<'de> for ListAgentDispatchRequesst { } } } - Ok(ListAgentDispatchRequesst { + Ok(ListAgentDispatchRequest { dispatch_id: dispatch_id__.unwrap_or_default(), room: room__.unwrap_or_default(), }) } } - deserializer.deserialize_struct("livekit.ListAgentDispatchRequesst", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.ListAgentDispatchRequest", FIELDS, GeneratedVisitor) } } impl serde::Serialize for ListAgentDispatchResponse { @@ -14110,6 +13934,9 @@ impl serde::Serialize for ParticipantInfo { if !self.attributes.is_empty() { len += 1; } + if self.disconnect_reason != 0 { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.ParticipantInfo", len)?; if !self.sid.is_empty() { struct_ser.serialize_field("sid", &self.sid)?; @@ -14155,6 +13982,11 @@ impl serde::Serialize for ParticipantInfo { if !self.attributes.is_empty() { struct_ser.serialize_field("attributes", &self.attributes)?; } + if self.disconnect_reason != 0 { + let v = DisconnectReason::try_from(self.disconnect_reason) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.disconnect_reason)))?; + struct_ser.serialize_field("disconnectReason", &v)?; + } struct_ser.end() } } @@ -14180,6 +14012,8 @@ impl<'de> serde::Deserialize<'de> for ParticipantInfo { "isPublisher", "kind", "attributes", + "disconnect_reason", + "disconnectReason", ]; #[allow(clippy::enum_variant_names)] @@ -14197,6 +14031,7 @@ impl<'de> serde::Deserialize<'de> for ParticipantInfo { IsPublisher, Kind, Attributes, + DisconnectReason, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -14232,6 +14067,7 @@ impl<'de> serde::Deserialize<'de> for ParticipantInfo { "isPublisher" | "is_publisher" => Ok(GeneratedField::IsPublisher), "kind" => Ok(GeneratedField::Kind), "attributes" => Ok(GeneratedField::Attributes), + "disconnectReason" | "disconnect_reason" => Ok(GeneratedField::DisconnectReason), _ => Ok(GeneratedField::__SkipField__), } } @@ -14264,6 +14100,7 @@ impl<'de> serde::Deserialize<'de> for ParticipantInfo { let mut is_publisher__ = None; let mut kind__ = None; let mut attributes__ = None; + let mut disconnect_reason__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::Sid => { @@ -14350,6 +14187,12 @@ impl<'de> serde::Deserialize<'de> for ParticipantInfo { map_.next_value::>()? ); } + GeneratedField::DisconnectReason => { + if disconnect_reason__.is_some() { + return Err(serde::de::Error::duplicate_field("disconnectReason")); + } + disconnect_reason__ = Some(map_.next_value::()? as i32); + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -14369,6 +14212,7 @@ impl<'de> serde::Deserialize<'de> for ParticipantInfo { is_publisher: is_publisher__.unwrap_or_default(), kind: kind__.unwrap_or_default(), attributes: attributes__.unwrap_or_default(), + disconnect_reason: disconnect_reason__.unwrap_or_default(), }) } } @@ -15720,7 +15564,7 @@ impl<'de> serde::Deserialize<'de> for RtpDrift { deserializer.deserialize_struct("livekit.RTPDrift", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for RtpStats { +impl serde::Serialize for RtpForwarderState { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -15728,43 +15572,460 @@ impl serde::Serialize for RtpStats { { use serde::ser::SerializeStruct; let mut len = 0; - if self.start_time.is_some() { + if self.started { len += 1; } - if self.end_time.is_some() { + if self.reference_layer_spatial != 0 { len += 1; } - if self.duration != 0. { + if self.pre_start_time != 0 { len += 1; } - if self.packets != 0 { + if self.ext_first_timestamp != 0 { len += 1; } - if self.packet_rate != 0. { + if self.dummy_start_timestamp_offset != 0 { len += 1; } - if self.bytes != 0 { + if self.rtp_munger.is_some() { len += 1; } - if self.header_bytes != 0 { + if self.codec_munger.is_some() { len += 1; } - if self.bitrate != 0. { - len += 1; + let mut struct_ser = serializer.serialize_struct("livekit.RTPForwarderState", len)?; + if self.started { + struct_ser.serialize_field("started", &self.started)?; } - if self.packets_lost != 0 { - len += 1; + if self.reference_layer_spatial != 0 { + struct_ser.serialize_field("referenceLayerSpatial", &self.reference_layer_spatial)?; } - if self.packet_loss_rate != 0. { - len += 1; + if self.pre_start_time != 0 { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("preStartTime", ToString::to_string(&self.pre_start_time).as_str())?; } - if self.packet_loss_percentage != 0. { - len += 1; + if self.ext_first_timestamp != 0 { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("extFirstTimestamp", ToString::to_string(&self.ext_first_timestamp).as_str())?; } - if self.packets_duplicate != 0 { - len += 1; + if self.dummy_start_timestamp_offset != 0 { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("dummyStartTimestampOffset", ToString::to_string(&self.dummy_start_timestamp_offset).as_str())?; } - if self.packet_duplicate_rate != 0. { + if let Some(v) = self.rtp_munger.as_ref() { + struct_ser.serialize_field("rtpMunger", v)?; + } + if let Some(v) = self.codec_munger.as_ref() { + match v { + rtp_forwarder_state::CodecMunger::Vp8Munger(v) => { + struct_ser.serialize_field("vp8Munger", v)?; + } + } + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for RtpForwarderState { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "started", + "reference_layer_spatial", + "referenceLayerSpatial", + "pre_start_time", + "preStartTime", + "ext_first_timestamp", + "extFirstTimestamp", + "dummy_start_timestamp_offset", + "dummyStartTimestampOffset", + "rtp_munger", + "rtpMunger", + "vp8_munger", + "vp8Munger", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Started, + ReferenceLayerSpatial, + PreStartTime, + ExtFirstTimestamp, + DummyStartTimestampOffset, + RtpMunger, + Vp8Munger, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "started" => Ok(GeneratedField::Started), + "referenceLayerSpatial" | "reference_layer_spatial" => Ok(GeneratedField::ReferenceLayerSpatial), + "preStartTime" | "pre_start_time" => Ok(GeneratedField::PreStartTime), + "extFirstTimestamp" | "ext_first_timestamp" => Ok(GeneratedField::ExtFirstTimestamp), + "dummyStartTimestampOffset" | "dummy_start_timestamp_offset" => Ok(GeneratedField::DummyStartTimestampOffset), + "rtpMunger" | "rtp_munger" => Ok(GeneratedField::RtpMunger), + "vp8Munger" | "vp8_munger" => Ok(GeneratedField::Vp8Munger), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = RtpForwarderState; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.RTPForwarderState") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut started__ = None; + let mut reference_layer_spatial__ = None; + let mut pre_start_time__ = None; + let mut ext_first_timestamp__ = None; + let mut dummy_start_timestamp_offset__ = None; + let mut rtp_munger__ = None; + let mut codec_munger__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Started => { + if started__.is_some() { + return Err(serde::de::Error::duplicate_field("started")); + } + started__ = Some(map_.next_value()?); + } + GeneratedField::ReferenceLayerSpatial => { + if reference_layer_spatial__.is_some() { + return Err(serde::de::Error::duplicate_field("referenceLayerSpatial")); + } + reference_layer_spatial__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::PreStartTime => { + if pre_start_time__.is_some() { + return Err(serde::de::Error::duplicate_field("preStartTime")); + } + pre_start_time__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::ExtFirstTimestamp => { + if ext_first_timestamp__.is_some() { + return Err(serde::de::Error::duplicate_field("extFirstTimestamp")); + } + ext_first_timestamp__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::DummyStartTimestampOffset => { + if dummy_start_timestamp_offset__.is_some() { + return Err(serde::de::Error::duplicate_field("dummyStartTimestampOffset")); + } + dummy_start_timestamp_offset__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::RtpMunger => { + if rtp_munger__.is_some() { + return Err(serde::de::Error::duplicate_field("rtpMunger")); + } + rtp_munger__ = map_.next_value()?; + } + GeneratedField::Vp8Munger => { + if codec_munger__.is_some() { + return Err(serde::de::Error::duplicate_field("vp8Munger")); + } + codec_munger__ = map_.next_value::<::std::option::Option<_>>()?.map(rtp_forwarder_state::CodecMunger::Vp8Munger) +; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(RtpForwarderState { + started: started__.unwrap_or_default(), + reference_layer_spatial: reference_layer_spatial__.unwrap_or_default(), + pre_start_time: pre_start_time__.unwrap_or_default(), + ext_first_timestamp: ext_first_timestamp__.unwrap_or_default(), + dummy_start_timestamp_offset: dummy_start_timestamp_offset__.unwrap_or_default(), + rtp_munger: rtp_munger__, + codec_munger: codec_munger__, + }) + } + } + deserializer.deserialize_struct("livekit.RTPForwarderState", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for RtpMungerState { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.ext_last_sequence_number != 0 { + len += 1; + } + if self.ext_second_last_sequence_number != 0 { + len += 1; + } + if self.ext_last_timestamp != 0 { + len += 1; + } + if self.ext_second_last_timestamp != 0 { + len += 1; + } + if self.last_marker { + len += 1; + } + if self.second_last_marker { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.RTPMungerState", len)?; + if self.ext_last_sequence_number != 0 { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("extLastSequenceNumber", ToString::to_string(&self.ext_last_sequence_number).as_str())?; + } + if self.ext_second_last_sequence_number != 0 { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("extSecondLastSequenceNumber", ToString::to_string(&self.ext_second_last_sequence_number).as_str())?; + } + if self.ext_last_timestamp != 0 { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("extLastTimestamp", ToString::to_string(&self.ext_last_timestamp).as_str())?; + } + if self.ext_second_last_timestamp != 0 { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("extSecondLastTimestamp", ToString::to_string(&self.ext_second_last_timestamp).as_str())?; + } + if self.last_marker { + struct_ser.serialize_field("lastMarker", &self.last_marker)?; + } + if self.second_last_marker { + struct_ser.serialize_field("secondLastMarker", &self.second_last_marker)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for RtpMungerState { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "ext_last_sequence_number", + "extLastSequenceNumber", + "ext_second_last_sequence_number", + "extSecondLastSequenceNumber", + "ext_last_timestamp", + "extLastTimestamp", + "ext_second_last_timestamp", + "extSecondLastTimestamp", + "last_marker", + "lastMarker", + "second_last_marker", + "secondLastMarker", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + ExtLastSequenceNumber, + ExtSecondLastSequenceNumber, + ExtLastTimestamp, + ExtSecondLastTimestamp, + LastMarker, + SecondLastMarker, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "extLastSequenceNumber" | "ext_last_sequence_number" => Ok(GeneratedField::ExtLastSequenceNumber), + "extSecondLastSequenceNumber" | "ext_second_last_sequence_number" => Ok(GeneratedField::ExtSecondLastSequenceNumber), + "extLastTimestamp" | "ext_last_timestamp" => Ok(GeneratedField::ExtLastTimestamp), + "extSecondLastTimestamp" | "ext_second_last_timestamp" => Ok(GeneratedField::ExtSecondLastTimestamp), + "lastMarker" | "last_marker" => Ok(GeneratedField::LastMarker), + "secondLastMarker" | "second_last_marker" => Ok(GeneratedField::SecondLastMarker), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = RtpMungerState; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.RTPMungerState") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut ext_last_sequence_number__ = None; + let mut ext_second_last_sequence_number__ = None; + let mut ext_last_timestamp__ = None; + let mut ext_second_last_timestamp__ = None; + let mut last_marker__ = None; + let mut second_last_marker__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::ExtLastSequenceNumber => { + if ext_last_sequence_number__.is_some() { + return Err(serde::de::Error::duplicate_field("extLastSequenceNumber")); + } + ext_last_sequence_number__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::ExtSecondLastSequenceNumber => { + if ext_second_last_sequence_number__.is_some() { + return Err(serde::de::Error::duplicate_field("extSecondLastSequenceNumber")); + } + ext_second_last_sequence_number__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::ExtLastTimestamp => { + if ext_last_timestamp__.is_some() { + return Err(serde::de::Error::duplicate_field("extLastTimestamp")); + } + ext_last_timestamp__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::ExtSecondLastTimestamp => { + if ext_second_last_timestamp__.is_some() { + return Err(serde::de::Error::duplicate_field("extSecondLastTimestamp")); + } + ext_second_last_timestamp__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::LastMarker => { + if last_marker__.is_some() { + return Err(serde::de::Error::duplicate_field("lastMarker")); + } + last_marker__ = Some(map_.next_value()?); + } + GeneratedField::SecondLastMarker => { + if second_last_marker__.is_some() { + return Err(serde::de::Error::duplicate_field("secondLastMarker")); + } + second_last_marker__ = Some(map_.next_value()?); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(RtpMungerState { + ext_last_sequence_number: ext_last_sequence_number__.unwrap_or_default(), + ext_second_last_sequence_number: ext_second_last_sequence_number__.unwrap_or_default(), + ext_last_timestamp: ext_last_timestamp__.unwrap_or_default(), + ext_second_last_timestamp: ext_second_last_timestamp__.unwrap_or_default(), + last_marker: last_marker__.unwrap_or_default(), + second_last_marker: second_last_marker__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.RTPMungerState", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for RtpStats { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.start_time.is_some() { + len += 1; + } + if self.end_time.is_some() { + len += 1; + } + if self.duration != 0. { + len += 1; + } + if self.packets != 0 { + len += 1; + } + if self.packet_rate != 0. { + len += 1; + } + if self.bytes != 0 { + len += 1; + } + if self.header_bytes != 0 { + len += 1; + } + if self.bitrate != 0. { + len += 1; + } + if self.packets_lost != 0 { + len += 1; + } + if self.packet_loss_rate != 0. { + len += 1; + } + if self.packet_loss_percentage != 0. { + len += 1; + } + if self.packets_duplicate != 0 { + len += 1; + } + if self.packet_duplicate_rate != 0. { len += 1; } if self.bytes_duplicate != 0 { @@ -17285,29 +17546,224 @@ impl serde::Serialize for RegisterWorkerResponse { if !self.worker_id.is_empty() { struct_ser.serialize_field("workerId", &self.worker_id)?; } - if let Some(v) = self.server_info.as_ref() { - struct_ser.serialize_field("serverInfo", v)?; + if let Some(v) = self.server_info.as_ref() { + struct_ser.serialize_field("serverInfo", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for RegisterWorkerResponse { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "worker_id", + "workerId", + "server_info", + "serverInfo", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + WorkerId, + ServerInfo, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "workerId" | "worker_id" => Ok(GeneratedField::WorkerId), + "serverInfo" | "server_info" => Ok(GeneratedField::ServerInfo), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = RegisterWorkerResponse; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.RegisterWorkerResponse") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut worker_id__ = None; + let mut server_info__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::WorkerId => { + if worker_id__.is_some() { + return Err(serde::de::Error::duplicate_field("workerId")); + } + worker_id__ = Some(map_.next_value()?); + } + GeneratedField::ServerInfo => { + if server_info__.is_some() { + return Err(serde::de::Error::duplicate_field("serverInfo")); + } + server_info__ = map_.next_value()?; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(RegisterWorkerResponse { + worker_id: worker_id__.unwrap_or_default(), + server_info: server_info__, + }) + } + } + deserializer.deserialize_struct("livekit.RegisterWorkerResponse", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for RemoveParticipantResponse { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let len = 0; + let struct_ser = serializer.serialize_struct("livekit.RemoveParticipantResponse", len)?; + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for RemoveParticipantResponse { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + Ok(GeneratedField::__SkipField__) + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = RemoveParticipantResponse; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.RemoveParticipantResponse") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + while map_.next_key::()?.is_some() { + let _ = map_.next_value::()?; + } + Ok(RemoveParticipantResponse { + }) + } + } + deserializer.deserialize_struct("livekit.RemoveParticipantResponse", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for RequestResponse { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.request_id != 0 { + len += 1; + } + if self.reason != 0 { + len += 1; + } + if !self.message.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.RequestResponse", len)?; + if self.request_id != 0 { + struct_ser.serialize_field("requestId", &self.request_id)?; + } + if self.reason != 0 { + let v = request_response::Reason::try_from(self.reason) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.reason)))?; + struct_ser.serialize_field("reason", &v)?; + } + if !self.message.is_empty() { + struct_ser.serialize_field("message", &self.message)?; } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for RegisterWorkerResponse { +impl<'de> serde::Deserialize<'de> for RequestResponse { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "worker_id", - "workerId", - "server_info", - "serverInfo", + "request_id", + "requestId", + "reason", + "message", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { - WorkerId, - ServerInfo, + RequestId, + Reason, + Message, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -17330,8 +17786,9 @@ impl<'de> serde::Deserialize<'de> for RegisterWorkerResponse { E: serde::de::Error, { match value { - "workerId" | "worker_id" => Ok(GeneratedField::WorkerId), - "serverInfo" | "server_info" => Ok(GeneratedField::ServerInfo), + "requestId" | "request_id" => Ok(GeneratedField::RequestId), + "reason" => Ok(GeneratedField::Reason), + "message" => Ok(GeneratedField::Message), _ => Ok(GeneratedField::__SkipField__), } } @@ -17341,116 +17798,131 @@ impl<'de> serde::Deserialize<'de> for RegisterWorkerResponse { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = RegisterWorkerResponse; + type Value = RequestResponse; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.RegisterWorkerResponse") + formatter.write_str("struct livekit.RequestResponse") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut worker_id__ = None; - let mut server_info__ = None; + let mut request_id__ = None; + let mut reason__ = None; + let mut message__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::WorkerId => { - if worker_id__.is_some() { - return Err(serde::de::Error::duplicate_field("workerId")); + GeneratedField::RequestId => { + if request_id__.is_some() { + return Err(serde::de::Error::duplicate_field("requestId")); } - worker_id__ = Some(map_.next_value()?); + request_id__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; } - GeneratedField::ServerInfo => { - if server_info__.is_some() { - return Err(serde::de::Error::duplicate_field("serverInfo")); + GeneratedField::Reason => { + if reason__.is_some() { + return Err(serde::de::Error::duplicate_field("reason")); } - server_info__ = map_.next_value()?; + reason__ = Some(map_.next_value::()? as i32); + } + GeneratedField::Message => { + if message__.is_some() { + return Err(serde::de::Error::duplicate_field("message")); + } + message__ = Some(map_.next_value()?); } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } } } - Ok(RegisterWorkerResponse { - worker_id: worker_id__.unwrap_or_default(), - server_info: server_info__, + Ok(RequestResponse { + request_id: request_id__.unwrap_or_default(), + reason: reason__.unwrap_or_default(), + message: message__.unwrap_or_default(), }) } } - deserializer.deserialize_struct("livekit.RegisterWorkerResponse", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.RequestResponse", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for RemoveParticipantResponse { +impl serde::Serialize for request_response::Reason { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where S: serde::Serializer, { - use serde::ser::SerializeStruct; - let len = 0; - let struct_ser = serializer.serialize_struct("livekit.RemoveParticipantResponse", len)?; - struct_ser.end() + let variant = match self { + Self::Ok => "OK", + Self::NotFound => "NOT_FOUND", + Self::NotAllowed => "NOT_ALLOWED", + Self::LimitExceeded => "LIMIT_EXCEEDED", + }; + serializer.serialize_str(variant) } } -impl<'de> serde::Deserialize<'de> for RemoveParticipantResponse { +impl<'de> serde::Deserialize<'de> for request_response::Reason { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ + "OK", + "NOT_FOUND", + "NOT_ALLOWED", + "LIMIT_EXCEEDED", ]; - #[allow(clippy::enum_variant_names)] - enum GeneratedField { - __SkipField__, - } - impl<'de> serde::Deserialize<'de> for GeneratedField { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - struct GeneratedVisitor; + struct GeneratedVisitor; - impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = GeneratedField; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = request_response::Reason; - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(formatter, "expected one of: {:?}", &FIELDS) - } + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } - #[allow(unused_variables)] - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - Ok(GeneratedField::__SkipField__) - } - } - deserializer.deserialize_identifier(GeneratedVisitor) + fn visit_i64(self, v: i64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self) + }) } - } - struct GeneratedVisitor; - impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = RemoveParticipantResponse; - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.RemoveParticipantResponse") + fn visit_u64(self, v: u64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(v), &self) + }) } - fn visit_map(self, mut map_: V) -> std::result::Result - where - V: serde::de::MapAccess<'de>, + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, { - while map_.next_key::()?.is_some() { - let _ = map_.next_value::()?; + match value { + "OK" => Ok(request_response::Reason::Ok), + "NOT_FOUND" => Ok(request_response::Reason::NotFound), + "NOT_ALLOWED" => Ok(request_response::Reason::NotAllowed), + "LIMIT_EXCEEDED" => Ok(request_response::Reason::LimitExceeded), + _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), } - Ok(RemoveParticipantResponse { - }) } } - deserializer.deserialize_struct("livekit.RemoveParticipantResponse", FIELDS, GeneratedVisitor) + deserializer.deserialize_any(GeneratedVisitor) } } impl serde::Serialize for Room { @@ -22646,8 +23118,8 @@ impl serde::Serialize for SignalResponse { signal_response::Message::SubscriptionResponse(v) => { struct_ser.serialize_field("subscriptionResponse", v)?; } - signal_response::Message::ErrorResponse(v) => { - struct_ser.serialize_field("errorResponse", v)?; + signal_response::Message::RequestResponse(v) => { + struct_ser.serialize_field("requestResponse", v)?; } signal_response::Message::TrackSubscribed(v) => { struct_ser.serialize_field("trackSubscribed", v)?; @@ -22695,8 +23167,8 @@ impl<'de> serde::Deserialize<'de> for SignalResponse { "pongResp", "subscription_response", "subscriptionResponse", - "error_response", - "errorResponse", + "request_response", + "requestResponse", "track_subscribed", "trackSubscribed", ]; @@ -22723,7 +23195,7 @@ impl<'de> serde::Deserialize<'de> for SignalResponse { Reconnect, PongResp, SubscriptionResponse, - ErrorResponse, + RequestResponse, TrackSubscribed, __SkipField__, } @@ -22767,7 +23239,7 @@ impl<'de> serde::Deserialize<'de> for SignalResponse { "reconnect" => Ok(GeneratedField::Reconnect), "pongResp" | "pong_resp" => Ok(GeneratedField::PongResp), "subscriptionResponse" | "subscription_response" => Ok(GeneratedField::SubscriptionResponse), - "errorResponse" | "error_response" => Ok(GeneratedField::ErrorResponse), + "requestResponse" | "request_response" => Ok(GeneratedField::RequestResponse), "trackSubscribed" | "track_subscribed" => Ok(GeneratedField::TrackSubscribed), _ => Ok(GeneratedField::__SkipField__), } @@ -22929,11 +23401,11 @@ impl<'de> serde::Deserialize<'de> for SignalResponse { message__ = map_.next_value::<::std::option::Option<_>>()?.map(signal_response::Message::SubscriptionResponse) ; } - GeneratedField::ErrorResponse => { + GeneratedField::RequestResponse => { if message__.is_some() { - return Err(serde::de::Error::duplicate_field("errorResponse")); + return Err(serde::de::Error::duplicate_field("requestResponse")); } - message__ = map_.next_value::<::std::option::Option<_>>()?.map(signal_response::Message::ErrorResponse) + message__ = map_.next_value::<::std::option::Option<_>>()?.map(signal_response::Message::RequestResponse) ; } GeneratedField::TrackSubscribed => { @@ -30315,6 +30787,216 @@ impl<'de> serde::Deserialize<'de> for UserPacket { deserializer.deserialize_struct("livekit.UserPacket", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for Vp8MungerState { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.ext_last_picture_id != 0 { + len += 1; + } + if self.picture_id_used { + len += 1; + } + if self.last_tl0_pic_idx != 0 { + len += 1; + } + if self.tl0_pic_idx_used { + len += 1; + } + if self.tid_used { + len += 1; + } + if self.last_key_idx != 0 { + len += 1; + } + if self.key_idx_used { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.VP8MungerState", len)?; + if self.ext_last_picture_id != 0 { + struct_ser.serialize_field("extLastPictureId", &self.ext_last_picture_id)?; + } + if self.picture_id_used { + struct_ser.serialize_field("pictureIdUsed", &self.picture_id_used)?; + } + if self.last_tl0_pic_idx != 0 { + struct_ser.serialize_field("lastTl0PicIdx", &self.last_tl0_pic_idx)?; + } + if self.tl0_pic_idx_used { + struct_ser.serialize_field("tl0PicIdxUsed", &self.tl0_pic_idx_used)?; + } + if self.tid_used { + struct_ser.serialize_field("tidUsed", &self.tid_used)?; + } + if self.last_key_idx != 0 { + struct_ser.serialize_field("lastKeyIdx", &self.last_key_idx)?; + } + if self.key_idx_used { + struct_ser.serialize_field("keyIdxUsed", &self.key_idx_used)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for Vp8MungerState { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "ext_last_picture_id", + "extLastPictureId", + "picture_id_used", + "pictureIdUsed", + "last_tl0_pic_idx", + "lastTl0PicIdx", + "tl0_pic_idx_used", + "tl0PicIdxUsed", + "tid_used", + "tidUsed", + "last_key_idx", + "lastKeyIdx", + "key_idx_used", + "keyIdxUsed", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + ExtLastPictureId, + PictureIdUsed, + LastTl0PicIdx, + Tl0PicIdxUsed, + TidUsed, + LastKeyIdx, + KeyIdxUsed, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "extLastPictureId" | "ext_last_picture_id" => Ok(GeneratedField::ExtLastPictureId), + "pictureIdUsed" | "picture_id_used" => Ok(GeneratedField::PictureIdUsed), + "lastTl0PicIdx" | "last_tl0_pic_idx" => Ok(GeneratedField::LastTl0PicIdx), + "tl0PicIdxUsed" | "tl0_pic_idx_used" => Ok(GeneratedField::Tl0PicIdxUsed), + "tidUsed" | "tid_used" => Ok(GeneratedField::TidUsed), + "lastKeyIdx" | "last_key_idx" => Ok(GeneratedField::LastKeyIdx), + "keyIdxUsed" | "key_idx_used" => Ok(GeneratedField::KeyIdxUsed), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = Vp8MungerState; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.VP8MungerState") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut ext_last_picture_id__ = None; + let mut picture_id_used__ = None; + let mut last_tl0_pic_idx__ = None; + let mut tl0_pic_idx_used__ = None; + let mut tid_used__ = None; + let mut last_key_idx__ = None; + let mut key_idx_used__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::ExtLastPictureId => { + if ext_last_picture_id__.is_some() { + return Err(serde::de::Error::duplicate_field("extLastPictureId")); + } + ext_last_picture_id__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::PictureIdUsed => { + if picture_id_used__.is_some() { + return Err(serde::de::Error::duplicate_field("pictureIdUsed")); + } + picture_id_used__ = Some(map_.next_value()?); + } + GeneratedField::LastTl0PicIdx => { + if last_tl0_pic_idx__.is_some() { + return Err(serde::de::Error::duplicate_field("lastTl0PicIdx")); + } + last_tl0_pic_idx__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::Tl0PicIdxUsed => { + if tl0_pic_idx_used__.is_some() { + return Err(serde::de::Error::duplicate_field("tl0PicIdxUsed")); + } + tl0_pic_idx_used__ = Some(map_.next_value()?); + } + GeneratedField::TidUsed => { + if tid_used__.is_some() { + return Err(serde::de::Error::duplicate_field("tidUsed")); + } + tid_used__ = Some(map_.next_value()?); + } + GeneratedField::LastKeyIdx => { + if last_key_idx__.is_some() { + return Err(serde::de::Error::duplicate_field("lastKeyIdx")); + } + last_key_idx__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::KeyIdxUsed => { + if key_idx_used__.is_some() { + return Err(serde::de::Error::duplicate_field("keyIdxUsed")); + } + key_idx_used__ = Some(map_.next_value()?); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(Vp8MungerState { + ext_last_picture_id: ext_last_picture_id__.unwrap_or_default(), + picture_id_used: picture_id_used__.unwrap_or_default(), + last_tl0_pic_idx: last_tl0_pic_idx__.unwrap_or_default(), + tl0_pic_idx_used: tl0_pic_idx_used__.unwrap_or_default(), + tid_used: tid_used__.unwrap_or_default(), + last_key_idx: last_key_idx__.unwrap_or_default(), + key_idx_used: key_idx_used__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.VP8MungerState", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for VideoCodec { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result diff --git a/livekit/src/room/mod.rs b/livekit/src/room/mod.rs index bcad3014b..2cd2ead76 100644 --- a/livekit/src/room/mod.rs +++ b/livekit/src/room/mod.rs @@ -69,6 +69,8 @@ pub enum RoomError { TrackAlreadyPublished, #[error("already closed")] AlreadyClosed, + #[error("request error: {reason:?} - {message}")] + Request { reason: proto::request_response::Reason, message: String }, } #[derive(Clone, Debug)] @@ -85,6 +87,9 @@ pub enum RoomEvent { publication: LocalTrackPublication, participant: LocalParticipant, }, + LocalTrackSubscribed { + track: LocalTrack, + }, TrackSubscribed { track: RemoteTrack, publication: RemoteTrackPublication, @@ -611,6 +616,9 @@ impl RoomSession { EngineEvent::ConnectionQuality { updates } => { self.handle_connection_quality_update(updates) } + EngineEvent::LocalTrackSubscribed { track_sid } => { + self.handle_track_subscribed(track_sid) + } } Ok(()) @@ -716,8 +724,13 @@ impl RoomSession { } let (participant_sid, stream_id) = lk_stream_id.unwrap(); + let mut track_id = track.id(); + if stream_id.starts_with("TR") { + track_id = stream_id.into(); + } + let participant_sid: ParticipantSid = participant_sid.to_owned().try_into().unwrap(); - let stream_id = stream_id.to_owned().try_into().unwrap(); + let track_id = track_id.to_owned().try_into().unwrap(); let remote_participant = self .remote_participants @@ -728,7 +741,7 @@ impl RoomSession { if let Some(remote_participant) = remote_participant { livekit_runtime::spawn(async move { - remote_participant.add_subscribed_media_track(stream_id, track, transceiver).await; + remote_participant.add_subscribed_media_track(track_id, track, transceiver).await; }); } else { // The server should send participant updates before sending a new offer, this should @@ -789,6 +802,17 @@ impl RoomSession { } } + /// Handle the first time a participant subscribes to a track + /// Pass this event forward + fn handle_track_subscribed(&self, track_sid: String) { + let publications = self.local_participant.track_publications().clone(); + let publication = publications.get(&track_sid.to_owned().try_into().unwrap()); + if let Some(publication) = publication { + self.dispatcher + .dispatch(&RoomEvent::LocalTrackSubscribed { track: publication.track().unwrap() }); + } + } + async fn send_sync_state(self: &Arc) { let auto_subscribe = self.options.auto_subscribe; let session = self.rtc_engine.session(); diff --git a/livekit/src/room/options.rs b/livekit/src/room/options.rs index 04e86ff06..3d808d29d 100644 --- a/livekit/src/room/options.rs +++ b/livekit/src/room/options.rs @@ -84,6 +84,7 @@ pub struct TrackPublishOptions { pub simulcast: bool, // pub name: String, pub source: TrackSource, + pub stream: String, } impl Default for TrackPublishOptions { @@ -96,6 +97,7 @@ impl Default for TrackPublishOptions { red: true, simulcast: true, source: TrackSource::Unknown, + stream: "".to_string(), } } } diff --git a/livekit/src/room/participant/local_participant.rs b/livekit/src/room/participant/local_participant.rs index ca1339e91..2706d48df 100644 --- a/livekit/src/room/participant/local_participant.rs +++ b/livekit/src/room/participant/local_participant.rs @@ -16,22 +16,27 @@ use std::{ collections::HashMap, fmt::Debug, sync::{self, Arc}, + time::Duration, }; use libwebrtc::rtp_parameters::RtpEncodingParameters; +use livekit_api::signal_client::SignalError; use livekit_protocol as proto; +use livekit_runtime::timeout; use parking_lot::Mutex; +use proto::request_response::Reason; use super::{ConnectionQuality, ParticipantInner, ParticipantKind}; use crate::{ e2ee::EncryptionType, - options, - options::{compute_video_encodings, video_layers_from_encodings, TrackPublishOptions}, + options::{self, compute_video_encodings, video_layers_from_encodings, TrackPublishOptions}, prelude::*, - rtc_engine::RtcEngine, + rtc_engine::{EngineError, RtcEngine}, DataPacket, SipDTMF, Transcription, }; +const REQUEST_TIMEOUT: Duration = Duration::from_secs(5); + type LocalTrackPublishedHandler = Box; type LocalTrackUnpublishedHandler = Box; @@ -186,6 +191,7 @@ impl LocalParticipant { disable_dtx: !options.dtx, disable_red: !options.red, encryption: proto::encryption::Type::from(self.local.encryption_type) as i32, + stream: options.stream.clone(), ..Default::default() }; @@ -236,48 +242,93 @@ impl LocalParticipant { } pub async fn set_metadata(&self, metadata: String) -> RoomResult<()> { - self.inner - .rtc_engine - .send_request(proto::signal_request::Message::UpdateMetadata( - proto::UpdateParticipantMetadata { - metadata, - name: self.name(), - attributes: Default::default(), - ..Default::default() - }, - )) - .await; - Ok(()) + if let Ok(response) = timeout(REQUEST_TIMEOUT, { + let request_id = self.inner.rtc_engine.session().signal_client().next_request_id(); + self.inner + .rtc_engine + .send_request(proto::signal_request::Message::UpdateMetadata( + proto::UpdateParticipantMetadata { + metadata, + name: self.name(), + attributes: Default::default(), + request_id, + ..Default::default() + }, + )) + .await; + self.inner.rtc_engine.get_response(request_id) + }) + .await + { + match response.reason() { + Reason::Ok => Ok(()), + reason => Err(RoomError::Request { reason, message: response.message }), + } + } else { + Err(RoomError::Engine(EngineError::Signal(SignalError::Timeout( + "request timeout".into(), + )))) + } } pub async fn set_attributes(&self, attributes: HashMap) -> RoomResult<()> { - self.inner - .rtc_engine - .send_request(proto::signal_request::Message::UpdateMetadata( - proto::UpdateParticipantMetadata { - attributes, - metadata: self.metadata(), - name: self.name(), - ..Default::default() - }, - )) - .await; - Ok(()) + if let Ok(response) = timeout(REQUEST_TIMEOUT, { + let request_id = self.inner.rtc_engine.session().signal_client().next_request_id(); + self.inner + .rtc_engine + .send_request(proto::signal_request::Message::UpdateMetadata( + proto::UpdateParticipantMetadata { + attributes, + metadata: self.metadata(), + name: self.name(), + request_id, + ..Default::default() + }, + )) + .await; + self.inner.rtc_engine.get_response(request_id) + }) + .await + { + match response.reason() { + Reason::Ok => Ok(()), + reason => Err(RoomError::Request { reason, message: response.message }), + } + } else { + Err(RoomError::Engine(EngineError::Signal(SignalError::Timeout( + "request timeout".into(), + )))) + } } pub async fn set_name(&self, name: String) -> RoomResult<()> { - self.inner - .rtc_engine - .send_request(proto::signal_request::Message::UpdateMetadata( - proto::UpdateParticipantMetadata { - name, - metadata: self.metadata(), - attributes: Default::default(), - ..Default::default() - }, - )) - .await; - Ok(()) + if let Ok(response) = timeout(REQUEST_TIMEOUT, { + let request_id = self.inner.rtc_engine.session().signal_client().next_request_id(); + self.inner + .rtc_engine + .send_request(proto::signal_request::Message::UpdateMetadata( + proto::UpdateParticipantMetadata { + name, + metadata: self.metadata(), + attributes: Default::default(), + request_id, + ..Default::default() + }, + )) + .await; + self.inner.rtc_engine.get_response(request_id) + }) + .await + { + match response.reason() { + Reason::Ok => Ok(()), + reason => Err(RoomError::Request { reason, message: response.message }), + } + } else { + Err(RoomError::Engine(EngineError::Signal(SignalError::Timeout( + "request timeout".into(), + )))) + } } pub async fn unpublish_track( diff --git a/livekit/src/rtc_engine/mod.rs b/livekit/src/rtc_engine/mod.rs index 598786da6..fb49b689b 100644 --- a/livekit/src/rtc_engine/mod.rs +++ b/livekit/src/rtc_engine/mod.rs @@ -136,6 +136,9 @@ pub enum EngineEvent { Disconnected { reason: DisconnectReason, }, + LocalTrackSubscribed { + track_sid: String, + }, } /// Represents a running RtcSession with the ability to close the session @@ -273,6 +276,11 @@ impl RtcEngine { // on fail } + pub async fn get_response(&self, request_id: u32) -> proto::RequestResponse { + let session = self.inner.running_handle.read().session.clone(); + session.get_response(request_id).await + } + pub async fn get_stats(&self) -> EngineResult { let session = self.inner.running_handle.read().session.clone(); session.get_stats().await @@ -393,19 +401,25 @@ impl EngineInner { async fn on_session_event(self: &Arc, event: SessionEvent) -> EngineResult<()> { match event { - SessionEvent::Close { source, reason, can_reconnect, retry_now, full_reconnect } => { + SessionEvent::Close { source, reason, action, retry_now } => { log::debug!("received session close: {}, {:?}", source, reason); - if can_reconnect { - self.reconnection_needed(retry_now, full_reconnect); - } else { - // Spawning a new task because the close function wait for the engine_task to - // finish. (So it doesn't make sense to await it here) - livekit_runtime::spawn({ - let inner = self.clone(); - async move { - inner.close(reason).await; - } - }); + match action { + proto::leave_request::Action::Resume => { + self.reconnection_needed(retry_now, false) + } + proto::leave_request::Action::Reconnect => { + self.reconnection_needed(retry_now, true) + } + proto::leave_request::Action::Disconnect => { + // Spawning a new task because the close function wait for the engine_task to + // finish. (So it doesn't make sense to await it here) + livekit_runtime::spawn({ + let inner = self.clone(); + async move { + inner.close(reason).await; + } + }); + } } } SessionEvent::Data { participant_sid, participant_identity, payload, topic, kind } => { @@ -443,6 +457,9 @@ impl EngineInner { SessionEvent::RoomUpdate { room } => { let _ = self.engine_tx.send(EngineEvent::RoomUpdate { room }); } + SessionEvent::LocalTrackSubscribed { track_sid } => { + let _ = self.engine_tx.send(EngineEvent::LocalTrackSubscribed { track_sid }); + } } Ok(()) } diff --git a/livekit/src/rtc_engine/rtc_session.rs b/livekit/src/rtc_engine/rtc_session.rs index c20a5b24f..7fb4c46d1 100644 --- a/livekit/src/rtc_engine/rtc_session.rs +++ b/livekit/src/rtc_engine/rtc_session.rs @@ -106,11 +106,13 @@ pub enum SessionEvent { RoomUpdate { room: proto::Room, }, + LocalTrackSubscribed { + track_sid: String, + }, Close { source: String, reason: DisconnectReason, - can_reconnect: bool, - full_reconnect: bool, + action: proto::leave_request::Action, retry_now: bool, }, } @@ -148,6 +150,8 @@ struct SessionInner { options: EngineOptions, negotiation_debouncer: Mutex>, + + pending_requests: Mutex>>, } /// This struct holds a WebRTC session @@ -233,6 +237,7 @@ impl RtcSession { emitter, options, negotiation_debouncer: Default::default(), + pending_requests: Default::default(), }); // Start session tasks @@ -336,6 +341,10 @@ impl RtcSession { pub fn data_channel(&self, target: SignalTarget, kind: DataPacketKind) -> Option { self.inner.data_channel(target, kind) } + + pub async fn get_response(&self, request_id: u32) -> proto::RequestResponse { + self.inner.get_response(request_id).await + } } impl SessionInner { @@ -411,9 +420,8 @@ impl SessionInner { self.on_session_disconnected( format!("signal client closed: {:?}", reason).as_str(), DisconnectReason::UnknownReason, - true, + proto::leave_request::Action::Disconnect, false, - false ); } } @@ -471,8 +479,7 @@ impl SessionInner { self.on_session_disconnected( "server request to leave", leave.reason(), - leave.can_reconnect, - true, + leave.action(), true, ); } @@ -499,6 +506,17 @@ impl SessionInner { let _ = self.emitter.send(SessionEvent::RoomUpdate { room: room_update.room.unwrap() }); } + proto::signal_response::Message::TrackSubscribed(track_subscribed) => { + let _ = self.emitter.send(SessionEvent::LocalTrackSubscribed { + track_sid: track_subscribed.track_sid, + }); + } + proto::signal_response::Message::RequestResponse(request_response) => { + let mut pending_requests = self.pending_requests.lock(); + if let Some(tx) = pending_requests.remove(&request_response.request_id) { + let _ = tx.send(request_response); + } + } _ => {} } @@ -530,8 +548,7 @@ impl SessionInner { self.on_session_disconnected( "pc_state failed", DisconnectReason::UnknownReason, - true, - false, + proto::leave_request::Action::Resume, false, ); } @@ -747,23 +764,21 @@ impl SessionInner { &self, source: &str, reason: DisconnectReason, - can_reconnect: bool, + action: proto::leave_request::Action, retry_now: bool, - full_reconnect: bool, ) { let _ = self.emitter.send(SessionEvent::Close { source: source.to_owned(), reason, - can_reconnect, + action, retry_now, - full_reconnect, }); } async fn close(&self) { self.signal_client .send(proto::signal_request::Message::Leave(proto::LeaveRequest { - can_reconnect: false, + action: proto::leave_request::Action::Disconnect.into(), reason: DisconnectReason::ClientInitiated as i32, ..Default::default() })) @@ -778,8 +793,8 @@ impl SessionInner { async fn simulate_scenario(&self, scenario: SimulateScenario) -> EngineResult<()> { let simulate_leave = || { self.on_signal_event(proto::signal_response::Message::Leave(proto::LeaveRequest { + action: proto::leave_request::Action::Reconnect.into(), reason: DisconnectReason::ClientInitiated as i32, - can_reconnect: true, ..Default::default() })) }; @@ -986,6 +1001,12 @@ impl SessionInner { unreachable!() } } + + async fn get_response(&self, request_id: u32) -> proto::RequestResponse { + let (tx, rx) = oneshot::channel(); + self.pending_requests.lock().insert(request_id, tx); + rx.await.unwrap() + } } macro_rules! make_rtc_config { From e8496a987c054a23ca1a5ad0c90797cd37bca5b9 Mon Sep 17 00:00:00 2001 From: aoife cassidy Date: Tue, 20 Aug 2024 13:31:30 -0700 Subject: [PATCH 012/274] bump: livekit 0.5.0, ffi 0.8.0, api 0.4.0 (#397) --- Cargo.lock | 8 ++++---- libwebrtc/Cargo.toml | 2 +- livekit-api/Cargo.toml | 4 ++-- livekit-ffi/Cargo.toml | 8 ++++---- livekit-protocol/Cargo.toml | 2 +- livekit/Cargo.toml | 6 +++--- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bb11f1f2c..ff85c18ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1546,7 +1546,7 @@ checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "livekit" -version = "0.4.1" +version = "0.5.0" dependencies = [ "futures-util", "lazy_static", @@ -1565,7 +1565,7 @@ dependencies = [ [[package]] name = "livekit-api" -version = "0.3.4" +version = "0.4.0" dependencies = [ "async-tungstenite", "base64", @@ -1591,7 +1591,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.7.2" +version = "0.8.0" dependencies = [ "console-subscriber", "dashmap", @@ -1615,7 +1615,7 @@ dependencies = [ [[package]] name = "livekit-protocol" -version = "0.3.4" +version = "0.3.5" dependencies = [ "futures-util", "livekit-runtime", diff --git a/libwebrtc/Cargo.toml b/libwebrtc/Cargo.toml index 17435e46b..d952235d6 100644 --- a/libwebrtc/Cargo.toml +++ b/libwebrtc/Cargo.toml @@ -8,7 +8,7 @@ description = "Livekit safe bindings to libwebrtc" repository = "https://github.com/livekit/rust-sdks" [dependencies] -livekit-protocol = { path = "../livekit-protocol", version = "0.3.4" } +livekit-protocol = { path = "../livekit-protocol", version = "0.3.5" } log = "0.4" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/livekit-api/Cargo.toml b/livekit-api/Cargo.toml index 7ad9cf3db..401ec9b61 100644 --- a/livekit-api/Cargo.toml +++ b/livekit-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-api" -version = "0.3.4" +version = "0.4.0" license = "Apache-2.0" description = "Rust Server SDK for LiveKit" edition = "2021" @@ -65,7 +65,7 @@ rustls-tls-webpki-roots = [ __rustls-tls = ["tokio-tungstenite?/__rustls-tls", "reqwest?/__rustls"] [dependencies] -livekit-protocol = { path = "../livekit-protocol", version = "0.3.4" } +livekit-protocol = { path = "../livekit-protocol", version = "0.3.5" } thiserror = "1.0" serde = { version = "1.0", features = ["derive"] } sha2 = "0.10" diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 501431791..061d0f6d6 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.7.2" +version = "0.8.0" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" @@ -18,8 +18,8 @@ __rustls-tls = ["livekit/__rustls-tls"] tracing = ["tokio/tracing", "console-subscriber"] [dependencies] -livekit = { path = "../livekit", version = "0.4.1" } -livekit-protocol = { path = "../livekit-protocol", version = "0.3.4" } +livekit = { path = "../livekit", version = "0.5.0" } +livekit-protocol = { path = "../livekit-protocol", version = "0.3.5" } tokio = { version = "1", features = ["full", "parking_lot"] } futures-util = { version = "0.3", default-features = false, features = ["sink"] } parking_lot = { version = "0.12", features = ["deadlock_detection"] } @@ -41,7 +41,7 @@ jni = "0.21.1" webrtc-sys-build = { path = "../webrtc-sys/build", version = "0.3.2" } [dev-dependencies] -livekit-api = { path = "../livekit-api", version = "0.3.2" } +livekit-api = { path = "../livekit-api", version = "0.4.0" } [lib] crate-type = ["lib", "cdylib"] diff --git a/livekit-protocol/Cargo.toml b/livekit-protocol/Cargo.toml index d161cf568..7831959a8 100644 --- a/livekit-protocol/Cargo.toml +++ b/livekit-protocol/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-protocol" -version = "0.3.4" +version = "0.3.5" edition = "2021" license = "Apache-2.0" description = "Livekit protocol and utilities for the Rust SDK" diff --git a/livekit/Cargo.toml b/livekit/Cargo.toml index 354e391b8..38df060d9 100644 --- a/livekit/Cargo.toml +++ b/livekit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit" -version = "0.4.1" +version = "0.5.0" edition = "2021" license = "Apache-2.0" description = "Rust Client SDK for LiveKit" @@ -28,9 +28,9 @@ __lk-internal = [] [dependencies] livekit-runtime = { path = "../livekit-runtime", version = "0.3.0", default-features = false } -livekit-api = { path = "../livekit-api", version = "0.3.4", default-features = false } +livekit-api = { path = "../livekit-api", version = "0.4.0", default-features = false } libwebrtc = { path = "../libwebrtc", version = "0.3.4" } -livekit-protocol = { path = "../livekit-protocol", version = "0.3.4" } +livekit-protocol = { path = "../livekit-protocol", version = "0.3.5" } prost = "0.12" serde = { version = "1", features = ["derive"] } serde_json = "1.0" From a7bedb53a20e4b55c44a2be6a179c300c84f9de6 Mon Sep 17 00:00:00 2001 From: aoife cassidy Date: Tue, 20 Aug 2024 14:13:06 -0700 Subject: [PATCH 013/274] ci: remove android builds for FFI for now (#398) --- .github/workflows/ffi-builds.yml | 43 ++++++++++++++++---------------- Cargo.lock | 2 +- livekit-ffi/Cargo.toml | 2 +- 3 files changed, 24 insertions(+), 23 deletions(-) diff --git a/.github/workflows/ffi-builds.yml b/.github/workflows/ffi-builds.yml index 66ccc1418..34a2ea8e4 100644 --- a/.github/workflows/ffi-builds.yml +++ b/.github/workflows/ffi-builds.yml @@ -78,27 +78,28 @@ jobs: dylib: liblivekit_ffi.so target: aarch64-unknown-linux-gnu name: ffi-linux-arm64 - - os: ubuntu-20.04 - platform: android - dylib: liblivekit_ffi.so - jar: libwebrtc.jar - target: aarch64-linux-android - name: ffi-android-arm64 - buildargs: --no-default-features --features "rustls-tls-webpki-roots" - - os: ubuntu-20.04 - platform: android - dylib: liblivekit_ffi.so - jar: libwebrtc.jar - target: armv7-linux-androideabi - name: ffi-android-armv7 - buildargs: --no-default-features --features "rustls-tls-webpki-roots" - - os: ubuntu-20.04 - platform: android - dylib: liblivekit_ffi.so - jar: libwebrtc.jar - target: x86_64-linux-android - name: ffi-android-x86_64 - buildargs: --no-default-features --features "rustls-tls-webpki-roots" + ## android builds broke + # - os: ubuntu-20.04 + # platform: android + # dylib: liblivekit_ffi.so + # jar: libwebrtc.jar + # target: aarch64-linux-android + # name: ffi-android-arm64 + # buildargs: --no-default-features --features "rustls-tls-webpki-roots" + # - os: ubuntu-20.04 + # platform: android + # dylib: liblivekit_ffi.so + # jar: libwebrtc.jar + # target: armv7-linux-androideabi + # name: ffi-android-armv7 + # buildargs: --no-default-features --features "rustls-tls-webpki-roots" + # - os: ubuntu-20.04 + # platform: android + # dylib: liblivekit_ffi.so + # jar: libwebrtc.jar + # target: x86_64-linux-android + # name: ffi-android-x86_64 + # buildargs: --no-default-features --features "rustls-tls-webpki-roots" name: Build (${{ matrix.target }}) runs-on: ${{ matrix.os }} diff --git a/Cargo.lock b/Cargo.lock index ff85c18ce..7798b0a21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1591,7 +1591,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.8.0" +version = "0.8.1" dependencies = [ "console-subscriber", "dashmap", diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 061d0f6d6..982ddc041 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.8.0" +version = "0.8.1" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" From 0465fecc82fab12398c7da1b8a30fd0f87393758 Mon Sep 17 00:00:00 2001 From: aoife cassidy Date: Thu, 22 Aug 2024 15:53:17 -0700 Subject: [PATCH 014/274] patch bump webrtc-sys and dependents (#401) --- libwebrtc/Cargo.toml | 4 ++-- livekit/Cargo.toml | 4 ++-- webrtc-sys/Cargo.toml | 4 ++-- webrtc-sys/build/Cargo.toml | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/libwebrtc/Cargo.toml b/libwebrtc/Cargo.toml index d952235d6..74ed6f6a5 100644 --- a/libwebrtc/Cargo.toml +++ b/libwebrtc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libwebrtc" -version = "0.3.4" +version = "0.3.5" edition = "2021" homepage = "https://livekit.io" license = "Apache-2.0" @@ -18,7 +18,7 @@ thiserror = "1.0" jni = "0.21" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -webrtc-sys = { path = "../webrtc-sys", version = "0.3.2" } +webrtc-sys = { path = "../webrtc-sys", version = "0.3.3" } livekit-runtime = { path = "../livekit-runtime", version = "0.3.0" } lazy_static = "1.4" parking_lot = { version = "0.12" } diff --git a/livekit/Cargo.toml b/livekit/Cargo.toml index 38df060d9..c03f5201d 100644 --- a/livekit/Cargo.toml +++ b/livekit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit" -version = "0.5.0" +version = "0.5.1" edition = "2021" license = "Apache-2.0" description = "Rust Client SDK for LiveKit" @@ -29,7 +29,7 @@ __lk-internal = [] [dependencies] livekit-runtime = { path = "../livekit-runtime", version = "0.3.0", default-features = false } livekit-api = { path = "../livekit-api", version = "0.4.0", default-features = false } -libwebrtc = { path = "../libwebrtc", version = "0.3.4" } +libwebrtc = { path = "../libwebrtc", version = "0.3.5" } livekit-protocol = { path = "../livekit-protocol", version = "0.3.5" } prost = "0.12" serde = { version = "1", features = ["derive"] } diff --git a/webrtc-sys/Cargo.toml b/webrtc-sys/Cargo.toml index 7ab71c449..e1f0a9263 100644 --- a/webrtc-sys/Cargo.toml +++ b/webrtc-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "webrtc-sys" -version = "0.3.2" +version = "0.3.3" edition = "2021" homepage = "https://livekit.io" license = "Apache-2.0" @@ -12,7 +12,7 @@ cxx = "1.0" log = "0.4" [build-dependencies] -webrtc-sys-build = { version = "0.3.2", path = "./build" } +webrtc-sys-build = { version = "0.3.3", path = "./build" } cxx-build = "1.0" glob = "0.3" cc = "1.0" diff --git a/webrtc-sys/build/Cargo.toml b/webrtc-sys/build/Cargo.toml index 6dc600b9e..f86387082 100644 --- a/webrtc-sys/build/Cargo.toml +++ b/webrtc-sys/build/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "webrtc-sys-build" -version = "0.3.2" +version = "0.3.3" edition = "2021" license = "Apache-2.0" description = "Build utilities when working with libwebrtc" From 6816a329bdcefcac9199224aea310ac5a6bc716c Mon Sep 17 00:00:00 2001 From: aoife cassidy Date: Fri, 23 Aug 2024 15:48:30 -0700 Subject: [PATCH 015/274] ffi: force VideoEncoding bitrate and framerate (#405) --- Cargo.lock | 10 +++++----- livekit-ffi/Cargo.toml | 2 +- livekit-ffi/src/server/room.rs | 10 ++++++++++ 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7798b0a21..cd647d60e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1490,7 +1490,7 @@ dependencies = [ [[package]] name = "libwebrtc" -version = "0.3.4" +version = "0.3.5" dependencies = [ "cxx", "env_logger", @@ -1546,7 +1546,7 @@ checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "livekit" -version = "0.5.0" +version = "0.5.1" dependencies = [ "futures-util", "lazy_static", @@ -1591,7 +1591,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.8.1" +version = "0.8.2" dependencies = [ "console-subscriber", "dashmap", @@ -3234,7 +3234,7 @@ checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" [[package]] name = "webrtc-sys" -version = "0.3.2" +version = "0.3.3" dependencies = [ "cc", "cxx", @@ -3247,7 +3247,7 @@ dependencies = [ [[package]] name = "webrtc-sys-build" -version = "0.3.2" +version = "0.3.3" dependencies = [ "fs2", "regex", diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 982ddc041..a27267bbf 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.8.1" +version = "0.8.2" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" diff --git a/livekit-ffi/src/server/room.rs b/livekit-ffi/src/server/room.rs index 700142ba9..967254ecf 100644 --- a/livekit-ffi/src/server/room.rs +++ b/livekit-ffi/src/server/room.rs @@ -403,6 +403,16 @@ impl RoomInner { let track = LocalTrack::try_from(ffi_track.track.clone()) .map_err(|_| FfiError::InvalidRequest("track is not a LocalTrack".into()))?; + // protobuf 3 doesn't let us require fields, instead defaulting to zero. + if publish.options.clone().is_some_and(|opts| { + opts.video_encoding + .is_some_and(|enc| enc.max_framerate == 0.0 || enc.max_bitrate == 0) + }) { + return Err(FfiError::InvalidRequest( + "VideoEncoding must specify both max_framerate and max_bitrate".into(), + )); + } + let publication = inner .room .local_participant() From 0dea73580b5b50f3cb75feafafe5dc669f3470f7 Mon Sep 17 00:00:00 2001 From: CloudWebRTC Date: Tue, 27 Aug 2024 08:53:03 +0800 Subject: [PATCH 016/274] Add more methods for track. (#383) * Add more methods for track. * update. * add DisconnectReason. * update. * Update requests.rs * cargo fmt. --- livekit-ffi/protocol/ffi.proto | 49 ++++++++++-------- livekit-ffi/protocol/track.proto | 20 +++++++ livekit-ffi/src/livekit.proto.rs | 83 ++++++++++++++++++++++-------- livekit-ffi/src/server/requests.rs | 68 +++++++++++++++++++++++- 4 files changed, 175 insertions(+), 45 deletions(-) diff --git a/livekit-ffi/protocol/ffi.proto b/livekit-ffi/protocol/ffi.proto index 6dae560b5..5d4f9bc0a 100644 --- a/livekit-ffi/protocol/ffi.proto +++ b/livekit-ffi/protocol/ffi.proto @@ -73,21 +73,24 @@ message FfiRequest { // Track CreateVideoTrackRequest create_video_track = 15; CreateAudioTrackRequest create_audio_track = 16; - GetStatsRequest get_stats = 17; + LocalTrackMuteRequest local_track_mute = 17; + EnableRemoteTrackRequest enable_remote_track = 18; + GetStatsRequest get_stats = 19; // Video - NewVideoStreamRequest new_video_stream = 18; - NewVideoSourceRequest new_video_source = 19; - CaptureVideoFrameRequest capture_video_frame = 20; - VideoConvertRequest video_convert = 21; + NewVideoStreamRequest new_video_stream = 20; + NewVideoSourceRequest new_video_source = 21; + CaptureVideoFrameRequest capture_video_frame = 22; + VideoConvertRequest video_convert = 23; // Audio - NewAudioStreamRequest new_audio_stream = 23; - NewAudioSourceRequest new_audio_source = 24; - CaptureAudioFrameRequest capture_audio_frame = 25; - NewAudioResamplerRequest new_audio_resampler = 26; - RemixAndResampleRequest remix_and_resample = 27; - E2eeRequest e2ee = 28; + NewAudioStreamRequest new_audio_stream = 24; + NewAudioSourceRequest new_audio_source = 25; + CaptureAudioFrameRequest capture_audio_frame = 26; + NewAudioResamplerRequest new_audio_resampler = 27; + RemixAndResampleRequest remix_and_resample = 28; + + E2eeRequest e2ee = 29; } } @@ -113,21 +116,23 @@ message FfiResponse { // Track CreateVideoTrackResponse create_video_track = 15; CreateAudioTrackResponse create_audio_track = 16; - GetStatsResponse get_stats = 17; + LocalTrackMuteResponse local_track_mute = 17; + EnableRemoteTrackResponse enable_remote_track = 18; + GetStatsResponse get_stats = 19; // Video - NewVideoStreamResponse new_video_stream = 18; - NewVideoSourceResponse new_video_source = 19; - CaptureVideoFrameResponse capture_video_frame = 20; - VideoConvertResponse video_convert = 21; + NewVideoStreamResponse new_video_stream = 20; + NewVideoSourceResponse new_video_source = 21; + CaptureVideoFrameResponse capture_video_frame = 22; + VideoConvertResponse video_convert = 23; // Audio - NewAudioStreamResponse new_audio_stream = 22; - NewAudioSourceResponse new_audio_source = 23; - CaptureAudioFrameResponse capture_audio_frame = 24; - NewAudioResamplerResponse new_audio_resampler = 25; - RemixAndResampleResponse remix_and_resample = 26; - E2eeResponse e2ee = 27; + NewAudioStreamResponse new_audio_stream = 24; + NewAudioSourceResponse new_audio_source = 25; + CaptureAudioFrameResponse capture_audio_frame = 26; + NewAudioResamplerResponse new_audio_resampler = 27; + RemixAndResampleResponse remix_and_resample = 28; + E2eeResponse e2ee = 29; } } diff --git a/livekit-ffi/protocol/track.proto b/livekit-ffi/protocol/track.proto index 38fb799d7..ee9e1ff04 100644 --- a/livekit-ffi/protocol/track.proto +++ b/livekit-ffi/protocol/track.proto @@ -109,3 +109,23 @@ message OwnedTrack { FfiOwnedHandle handle = 1; TrackInfo info = 2; } + +// Mute/UnMute a track +message LocalTrackMuteRequest { + uint64 track_handle = 1; + bool mute = 2; +} + +message LocalTrackMuteResponse { + bool muted = 1; +} + +// Enable/Disable a remote track +message EnableRemoteTrackRequest { + uint64 track_handle = 1; + bool enabled = 2; +} + +message EnableRemoteTrackResponse { + bool enabled = 1; +} diff --git a/livekit-ffi/src/livekit.proto.rs b/livekit-ffi/src/livekit.proto.rs index 5151fdbf1..ce7c11574 100644 --- a/livekit-ffi/src/livekit.proto.rs +++ b/livekit-ffi/src/livekit.proto.rs @@ -1,4 +1,5 @@ // @generated +// This file is @generated by prost-build. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FrameCryptor { @@ -1405,6 +1406,36 @@ pub struct OwnedTrack { #[prost(message, optional, tag="2")] pub info: ::core::option::Option, } +/// Mute/UnMute a track +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct LocalTrackMuteRequest { + #[prost(uint64, tag="1")] + pub track_handle: u64, + #[prost(bool, tag="2")] + pub mute: bool, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct LocalTrackMuteResponse { + #[prost(bool, tag="1")] + pub muted: bool, +} +/// Enable/Disable a remote track +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EnableRemoteTrackRequest { + #[prost(uint64, tag="1")] + pub track_handle: u64, + #[prost(bool, tag="2")] + pub enabled: bool, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EnableRemoteTrackResponse { + #[prost(bool, tag="1")] + pub enabled: bool, +} #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] pub enum TrackKind { @@ -3128,7 +3159,7 @@ impl AudioSourceType { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiRequest { - #[prost(oneof="ffi_request::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 23, 24, 25, 26, 27, 28")] + #[prost(oneof="ffi_request::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29")] pub message: ::core::option::Option, } /// Nested message and enum types in `FfiRequest`. @@ -3169,28 +3200,32 @@ pub mod ffi_request { #[prost(message, tag="16")] CreateAudioTrack(super::CreateAudioTrackRequest), #[prost(message, tag="17")] + LocalTrackMute(super::LocalTrackMuteRequest), + #[prost(message, tag="18")] + EnableRemoteTrack(super::EnableRemoteTrackRequest), + #[prost(message, tag="19")] GetStats(super::GetStatsRequest), /// Video - #[prost(message, tag="18")] + #[prost(message, tag="20")] NewVideoStream(super::NewVideoStreamRequest), - #[prost(message, tag="19")] + #[prost(message, tag="21")] NewVideoSource(super::NewVideoSourceRequest), - #[prost(message, tag="20")] + #[prost(message, tag="22")] CaptureVideoFrame(super::CaptureVideoFrameRequest), - #[prost(message, tag="21")] + #[prost(message, tag="23")] VideoConvert(super::VideoConvertRequest), /// Audio - #[prost(message, tag="23")] - NewAudioStream(super::NewAudioStreamRequest), #[prost(message, tag="24")] - NewAudioSource(super::NewAudioSourceRequest), + NewAudioStream(super::NewAudioStreamRequest), #[prost(message, tag="25")] - CaptureAudioFrame(super::CaptureAudioFrameRequest), + NewAudioSource(super::NewAudioSourceRequest), #[prost(message, tag="26")] - NewAudioResampler(super::NewAudioResamplerRequest), + CaptureAudioFrame(super::CaptureAudioFrameRequest), #[prost(message, tag="27")] - RemixAndResample(super::RemixAndResampleRequest), + NewAudioResampler(super::NewAudioResamplerRequest), #[prost(message, tag="28")] + RemixAndResample(super::RemixAndResampleRequest), + #[prost(message, tag="29")] E2ee(super::E2eeRequest), } } @@ -3198,7 +3233,7 @@ pub mod ffi_request { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiResponse { - #[prost(oneof="ffi_response::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27")] + #[prost(oneof="ffi_response::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29")] pub message: ::core::option::Option, } /// Nested message and enum types in `FfiResponse`. @@ -3239,28 +3274,32 @@ pub mod ffi_response { #[prost(message, tag="16")] CreateAudioTrack(super::CreateAudioTrackResponse), #[prost(message, tag="17")] + LocalTrackMute(super::LocalTrackMuteResponse), + #[prost(message, tag="18")] + EnableRemoteTrack(super::EnableRemoteTrackResponse), + #[prost(message, tag="19")] GetStats(super::GetStatsResponse), /// Video - #[prost(message, tag="18")] + #[prost(message, tag="20")] NewVideoStream(super::NewVideoStreamResponse), - #[prost(message, tag="19")] + #[prost(message, tag="21")] NewVideoSource(super::NewVideoSourceResponse), - #[prost(message, tag="20")] + #[prost(message, tag="22")] CaptureVideoFrame(super::CaptureVideoFrameResponse), - #[prost(message, tag="21")] + #[prost(message, tag="23")] VideoConvert(super::VideoConvertResponse), /// Audio - #[prost(message, tag="22")] + #[prost(message, tag="24")] NewAudioStream(super::NewAudioStreamResponse), - #[prost(message, tag="23")] + #[prost(message, tag="25")] NewAudioSource(super::NewAudioSourceResponse), - #[prost(message, tag="24")] + #[prost(message, tag="26")] CaptureAudioFrame(super::CaptureAudioFrameResponse), - #[prost(message, tag="25")] + #[prost(message, tag="27")] NewAudioResampler(super::NewAudioResamplerResponse), - #[prost(message, tag="26")] + #[prost(message, tag="28")] RemixAndResample(super::RemixAndResampleResponse), - #[prost(message, tag="27")] + #[prost(message, tag="29")] E2ee(super::E2eeResponse), } } diff --git a/livekit-ffi/src/server/requests.rs b/livekit-ffi/src/server/requests.rs index c2e93853d..a13ade6a6 100644 --- a/livekit-ffi/src/server/requests.rs +++ b/livekit-ffi/src/server/requests.rs @@ -26,7 +26,7 @@ use super::{ room::{self, FfiParticipant, FfiPublication, FfiTrack}, video_source, video_stream, FfiError, FfiResult, FfiServer, }; -use crate::proto; +use crate::{conversion::track, proto}; /// Dispose the server, close all rooms and clean up all handles /// It is not mandatory to call this function. @@ -231,6 +231,66 @@ fn on_create_audio_track( }) } +fn on_local_track_mute( + server: &'static FfiServer, + request: proto::LocalTrackMuteRequest, +) -> FfiResult { + let ffi_track = server.retrieve_handle::(request.track_handle)?.clone(); + + let mut muted = false; + match ffi_track.track { + Track::LocalAudio(track) => { + if request.mute { + track.mute(); + } else { + track.unmute(); + } + muted = track.is_muted(); + } + Track::LocalVideo(track) => { + if request.mute { + track.mute(); + } else { + track.unmute(); + } + muted = track.is_muted(); + } + _ => return Err(FfiError::InvalidRequest("track is not a local track".into())), + } + + Ok(proto::LocalTrackMuteResponse { muted: muted }) +} + +fn on_enable_remote_track( + server: &'static FfiServer, + request: proto::EnableRemoteTrackRequest, +) -> FfiResult { + let ffi_track = server.retrieve_handle::(request.track_handle)?.clone(); + + let mut enabled = false; + match ffi_track.track { + Track::RemoteAudio(track) => { + if request.enabled { + track.enable(); + } else { + track.disable(); + } + enabled = track.is_enabled(); + } + Track::RemoteVideo(track) => { + if request.enabled { + track.enable(); + } else { + track.disable(); + } + enabled = track.is_enabled(); + } + _ => return Err(FfiError::InvalidRequest("track is not a remote track".into())), + } + + Ok(proto::EnableRemoteTrackResponse { enabled: enabled }) +} + /// Retrieve the stats from a track fn on_get_stats( server: &'static FfiServer, @@ -627,6 +687,12 @@ pub fn handle_request( proto::ffi_request::Message::CreateAudioTrack(create) => { proto::ffi_response::Message::CreateAudioTrack(on_create_audio_track(server, create)?) } + proto::ffi_request::Message::LocalTrackMute(create) => { + proto::ffi_response::Message::LocalTrackMute(on_local_track_mute(server, create)?) + } + proto::ffi_request::Message::EnableRemoteTrack(create) => { + proto::ffi_response::Message::EnableRemoteTrack(on_enable_remote_track(server, create)?) + } proto::ffi_request::Message::GetStats(get_stats) => { proto::ffi_response::Message::GetStats(on_get_stats(server, get_stats)?) } From 1e5a6acb83de10bb77a0bada9fbfe0bd5ac2bef1 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Thu, 29 Aug 2024 10:30:03 -0700 Subject: [PATCH 017/274] Remove unexpected close error message (#406) This is confusing to be logged at the Rust layer. --- livekit/src/room/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/livekit/src/room/mod.rs b/livekit/src/room/mod.rs index 2cd2ead76..a9bcb24d0 100644 --- a/livekit/src/room/mod.rs +++ b/livekit/src/room/mod.rs @@ -1024,9 +1024,8 @@ impl RoomSession { self.dispatcher.dispatch(&RoomEvent::Disconnected { reason }); } + log::info!("disconnected from room with reason: {:?}", reason); if reason != DisconnectReason::ClientInitiated { - log::error!("unexpectedly disconnected from room: {:?}", reason); - livekit_runtime::spawn({ let inner = self.clone(); async move { From 2e1616eb06116dd74f2ac5ce9f48346d884e5bce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Thu, 29 Aug 2024 13:03:10 -0700 Subject: [PATCH 018/274] audiostream: add resampling options (#412) --- libwebrtc/src/audio_stream.rs | 6 ++-- libwebrtc/src/native/audio_stream.rs | 10 +++--- livekit-ffi/protocol/audio_frame.proto | 2 ++ livekit-ffi/src/livekit.proto.rs | 5 ++- livekit-ffi/src/server/audio_stream.rs | 9 +++++- webrtc-sys/include/livekit/audio_track.h | 16 ++++++++-- webrtc-sys/src/audio_track.cpp | 39 +++++++++++++++++++----- webrtc-sys/src/audio_track.rs | 6 +++- 8 files changed, 75 insertions(+), 18 deletions(-) diff --git a/libwebrtc/src/audio_stream.rs b/libwebrtc/src/audio_stream.rs index 7bbee81ef..cfd242125 100644 --- a/libwebrtc/src/audio_stream.rs +++ b/libwebrtc/src/audio_stream.rs @@ -38,8 +38,10 @@ pub mod native { } impl NativeAudioStream { - pub fn new(audio_track: RtcAudioTrack) -> Self { - Self { handle: stream_imp::NativeAudioStream::new(audio_track) } + pub fn new(audio_track: RtcAudioTrack, sample_rate: i32, num_channels: i32) -> Self { + Self { + handle: stream_imp::NativeAudioStream::new(audio_track, sample_rate, num_channels), + } } pub fn track(&self) -> RtcAudioTrack { diff --git a/libwebrtc/src/native/audio_stream.rs b/libwebrtc/src/native/audio_stream.rs index b3fa184c5..297c7cf5a 100644 --- a/libwebrtc/src/native/audio_stream.rs +++ b/libwebrtc/src/native/audio_stream.rs @@ -32,12 +32,14 @@ pub struct NativeAudioStream { } impl NativeAudioStream { - pub fn new(audio_track: RtcAudioTrack) -> Self { + pub fn new(audio_track: RtcAudioTrack, sample_rate: i32, num_channels: i32) -> Self { let (frame_tx, frame_rx) = mpsc::unbounded_channel(); let observer = Arc::new(AudioTrackObserver { frame_tx }); - let native_sink = sys_at::ffi::new_native_audio_sink(Box::new( - sys_at::AudioSinkWrapper::new(observer.clone()), - )); + let native_sink = sys_at::ffi::new_native_audio_sink( + Box::new(sys_at::AudioSinkWrapper::new(observer.clone())), + sample_rate, + num_channels, + ); let audio = unsafe { sys_at::ffi::media_to_audio(audio_track.sys_handle()) }; audio.add_sink(&native_sink); diff --git a/livekit-ffi/protocol/audio_frame.proto b/livekit-ffi/protocol/audio_frame.proto index dd7be86a2..35f6b8372 100644 --- a/livekit-ffi/protocol/audio_frame.proto +++ b/livekit-ffi/protocol/audio_frame.proto @@ -24,6 +24,8 @@ import "handle.proto"; message NewAudioStreamRequest { uint64 track_handle = 1; AudioStreamType type = 2; + uint32 sample_rate = 3; + uint32 num_channels = 4; } message NewAudioStreamResponse { OwnedAudioStream stream = 1; } diff --git a/livekit-ffi/src/livekit.proto.rs b/livekit-ffi/src/livekit.proto.rs index ce7c11574..141966bba 100644 --- a/livekit-ffi/src/livekit.proto.rs +++ b/livekit-ffi/src/livekit.proto.rs @@ -1,5 +1,4 @@ // @generated -// This file is @generated by prost-build. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FrameCryptor { @@ -2881,6 +2880,10 @@ pub struct NewAudioStreamRequest { pub track_handle: u64, #[prost(enumeration="AudioStreamType", tag="2")] pub r#type: i32, + #[prost(uint32, tag="3")] + pub sample_rate: u32, + #[prost(uint32, tag="4")] + pub num_channels: u32, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] diff --git a/livekit-ffi/src/server/audio_stream.rs b/livekit-ffi/src/server/audio_stream.rs index b5edcad0d..9cb0f6c21 100644 --- a/livekit-ffi/src/server/audio_stream.rs +++ b/livekit-ffi/src/server/audio_stream.rs @@ -57,7 +57,14 @@ impl FfiAudioStream { proto::AudioStreamType::AudioStreamNative => { let audio_stream = Self { handle_id, stream_type, close_tx }; - let native_stream = NativeAudioStream::new(rtc_track); + let sample_rate = + if new_stream.sample_rate == 0 { 48000 } else { new_stream.sample_rate as i32 }; + + let num_channels = + if new_stream.num_channels == 0 { 1 } else { new_stream.num_channels as i32 }; + + let native_stream = + NativeAudioStream::new(rtc_track, sample_rate as i32, num_channels as i32); let handle = server.async_runtime.spawn(Self::native_audio_stream_task( server, handle_id, diff --git a/webrtc-sys/include/livekit/audio_track.h b/webrtc-sys/include/livekit/audio_track.h index a67ca8116..3e074785b 100644 --- a/webrtc-sys/include/livekit/audio_track.h +++ b/webrtc-sys/include/livekit/audio_track.h @@ -18,7 +18,9 @@ #include +#include "api/audio/audio_frame.h" #include "api/audio_options.h" +#include "common_audio/resampler/include/push_resampler.h" #include "livekit/helper.h" #include "livekit/media_stream_track.h" #include "livekit/webrtc.h" @@ -62,7 +64,9 @@ class AudioTrack : public MediaStreamTrack { class NativeAudioSink : public webrtc::AudioTrackSinkInterface { public: - explicit NativeAudioSink(rust::Box observer); + explicit NativeAudioSink(rust::Box observer, + int sample_rate, + int num_channels); void OnData(const void* audio_data, int bits_per_sample, int sample_rate, @@ -71,10 +75,18 @@ class NativeAudioSink : public webrtc::AudioTrackSinkInterface { private: rust::Box observer_; + + int sample_rate_; + int num_channels_; + + webrtc::AudioFrame frame_; + webrtc::PushResampler resampler_; }; std::shared_ptr new_native_audio_sink( - rust::Box observer); + rust::Box observer, + int sample_rate, + int num_channels); class AudioTrackSource { class InternalSource : public webrtc::LocalAudioSource { diff --git a/webrtc-sys/src/audio_track.cpp b/webrtc-sys/src/audio_track.cpp index 951208d24..d7d591fcd 100644 --- a/webrtc-sys/src/audio_track.cpp +++ b/webrtc-sys/src/audio_track.cpp @@ -75,8 +75,12 @@ void AudioTrack::remove_sink( sinks_.erase(std::remove(sinks_.begin(), sinks_.end(), sink), sinks_.end()); } -NativeAudioSink::NativeAudioSink(rust::Box observer) - : observer_(std::move(observer)) {} +NativeAudioSink::NativeAudioSink(rust::Box observer, + int sample_rate, + int num_channels) + : observer_(std::move(observer)), + sample_rate_(sample_rate), + num_channels_(num_channels) {} void NativeAudioSink::OnData(const void* audio_data, int bits_per_sample, @@ -84,14 +88,35 @@ void NativeAudioSink::OnData(const void* audio_data, size_t number_of_channels, size_t number_of_frames) { RTC_CHECK_EQ(16, bits_per_sample); - rust::Slice data(static_cast(audio_data), - number_of_channels * number_of_frames); - observer_->on_data(data, sample_rate, number_of_channels, number_of_frames); + + const int16_t* data = static_cast(audio_data); + + if (sample_rate_ != sample_rate || num_channels_ != number_of_channels) { + // resample/remix before capturing + webrtc::voe::RemixAndResample(data, number_of_frames, number_of_channels, + sample_rate, &resampler_, &frame_); + + rust::Slice rust_slice( + frame_.data(), frame_.num_channels() * frame_.samples_per_channel()); + + observer_->on_data(rust_slice, frame_.sample_rate_hz(), + frame_.num_channels(), frame_.samples_per_channel()); + + } else { + rust::Slice rust_slice( + data, number_of_channels * number_of_frames); + + observer_->on_data(rust_slice, sample_rate, number_of_channels, + number_of_frames); + } } std::shared_ptr new_native_audio_sink( - rust::Box observer) { - return std::make_shared(std::move(observer)); + rust::Box observer, + int sample_rate, + int num_channels) { + return std::make_shared(std::move(observer), sample_rate, + num_channels); } AudioTrackSource::InternalSource::InternalSource( diff --git a/webrtc-sys/src/audio_track.rs b/webrtc-sys/src/audio_track.rs index ee32e475d..26b9f8308 100644 --- a/webrtc-sys/src/audio_track.rs +++ b/webrtc-sys/src/audio_track.rs @@ -40,7 +40,11 @@ pub mod ffi { fn add_sink(self: &AudioTrack, sink: &SharedPtr); fn remove_sink(self: &AudioTrack, sink: &SharedPtr); - fn new_native_audio_sink(observer: Box) -> SharedPtr; + fn new_native_audio_sink( + observer: Box, + sample_rate: i32, + num_channels: i32, + ) -> SharedPtr; fn on_captured_frame( self: &AudioTrackSource, From 5e8101ea20725fabededcc6c5239451da0f35759 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?The=CC=81o=20Monnom?= Date: Thu, 29 Aug 2024 22:31:44 -0700 Subject: [PATCH 019/274] Update audio_track.cpp --- webrtc-sys/src/audio_track.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/webrtc-sys/src/audio_track.cpp b/webrtc-sys/src/audio_track.cpp index d7d591fcd..165f8e9c7 100644 --- a/webrtc-sys/src/audio_track.cpp +++ b/webrtc-sys/src/audio_track.cpp @@ -80,7 +80,10 @@ NativeAudioSink::NativeAudioSink(rust::Box observer, int num_channels) : observer_(std::move(observer)), sample_rate_(sample_rate), - num_channels_(num_channels) {} + num_channels_(num_channels) { + frame_.sample_rate_hz_ = sample_rate; + frame_.num_channels_ = num_channels; +} void NativeAudioSink::OnData(const void* audio_data, int bits_per_sample, From efa94db86a430dc7212455e4533e3edc40d86967 Mon Sep 17 00:00:00 2001 From: Marcus Asteborg Date: Mon, 2 Sep 2024 17:19:30 -0700 Subject: [PATCH 020/274] Fix cargo config path in README.md (#414) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 29feec1f3..e305bd727 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ Use this SDK to add realtime video, audio and data features to your Rust app. By - `livekit-protocol`: LiveKit protocol generated code When adding the SDK as a dependency to your project, make sure to add the -[necessary `rustflags`](https://github.com/livekit/rust-sdks/blob/main/.cargo/config) +[necessary `rustflags`](https://github.com/livekit/rust-sdks/blob/main/.cargo/config.toml) to your cargo config, otherwise linking may fail. Also, please refer to the list of the [supported platform toolkits](https://github.com/livekit/rust-sdks/blob/main/.github/workflows/builds.yml). From 5f4dc2145e6523b3e4ec0fc8819b51ca2b39d12a Mon Sep 17 00:00:00 2001 From: aoife cassidy Date: Mon, 2 Sep 2024 23:59:48 -0700 Subject: [PATCH 021/274] webrtc-sys: re-extract boringssl symbols (#416) --- .../libwebrtc/boringssl_prefix_symbols.txt | 2294 +++++------------ 1 file changed, 713 insertions(+), 1581 deletions(-) diff --git a/webrtc-sys/libwebrtc/boringssl_prefix_symbols.txt b/webrtc-sys/libwebrtc/boringssl_prefix_symbols.txt index 2a17f1a7a..ab6604df3 100644 --- a/webrtc-sys/libwebrtc/boringssl_prefix_symbols.txt +++ b/webrtc-sys/libwebrtc/boringssl_prefix_symbols.txt @@ -1,6 +1,8 @@ -ACCESS_DESCRIPTION_free LK_ACCESS_DESCRIPTION_free -ACCESS_DESCRIPTION_it LK_ACCESS_DESCRIPTION_it -ACCESS_DESCRIPTION_new LK_ACCESS_DESCRIPTION_new +# this list was extracted from the version of BoringSSL used by libwebrtc +# version m114_release, by grepping for OPENSSL_EXPORT: +# +# rg -NIU "^.*OPENSSL_EXPORT .*[ \n]\*?([\d\w_]+)\(.*$" -r '$1' | sort | uniq + AES_CMAC LK_AES_CMAC AES_cbc_encrypt LK_AES_cbc_encrypt AES_cfb128_encrypt LK_AES_cfb128_encrypt @@ -15,43 +17,34 @@ AES_unwrap_key LK_AES_unwrap_key AES_unwrap_key_padded LK_AES_unwrap_key_padded AES_wrap_key LK_AES_wrap_key AES_wrap_key_padded LK_AES_wrap_key_padded -ASN1_ANY_it LK_ASN1_ANY_it ASN1_BIT_STRING_check LK_ASN1_BIT_STRING_check ASN1_BIT_STRING_free LK_ASN1_BIT_STRING_free ASN1_BIT_STRING_get_bit LK_ASN1_BIT_STRING_get_bit -ASN1_BIT_STRING_it LK_ASN1_BIT_STRING_it ASN1_BIT_STRING_new LK_ASN1_BIT_STRING_new ASN1_BIT_STRING_num_bytes LK_ASN1_BIT_STRING_num_bytes ASN1_BIT_STRING_set LK_ASN1_BIT_STRING_set ASN1_BIT_STRING_set_bit LK_ASN1_BIT_STRING_set_bit ASN1_BMPSTRING_free LK_ASN1_BMPSTRING_free -ASN1_BMPSTRING_it LK_ASN1_BMPSTRING_it ASN1_BMPSTRING_new LK_ASN1_BMPSTRING_new -ASN1_BOOLEAN_it LK_ASN1_BOOLEAN_it ASN1_ENUMERATED_free LK_ASN1_ENUMERATED_free ASN1_ENUMERATED_get LK_ASN1_ENUMERATED_get ASN1_ENUMERATED_get_int64 LK_ASN1_ENUMERATED_get_int64 ASN1_ENUMERATED_get_uint64 LK_ASN1_ENUMERATED_get_uint64 -ASN1_ENUMERATED_it LK_ASN1_ENUMERATED_it ASN1_ENUMERATED_new LK_ASN1_ENUMERATED_new ASN1_ENUMERATED_set LK_ASN1_ENUMERATED_set ASN1_ENUMERATED_set_int64 LK_ASN1_ENUMERATED_set_int64 ASN1_ENUMERATED_set_uint64 LK_ASN1_ENUMERATED_set_uint64 ASN1_ENUMERATED_to_BN LK_ASN1_ENUMERATED_to_BN -ASN1_FBOOLEAN_it LK_ASN1_FBOOLEAN_it ASN1_GENERALIZEDTIME_adj LK_ASN1_GENERALIZEDTIME_adj ASN1_GENERALIZEDTIME_check LK_ASN1_GENERALIZEDTIME_check ASN1_GENERALIZEDTIME_free LK_ASN1_GENERALIZEDTIME_free -ASN1_GENERALIZEDTIME_it LK_ASN1_GENERALIZEDTIME_it ASN1_GENERALIZEDTIME_new LK_ASN1_GENERALIZEDTIME_new ASN1_GENERALIZEDTIME_print LK_ASN1_GENERALIZEDTIME_print ASN1_GENERALIZEDTIME_set LK_ASN1_GENERALIZEDTIME_set ASN1_GENERALIZEDTIME_set_string LK_ASN1_GENERALIZEDTIME_set_string ASN1_GENERALSTRING_free LK_ASN1_GENERALSTRING_free -ASN1_GENERALSTRING_it LK_ASN1_GENERALSTRING_it ASN1_GENERALSTRING_new LK_ASN1_GENERALSTRING_new ASN1_IA5STRING_free LK_ASN1_IA5STRING_free -ASN1_IA5STRING_it LK_ASN1_IA5STRING_it ASN1_IA5STRING_new LK_ASN1_IA5STRING_new ASN1_INTEGER_cmp LK_ASN1_INTEGER_cmp ASN1_INTEGER_dup LK_ASN1_INTEGER_dup @@ -59,34 +52,24 @@ ASN1_INTEGER_free LK_ASN1_INTEGER_free ASN1_INTEGER_get LK_ASN1_INTEGER_get ASN1_INTEGER_get_int64 LK_ASN1_INTEGER_get_int64 ASN1_INTEGER_get_uint64 LK_ASN1_INTEGER_get_uint64 -ASN1_INTEGER_it LK_ASN1_INTEGER_it ASN1_INTEGER_new LK_ASN1_INTEGER_new ASN1_INTEGER_set LK_ASN1_INTEGER_set ASN1_INTEGER_set_int64 LK_ASN1_INTEGER_set_int64 ASN1_INTEGER_set_uint64 LK_ASN1_INTEGER_set_uint64 ASN1_INTEGER_to_BN LK_ASN1_INTEGER_to_BN ASN1_NULL_free LK_ASN1_NULL_free -ASN1_NULL_it LK_ASN1_NULL_it ASN1_NULL_new LK_ASN1_NULL_new ASN1_OBJECT_create LK_ASN1_OBJECT_create ASN1_OBJECT_free LK_ASN1_OBJECT_free -ASN1_OBJECT_it LK_ASN1_OBJECT_it -ASN1_OBJECT_new LK_ASN1_OBJECT_new ASN1_OCTET_STRING_cmp LK_ASN1_OCTET_STRING_cmp ASN1_OCTET_STRING_dup LK_ASN1_OCTET_STRING_dup ASN1_OCTET_STRING_free LK_ASN1_OCTET_STRING_free -ASN1_OCTET_STRING_it LK_ASN1_OCTET_STRING_it ASN1_OCTET_STRING_new LK_ASN1_OCTET_STRING_new ASN1_OCTET_STRING_set LK_ASN1_OCTET_STRING_set ASN1_PRINTABLESTRING_free LK_ASN1_PRINTABLESTRING_free -ASN1_PRINTABLESTRING_it LK_ASN1_PRINTABLESTRING_it ASN1_PRINTABLESTRING_new LK_ASN1_PRINTABLESTRING_new ASN1_PRINTABLE_free LK_ASN1_PRINTABLE_free -ASN1_PRINTABLE_it LK_ASN1_PRINTABLE_it ASN1_PRINTABLE_new LK_ASN1_PRINTABLE_new -ASN1_SEQUENCE_ANY_it LK_ASN1_SEQUENCE_ANY_it -ASN1_SEQUENCE_it LK_ASN1_SEQUENCE_it -ASN1_SET_ANY_it LK_ASN1_SET_ANY_it ASN1_STRING_TABLE_add LK_ASN1_STRING_TABLE_add ASN1_STRING_TABLE_cleanup LK_ASN1_STRING_TABLE_cleanup ASN1_STRING_cmp LK_ASN1_STRING_cmp @@ -110,20 +93,16 @@ ASN1_STRING_to_UTF8 LK_ASN1_STRING_to_UTF8 ASN1_STRING_type LK_ASN1_STRING_type ASN1_STRING_type_new LK_ASN1_STRING_type_new ASN1_T61STRING_free LK_ASN1_T61STRING_free -ASN1_T61STRING_it LK_ASN1_T61STRING_it ASN1_T61STRING_new LK_ASN1_T61STRING_new -ASN1_TBOOLEAN_it LK_ASN1_TBOOLEAN_it ASN1_TIME_adj LK_ASN1_TIME_adj ASN1_TIME_check LK_ASN1_TIME_check ASN1_TIME_diff LK_ASN1_TIME_diff ASN1_TIME_free LK_ASN1_TIME_free -ASN1_TIME_it LK_ASN1_TIME_it ASN1_TIME_new LK_ASN1_TIME_new ASN1_TIME_print LK_ASN1_TIME_print ASN1_TIME_set LK_ASN1_TIME_set ASN1_TIME_set_posix LK_ASN1_TIME_set_posix ASN1_TIME_set_string LK_ASN1_TIME_set_string -ASN1_TIME_set_string_X509 LK_ASN1_TIME_set_string_X509 ASN1_TIME_to_generalizedtime LK_ASN1_TIME_to_generalizedtime ASN1_TIME_to_posix LK_ASN1_TIME_to_posix ASN1_TIME_to_time_t LK_ASN1_TIME_to_time_t @@ -134,35 +113,26 @@ ASN1_TYPE_new LK_ASN1_TYPE_new ASN1_TYPE_set LK_ASN1_TYPE_set ASN1_TYPE_set1 LK_ASN1_TYPE_set1 ASN1_UNIVERSALSTRING_free LK_ASN1_UNIVERSALSTRING_free -ASN1_UNIVERSALSTRING_it LK_ASN1_UNIVERSALSTRING_it ASN1_UNIVERSALSTRING_new LK_ASN1_UNIVERSALSTRING_new ASN1_UTCTIME_adj LK_ASN1_UTCTIME_adj ASN1_UTCTIME_check LK_ASN1_UTCTIME_check ASN1_UTCTIME_cmp_time_t LK_ASN1_UTCTIME_cmp_time_t ASN1_UTCTIME_free LK_ASN1_UTCTIME_free -ASN1_UTCTIME_it LK_ASN1_UTCTIME_it ASN1_UTCTIME_new LK_ASN1_UTCTIME_new ASN1_UTCTIME_print LK_ASN1_UTCTIME_print ASN1_UTCTIME_set LK_ASN1_UTCTIME_set ASN1_UTCTIME_set_string LK_ASN1_UTCTIME_set_string ASN1_UTF8STRING_free LK_ASN1_UTF8STRING_free -ASN1_UTF8STRING_it LK_ASN1_UTF8STRING_it ASN1_UTF8STRING_new LK_ASN1_UTF8STRING_new ASN1_VISIBLESTRING_free LK_ASN1_VISIBLESTRING_free -ASN1_VISIBLESTRING_it LK_ASN1_VISIBLESTRING_it ASN1_VISIBLESTRING_new LK_ASN1_VISIBLESTRING_new ASN1_digest LK_ASN1_digest -ASN1_generate_v3 LK_ASN1_generate_v3 ASN1_get_object LK_ASN1_get_object ASN1_item_d2i LK_ASN1_item_d2i ASN1_item_d2i_bio LK_ASN1_item_d2i_bio ASN1_item_d2i_fp LK_ASN1_item_d2i_fp ASN1_item_digest LK_ASN1_item_digest ASN1_item_dup LK_ASN1_item_dup -ASN1_item_ex_d2i LK_ASN1_item_ex_d2i -ASN1_item_ex_free LK_ASN1_item_ex_free -ASN1_item_ex_i2d LK_ASN1_item_ex_i2d -ASN1_item_ex_new LK_ASN1_item_ex_new ASN1_item_free LK_ASN1_item_free ASN1_item_i2d LK_ASN1_item_i2d ASN1_item_i2d_bio LK_ASN1_item_i2d_bio @@ -176,21 +146,10 @@ ASN1_item_verify LK_ASN1_item_verify ASN1_mbstring_copy LK_ASN1_mbstring_copy ASN1_mbstring_ncopy LK_ASN1_mbstring_ncopy ASN1_object_size LK_ASN1_object_size -ASN1_primitive_free LK_ASN1_primitive_free ASN1_put_eoc LK_ASN1_put_eoc ASN1_put_object LK_ASN1_put_object ASN1_tag2bit LK_ASN1_tag2bit ASN1_tag2str LK_ASN1_tag2str -ASN1_template_free LK_ASN1_template_free -AUTHORITY_INFO_ACCESS_free LK_AUTHORITY_INFO_ACCESS_free -AUTHORITY_INFO_ACCESS_it LK_AUTHORITY_INFO_ACCESS_it -AUTHORITY_INFO_ACCESS_new LK_AUTHORITY_INFO_ACCESS_new -AUTHORITY_KEYID_free LK_AUTHORITY_KEYID_free -AUTHORITY_KEYID_it LK_AUTHORITY_KEYID_it -AUTHORITY_KEYID_new LK_AUTHORITY_KEYID_new -BASIC_CONSTRAINTS_free LK_BASIC_CONSTRAINTS_free -BASIC_CONSTRAINTS_it LK_BASIC_CONSTRAINTS_it -BASIC_CONSTRAINTS_new LK_BASIC_CONSTRAINTS_new BF_cbc_encrypt LK_BF_cbc_encrypt BF_decrypt LK_BF_decrypt BF_ecb_encrypt LK_BF_ecb_encrypt @@ -208,13 +167,12 @@ BIO_ctrl_pending LK_BIO_ctrl_pending BIO_do_connect LK_BIO_do_connect BIO_eof LK_BIO_eof BIO_f_base64 LK_BIO_f_base64 +BIO_f_ssl LK_BIO_f_ssl BIO_find_type LK_BIO_find_type BIO_flush LK_BIO_flush BIO_free LK_BIO_free BIO_free_all LK_BIO_free_all BIO_get_data LK_BIO_get_data -BIO_get_ex_data LK_BIO_get_ex_data -BIO_get_ex_new_index LK_BIO_get_ex_new_index BIO_get_fd LK_BIO_get_fd BIO_get_fp LK_BIO_get_fp BIO_get_init LK_BIO_get_init @@ -272,7 +230,6 @@ BIO_set_conn_hostname LK_BIO_set_conn_hostname BIO_set_conn_int_port LK_BIO_set_conn_int_port BIO_set_conn_port LK_BIO_set_conn_port BIO_set_data LK_BIO_set_data -BIO_set_ex_data LK_BIO_set_ex_data BIO_set_fd LK_BIO_set_fd BIO_set_flags LK_BIO_set_flags BIO_set_fp LK_BIO_set_fp @@ -285,6 +242,7 @@ BIO_set_retry_reason LK_BIO_set_retry_reason BIO_set_retry_special LK_BIO_set_retry_special BIO_set_retry_write LK_BIO_set_retry_write BIO_set_shutdown LK_BIO_set_shutdown +BIO_set_ssl LK_BIO_set_ssl BIO_set_write_buffer_size LK_BIO_set_write_buffer_size BIO_should_io_special LK_BIO_should_io_special BIO_should_read LK_BIO_should_read @@ -305,11 +263,6 @@ BLAKE2B256 LK_BLAKE2B256 BLAKE2B256_Final LK_BLAKE2B256_Final BLAKE2B256_Init LK_BLAKE2B256_Init BLAKE2B256_Update LK_BLAKE2B256_Update -BN_BLINDING_convert LK_BN_BLINDING_convert -BN_BLINDING_free LK_BN_BLINDING_free -BN_BLINDING_invalidate LK_BN_BLINDING_invalidate -BN_BLINDING_invert LK_BN_BLINDING_invert -BN_BLINDING_new LK_BN_BLINDING_new BN_CTX_end LK_BN_CTX_end BN_CTX_free LK_BN_CTX_free BN_CTX_get LK_BN_CTX_get @@ -326,7 +279,6 @@ BN_MONT_CTX_new LK_BN_MONT_CTX_new BN_MONT_CTX_new_consttime LK_BN_MONT_CTX_new_consttime BN_MONT_CTX_new_for_modulus LK_BN_MONT_CTX_new_for_modulus BN_MONT_CTX_set LK_BN_MONT_CTX_set -BN_MONT_CTX_set_locked LK_BN_MONT_CTX_set_locked BN_abs_is_word LK_BN_abs_is_word BN_add LK_BN_add BN_add_word LK_BN_add_word @@ -339,7 +291,6 @@ BN_bn2cbb_padded LK_BN_bn2cbb_padded BN_bn2dec LK_BN_bn2dec BN_bn2hex LK_BN_bn2hex BN_bn2le_padded LK_BN_bn2le_padded -BN_bn2lebinpad LK_BN_bn2lebinpad BN_bn2mpi LK_BN_bn2mpi BN_clear LK_BN_clear BN_clear_bit LK_BN_clear_bit @@ -379,7 +330,6 @@ BN_is_prime_fasttest_ex LK_BN_is_prime_fasttest_ex BN_is_word LK_BN_is_word BN_is_zero LK_BN_is_zero BN_le2bn LK_BN_le2bn -BN_lebin2bn LK_BN_lebin2bn BN_lshift LK_BN_lshift BN_lshift1 LK_BN_lshift1 BN_marshal_asn1 LK_BN_marshal_asn1 @@ -393,7 +343,6 @@ BN_mod_exp_mont_consttime LK_BN_mod_exp_mont_consttime BN_mod_exp_mont_word LK_BN_mod_exp_mont_word BN_mod_inverse LK_BN_mod_inverse BN_mod_inverse_blinded LK_BN_mod_inverse_blinded -BN_mod_inverse_odd LK_BN_mod_inverse_odd BN_mod_lshift LK_BN_mod_lshift BN_mod_lshift1 LK_BN_mod_lshift1 BN_mod_lshift1_quick LK_BN_mod_lshift1_quick @@ -444,9 +393,8 @@ BN_ucmp LK_BN_ucmp BN_usub LK_BN_usub BN_value_one LK_BN_value_one BN_zero LK_BN_zero -BORINGSSL_function_hit LK_BORINGSSL_function_hit +BORINGSSL_integrity_test LK_BORINGSSL_integrity_test BORINGSSL_keccak LK_BORINGSSL_keccak -BORINGSSL_keccak_absorb LK_BORINGSSL_keccak_absorb BORINGSSL_keccak_init LK_BORINGSSL_keccak_init BORINGSSL_keccak_squeeze LK_BORINGSSL_keccak_squeeze BORINGSSL_self_test LK_BORINGSSL_self_test @@ -462,20 +410,13 @@ BUF_strlcat LK_BUF_strlcat BUF_strlcpy LK_BUF_strlcpy BUF_strndup LK_BUF_strndup BUF_strnlen LK_BUF_strnlen -CAST_S_table0 LK_CAST_S_table0 -CAST_S_table1 LK_CAST_S_table1 -CAST_S_table2 LK_CAST_S_table2 -CAST_S_table3 LK_CAST_S_table3 -CAST_S_table4 LK_CAST_S_table4 -CAST_S_table5 LK_CAST_S_table5 -CAST_S_table6 LK_CAST_S_table6 -CAST_S_table7 LK_CAST_S_table7 CAST_cbc_encrypt LK_CAST_cbc_encrypt CAST_cfb64_encrypt LK_CAST_cfb64_encrypt CAST_decrypt LK_CAST_decrypt CAST_ecb_encrypt LK_CAST_ecb_encrypt CAST_encrypt LK_CAST_encrypt CAST_set_key LK_CAST_set_key +CBBFinishArray LK_CBBFinishArray CBB_add_asn1 LK_CBB_add_asn1 CBB_add_asn1_bool LK_CBB_add_asn1_bool CBB_add_asn1_int64 LK_CBB_add_asn1_int64 @@ -485,7 +426,6 @@ CBB_add_asn1_oid_from_text LK_CBB_add_asn1_oid_from_text CBB_add_asn1_uint64 LK_CBB_add_asn1_uint64 CBB_add_asn1_uint64_with_tag LK_CBB_add_asn1_uint64_with_tag CBB_add_bytes LK_CBB_add_bytes -CBB_add_latin1 LK_CBB_add_latin1 CBB_add_space LK_CBB_add_space CBB_add_u16 LK_CBB_add_u16 CBB_add_u16_length_prefixed LK_CBB_add_u16_length_prefixed @@ -498,19 +438,14 @@ CBB_add_u64 LK_CBB_add_u64 CBB_add_u64le LK_CBB_add_u64le CBB_add_u8 LK_CBB_add_u8 CBB_add_u8_length_prefixed LK_CBB_add_u8_length_prefixed -CBB_add_ucs2_be LK_CBB_add_ucs2_be -CBB_add_utf32_be LK_CBB_add_utf32_be -CBB_add_utf8 LK_CBB_add_utf8 CBB_add_zeros LK_CBB_add_zeros CBB_cleanup LK_CBB_cleanup CBB_data LK_CBB_data CBB_did_write LK_CBB_did_write CBB_discard_child LK_CBB_discard_child CBB_finish LK_CBB_finish -CBB_finish_i2d LK_CBB_finish_i2d CBB_flush LK_CBB_flush CBB_flush_asn1_set_of LK_CBB_flush_asn1_set_of -CBB_get_utf8_len LK_CBB_get_utf8_len CBB_init LK_CBB_init CBB_init_fixed LK_CBB_init_fixed CBB_len LK_CBB_len @@ -521,6 +456,7 @@ CBS_asn1_bitstring_has_bit LK_CBS_asn1_bitstring_has_bit CBS_asn1_oid_to_text LK_CBS_asn1_oid_to_text CBS_contains_zero_byte LK_CBS_contains_zero_byte CBS_copy_bytes LK_CBS_copy_bytes +CBS_data LK_CBS_data CBS_get_any_asn1 LK_CBS_get_any_asn1 CBS_get_any_asn1_element LK_CBS_get_any_asn1_element CBS_get_any_ber_asn1_element LK_CBS_get_any_ber_asn1_element @@ -532,7 +468,6 @@ CBS_get_asn1_int64 LK_CBS_get_asn1_int64 CBS_get_asn1_uint64 LK_CBS_get_asn1_uint64 CBS_get_bytes LK_CBS_get_bytes CBS_get_last_u8 LK_CBS_get_last_u8 -CBS_get_latin1 LK_CBS_get_latin1 CBS_get_optional_asn1 LK_CBS_get_optional_asn1 CBS_get_optional_asn1_bool LK_CBS_get_optional_asn1_bool CBS_get_optional_asn1_octet_string LK_CBS_get_optional_asn1_octet_string @@ -549,14 +484,13 @@ CBS_get_u64_decimal LK_CBS_get_u64_decimal CBS_get_u64le LK_CBS_get_u64le CBS_get_u8 LK_CBS_get_u8 CBS_get_u8_length_prefixed LK_CBS_get_u8_length_prefixed -CBS_get_ucs2_be LK_CBS_get_ucs2_be CBS_get_until_first LK_CBS_get_until_first -CBS_get_utf32_be LK_CBS_get_utf32_be -CBS_get_utf8 LK_CBS_get_utf8 +CBS_init LK_CBS_init CBS_is_unsigned_asn1_integer LK_CBS_is_unsigned_asn1_integer CBS_is_valid_asn1_bitstring LK_CBS_is_valid_asn1_bitstring CBS_is_valid_asn1_integer LK_CBS_is_valid_asn1_integer CBS_is_valid_asn1_oid LK_CBS_is_valid_asn1_oid +CBS_len LK_CBS_len CBS_mem_equal LK_CBS_mem_equal CBS_parse_generalized_time LK_CBS_parse_generalized_time CBS_parse_utc_time LK_CBS_parse_utc_time @@ -564,9 +498,6 @@ CBS_peek_asn1_tag LK_CBS_peek_asn1_tag CBS_skip LK_CBS_skip CBS_stow LK_CBS_stow CBS_strdup LK_CBS_strdup -CERTIFICATEPOLICIES_free LK_CERTIFICATEPOLICIES_free -CERTIFICATEPOLICIES_it LK_CERTIFICATEPOLICIES_it -CERTIFICATEPOLICIES_new LK_CERTIFICATEPOLICIES_new CMAC_CTX_copy LK_CMAC_CTX_copy CMAC_CTX_free LK_CMAC_CTX_free CMAC_CTX_new LK_CMAC_CTX_new @@ -574,13 +505,9 @@ CMAC_Final LK_CMAC_Final CMAC_Init LK_CMAC_Init CMAC_Reset LK_CMAC_Reset CMAC_Update LK_CMAC_Update -CONF_VALUE_new LK_CONF_VALUE_new CONF_modules_free LK_CONF_modules_free CONF_modules_load_file LK_CONF_modules_load_file CONF_parse_list LK_CONF_parse_list -CRL_DIST_POINTS_free LK_CRL_DIST_POINTS_free -CRL_DIST_POINTS_it LK_CRL_DIST_POINTS_it -CRL_DIST_POINTS_new LK_CRL_DIST_POINTS_new CRYPTO_BUFFER_POOL_free LK_CRYPTO_BUFFER_POOL_free CRYPTO_BUFFER_POOL_new LK_CRYPTO_BUFFER_POOL_new CRYPTO_BUFFER_alloc LK_CRYPTO_BUFFER_alloc @@ -598,23 +525,16 @@ CRYPTO_MUTEX_lock_read LK_CRYPTO_MUTEX_lock_read CRYPTO_MUTEX_lock_write LK_CRYPTO_MUTEX_lock_write CRYPTO_MUTEX_unlock_read LK_CRYPTO_MUTEX_unlock_read CRYPTO_MUTEX_unlock_write LK_CRYPTO_MUTEX_unlock_write -CRYPTO_POLYVAL_finish LK_CRYPTO_POLYVAL_finish -CRYPTO_POLYVAL_init LK_CRYPTO_POLYVAL_init -CRYPTO_POLYVAL_update_blocks LK_CRYPTO_POLYVAL_update_blocks +CRYPTO_STATIC_MUTEX_lock_read LK_CRYPTO_STATIC_MUTEX_lock_read +CRYPTO_STATIC_MUTEX_lock_write LK_CRYPTO_STATIC_MUTEX_lock_write +CRYPTO_STATIC_MUTEX_unlock_read LK_CRYPTO_STATIC_MUTEX_unlock_read +CRYPTO_STATIC_MUTEX_unlock_write LK_CRYPTO_STATIC_MUTEX_unlock_write CRYPTO_THREADID_current LK_CRYPTO_THREADID_current CRYPTO_THREADID_set_callback LK_CRYPTO_THREADID_set_callback CRYPTO_THREADID_set_numeric LK_CRYPTO_THREADID_set_numeric CRYPTO_THREADID_set_pointer LK_CRYPTO_THREADID_set_pointer -CRYPTO_cbc128_decrypt LK_CRYPTO_cbc128_decrypt -CRYPTO_cbc128_encrypt LK_CRYPTO_cbc128_encrypt -CRYPTO_cfb128_1_encrypt LK_CRYPTO_cfb128_1_encrypt -CRYPTO_cfb128_8_encrypt LK_CRYPTO_cfb128_8_encrypt -CRYPTO_cfb128_encrypt LK_CRYPTO_cfb128_encrypt CRYPTO_chacha_20 LK_CRYPTO_chacha_20 CRYPTO_cleanup_all_ex_data LK_CRYPTO_cleanup_all_ex_data -CRYPTO_ctr128_encrypt LK_CRYPTO_ctr128_encrypt -CRYPTO_ctr128_encrypt_ctr32 LK_CRYPTO_ctr128_encrypt_ctr32 -CRYPTO_fips_186_2_prf LK_CRYPTO_fips_186_2_prf CRYPTO_fork_detect_force_madv_wipeonfork_for_testing LK_CRYPTO_fork_detect_force_madv_wipeonfork_for_testing CRYPTO_free LK_CRYPTO_free CRYPTO_free_ex_data LK_CRYPTO_free_ex_data @@ -627,34 +547,27 @@ CRYPTO_gcm128_finish LK_CRYPTO_gcm128_finish CRYPTO_gcm128_init_key LK_CRYPTO_gcm128_init_key CRYPTO_gcm128_setiv LK_CRYPTO_gcm128_setiv CRYPTO_gcm128_tag LK_CRYPTO_gcm128_tag -CRYPTO_get_dynlock_create_callback LK_CRYPTO_get_dynlock_create_callback -CRYPTO_get_dynlock_destroy_callback LK_CRYPTO_get_dynlock_destroy_callback -CRYPTO_get_dynlock_lock_callback LK_CRYPTO_get_dynlock_lock_callback CRYPTO_get_ex_data LK_CRYPTO_get_ex_data -CRYPTO_get_ex_new_index_ex LK_CRYPTO_get_ex_new_index_ex +CRYPTO_get_ex_new_index LK_CRYPTO_get_ex_new_index CRYPTO_get_fork_generation LK_CRYPTO_get_fork_generation CRYPTO_get_lock_name LK_CRYPTO_get_lock_name -CRYPTO_get_locking_callback LK_CRYPTO_get_locking_callback CRYPTO_get_thread_local LK_CRYPTO_get_thread_local -CRYPTO_ghash_init LK_CRYPTO_ghash_init CRYPTO_has_asm LK_CRYPTO_has_asm -CRYPTO_hchacha20 LK_CRYPTO_hchacha20 -CRYPTO_init_sysrand LK_CRYPTO_init_sysrand +CRYPTO_has_broken_NEON LK_CRYPTO_has_broken_NEON +CRYPTO_is_NEON_capable_at_runtime LK_CRYPTO_is_NEON_capable_at_runtime CRYPTO_is_confidential_build LK_CRYPTO_is_confidential_build CRYPTO_library_init LK_CRYPTO_library_init CRYPTO_malloc LK_CRYPTO_malloc CRYPTO_malloc_init LK_CRYPTO_malloc_init CRYPTO_memcmp LK_CRYPTO_memcmp +CRYPTO_needs_hwcap2_workaround LK_CRYPTO_needs_hwcap2_workaround CRYPTO_new_ex_data LK_CRYPTO_new_ex_data CRYPTO_num_locks LK_CRYPTO_num_locks -CRYPTO_ofb128_encrypt LK_CRYPTO_ofb128_encrypt CRYPTO_once LK_CRYPTO_once CRYPTO_poly1305_finish LK_CRYPTO_poly1305_finish CRYPTO_poly1305_init LK_CRYPTO_poly1305_init CRYPTO_poly1305_update LK_CRYPTO_poly1305_update CRYPTO_pre_sandbox_init LK_CRYPTO_pre_sandbox_init -CRYPTO_rdrand LK_CRYPTO_rdrand -CRYPTO_rdrand_multiple8_buf LK_CRYPTO_rdrand_multiple8_buf CRYPTO_realloc LK_CRYPTO_realloc CRYPTO_refcount_dec_and_test_zero LK_CRYPTO_refcount_dec_and_test_zero CRYPTO_refcount_inc LK_CRYPTO_refcount_inc @@ -669,9 +582,6 @@ CRYPTO_set_ex_data LK_CRYPTO_set_ex_data CRYPTO_set_id_callback LK_CRYPTO_set_id_callback CRYPTO_set_locking_callback LK_CRYPTO_set_locking_callback CRYPTO_set_thread_local LK_CRYPTO_set_thread_local -CRYPTO_sysrand LK_CRYPTO_sysrand -CRYPTO_sysrand_for_seed LK_CRYPTO_sysrand_for_seed -CRYPTO_sysrand_if_available LK_CRYPTO_sysrand_if_available CRYPTO_tls13_hkdf_expand_label LK_CRYPTO_tls13_hkdf_expand_label CRYPTO_tls1_prf LK_CRYPTO_tls1_prf CTR_DRBG_clear LK_CTR_DRBG_clear @@ -680,25 +590,16 @@ CTR_DRBG_generate LK_CTR_DRBG_generate CTR_DRBG_init LK_CTR_DRBG_init CTR_DRBG_new LK_CTR_DRBG_new CTR_DRBG_reseed LK_CTR_DRBG_reseed -ChaCha20_ctr32_avx2 LK_ChaCha20_ctr32_avx2 -ChaCha20_ctr32_nohw LK_ChaCha20_ctr32_nohw -ChaCha20_ctr32_ssse3 LK_ChaCha20_ctr32_ssse3 -ChaCha20_ctr32_ssse3_4x LK_ChaCha20_ctr32_ssse3_4x DES_decrypt3 LK_DES_decrypt3 DES_ecb3_encrypt LK_DES_ecb3_encrypt -DES_ecb3_encrypt_ex LK_DES_ecb3_encrypt_ex DES_ecb_encrypt LK_DES_ecb_encrypt -DES_ecb_encrypt_ex LK_DES_ecb_encrypt_ex DES_ede2_cbc_encrypt LK_DES_ede2_cbc_encrypt DES_ede3_cbc_encrypt LK_DES_ede3_cbc_encrypt -DES_ede3_cbc_encrypt_ex LK_DES_ede3_cbc_encrypt_ex DES_ede3_cfb64_encrypt LK_DES_ede3_cfb64_encrypt DES_ede3_cfb_encrypt LK_DES_ede3_cfb_encrypt DES_encrypt3 LK_DES_encrypt3 DES_ncbc_encrypt LK_DES_ncbc_encrypt -DES_ncbc_encrypt_ex LK_DES_ncbc_encrypt_ex DES_set_key LK_DES_set_key -DES_set_key_ex LK_DES_set_key_ex DES_set_key_unchecked LK_DES_set_key_unchecked DES_set_odd_parity LK_DES_set_odd_parity DH_bits LK_DH_bits @@ -730,17 +631,9 @@ DH_size LK_DH_size DH_up_ref LK_DH_up_ref DHparams_dup LK_DHparams_dup DIRECTORYSTRING_free LK_DIRECTORYSTRING_free -DIRECTORYSTRING_it LK_DIRECTORYSTRING_it DIRECTORYSTRING_new LK_DIRECTORYSTRING_new DISPLAYTEXT_free LK_DISPLAYTEXT_free -DISPLAYTEXT_it LK_DISPLAYTEXT_it DISPLAYTEXT_new LK_DISPLAYTEXT_new -DIST_POINT_NAME_free LK_DIST_POINT_NAME_free -DIST_POINT_NAME_it LK_DIST_POINT_NAME_it -DIST_POINT_NAME_new LK_DIST_POINT_NAME_new -DIST_POINT_free LK_DIST_POINT_free -DIST_POINT_it LK_DIST_POINT_it -DIST_POINT_new LK_DIST_POINT_new DIST_POINT_set_dpname LK_DIST_POINT_set_dpname DSA_SIG_free LK_DSA_SIG_free DSA_SIG_get0 LK_DSA_SIG_get0 @@ -782,7 +675,19 @@ DSA_size LK_DSA_size DSA_up_ref LK_DSA_up_ref DSA_verify LK_DSA_verify DSAparams_dup LK_DSAparams_dup -DeleteThreadLocalValue LK_DeleteThreadLocalValue +DTLS_client_method LK_DTLS_client_method +DTLS_method LK_DTLS_method +DTLS_server_method LK_DTLS_server_method +DTLS_with_buffers_method LK_DTLS_with_buffers_method +DTLSv1_2_client_method LK_DTLSv1_2_client_method +DTLSv1_2_method LK_DTLSv1_2_method +DTLSv1_2_server_method LK_DTLSv1_2_server_method +DTLSv1_client_method LK_DTLSv1_client_method +DTLSv1_get_timeout LK_DTLSv1_get_timeout +DTLSv1_handle_timeout LK_DTLSv1_handle_timeout +DTLSv1_method LK_DTLSv1_method +DTLSv1_server_method LK_DTLSv1_server_method +DTLSv1_set_initial_timeout_duration LK_DTLSv1_set_initial_timeout_duration ECDH_compute_key LK_ECDH_compute_key ECDH_compute_key_fips LK_ECDH_compute_key_fips ECDSA_SIG_free LK_ECDSA_SIG_free @@ -802,10 +707,6 @@ ECDSA_sign LK_ECDSA_sign ECDSA_sign_with_nonce_and_leak_private_key_for_testing LK_ECDSA_sign_with_nonce_and_leak_private_key_for_testing ECDSA_size LK_ECDSA_size ECDSA_verify LK_ECDSA_verify -EC_GFp_mont_method LK_EC_GFp_mont_method -EC_GFp_nistp224_method LK_EC_GFp_nistp224_method -EC_GFp_nistp256_method LK_EC_GFp_nistp256_method -EC_GFp_nistz256_method LK_EC_GFp_nistz256_method EC_GROUP_cmp LK_EC_GROUP_cmp EC_GROUP_dup LK_EC_GROUP_dup EC_GROUP_free LK_EC_GROUP_free @@ -887,19 +788,12 @@ EC_POINT_set_to_infinity LK_EC_POINT_set_to_infinity EC_curve_nid2nist LK_EC_curve_nid2nist EC_curve_nist2nid LK_EC_curve_nist2nid EC_get_builtin_curves LK_EC_get_builtin_curves -EC_group_p224 LK_EC_group_p224 -EC_group_p256 LK_EC_group_p256 -EC_group_p384 LK_EC_group_p384 -EC_group_p521 LK_EC_group_p521 EC_hash_to_curve_p256_xmd_sha256_sswu LK_EC_hash_to_curve_p256_xmd_sha256_sswu EC_hash_to_curve_p384_xmd_sha384_sswu LK_EC_hash_to_curve_p384_xmd_sha384_sswu ED25519_keypair LK_ED25519_keypair ED25519_keypair_from_seed LK_ED25519_keypair_from_seed ED25519_sign LK_ED25519_sign ED25519_verify LK_ED25519_verify -EDIPARTYNAME_free LK_EDIPARTYNAME_free -EDIPARTYNAME_it LK_EDIPARTYNAME_it -EDIPARTYNAME_new LK_EDIPARTYNAME_new ENGINE_free LK_ENGINE_free ENGINE_get_ECDSA_method LK_ENGINE_get_ECDSA_method ENGINE_get_RSA_method LK_ENGINE_get_RSA_method @@ -922,10 +816,10 @@ ERR_get_error_line LK_ERR_get_error_line ERR_get_error_line_data LK_ERR_get_error_line_data ERR_get_next_error_library LK_ERR_get_next_error_library ERR_lib_error_string LK_ERR_lib_error_string -ERR_lib_symbol_name LK_ERR_lib_symbol_name ERR_load_BIO_strings LK_ERR_load_BIO_strings ERR_load_ERR_strings LK_ERR_load_ERR_strings ERR_load_RAND_strings LK_ERR_load_RAND_strings +ERR_load_SSL_strings LK_ERR_load_SSL_strings ERR_load_crypto_strings LK_ERR_load_crypto_strings ERR_peek_error LK_ERR_peek_error ERR_peek_error_line LK_ERR_peek_error_line @@ -939,7 +833,6 @@ ERR_print_errors_cb LK_ERR_print_errors_cb ERR_print_errors_fp LK_ERR_print_errors_fp ERR_put_error LK_ERR_put_error ERR_reason_error_string LK_ERR_reason_error_string -ERR_reason_symbol_name LK_ERR_reason_symbol_name ERR_remove_state LK_ERR_remove_state ERR_remove_thread_state LK_ERR_remove_thread_state ERR_restore_state LK_ERR_restore_state @@ -1047,9 +940,6 @@ EVP_HPKE_CTX_max_overhead LK_EVP_HPKE_CTX_max_overhead EVP_HPKE_CTX_new LK_EVP_HPKE_CTX_new EVP_HPKE_CTX_open LK_EVP_HPKE_CTX_open EVP_HPKE_CTX_seal LK_EVP_HPKE_CTX_seal -EVP_HPKE_CTX_setup_auth_recipient LK_EVP_HPKE_CTX_setup_auth_recipient -EVP_HPKE_CTX_setup_auth_sender LK_EVP_HPKE_CTX_setup_auth_sender -EVP_HPKE_CTX_setup_auth_sender_with_seed_for_testing LK_EVP_HPKE_CTX_setup_auth_sender_with_seed_for_testing EVP_HPKE_CTX_setup_recipient LK_EVP_HPKE_CTX_setup_recipient EVP_HPKE_CTX_setup_sender LK_EVP_HPKE_CTX_setup_sender EVP_HPKE_CTX_setup_sender_with_seed_for_testing LK_EVP_HPKE_CTX_setup_sender_with_seed_for_testing @@ -1066,7 +956,6 @@ EVP_HPKE_KEY_free LK_EVP_HPKE_KEY_free EVP_HPKE_KEY_generate LK_EVP_HPKE_KEY_generate EVP_HPKE_KEY_init LK_EVP_HPKE_KEY_init EVP_HPKE_KEY_kem LK_EVP_HPKE_KEY_kem -EVP_HPKE_KEY_move LK_EVP_HPKE_KEY_move EVP_HPKE_KEY_new LK_EVP_HPKE_KEY_new EVP_HPKE_KEY_private_key LK_EVP_HPKE_KEY_private_key EVP_HPKE_KEY_public_key LK_EVP_HPKE_KEY_public_key @@ -1115,7 +1004,6 @@ EVP_PKEY_CTX_new_id LK_EVP_PKEY_CTX_new_id EVP_PKEY_CTX_set0_rsa_oaep_label LK_EVP_PKEY_CTX_set0_rsa_oaep_label EVP_PKEY_CTX_set1_hkdf_key LK_EVP_PKEY_CTX_set1_hkdf_key EVP_PKEY_CTX_set1_hkdf_salt LK_EVP_PKEY_CTX_set1_hkdf_salt -EVP_PKEY_CTX_set_dh_pad LK_EVP_PKEY_CTX_set_dh_pad EVP_PKEY_CTX_set_dsa_paramgen_bits LK_EVP_PKEY_CTX_set_dsa_paramgen_bits EVP_PKEY_CTX_set_dsa_paramgen_q_bits LK_EVP_PKEY_CTX_set_dsa_paramgen_q_bits EVP_PKEY_CTX_set_ec_param_enc LK_EVP_PKEY_CTX_set_ec_param_enc @@ -1132,7 +1020,6 @@ EVP_PKEY_CTX_set_rsa_pss_keygen_saltlen LK_EVP_PKEY_CTX_set_rsa_pss_keygen_saltl EVP_PKEY_CTX_set_rsa_pss_saltlen LK_EVP_PKEY_CTX_set_rsa_pss_saltlen EVP_PKEY_CTX_set_signature_md LK_EVP_PKEY_CTX_set_signature_md EVP_PKEY_assign LK_EVP_PKEY_assign -EVP_PKEY_assign_DH LK_EVP_PKEY_assign_DH EVP_PKEY_assign_DSA LK_EVP_PKEY_assign_DSA EVP_PKEY_assign_EC_KEY LK_EVP_PKEY_assign_EC_KEY EVP_PKEY_assign_RSA LK_EVP_PKEY_assign_RSA @@ -1174,7 +1061,6 @@ EVP_PKEY_paramgen_init LK_EVP_PKEY_paramgen_init EVP_PKEY_print_params LK_EVP_PKEY_print_params EVP_PKEY_print_private LK_EVP_PKEY_print_private EVP_PKEY_print_public LK_EVP_PKEY_print_public -EVP_PKEY_set1_DH LK_EVP_PKEY_set1_DH EVP_PKEY_set1_DSA LK_EVP_PKEY_set1_DSA EVP_PKEY_set1_EC_KEY LK_EVP_PKEY_set1_EC_KEY EVP_PKEY_set1_RSA LK_EVP_PKEY_set1_RSA @@ -1201,7 +1087,6 @@ EVP_add_cipher_alias LK_EVP_add_cipher_alias EVP_add_digest LK_EVP_add_digest EVP_aead_aes_128_cbc_sha1_tls LK_EVP_aead_aes_128_cbc_sha1_tls EVP_aead_aes_128_cbc_sha1_tls_implicit_iv LK_EVP_aead_aes_128_cbc_sha1_tls_implicit_iv -EVP_aead_aes_128_cbc_sha256_tls LK_EVP_aead_aes_128_cbc_sha256_tls EVP_aead_aes_128_ccm_bluetooth LK_EVP_aead_aes_128_ccm_bluetooth EVP_aead_aes_128_ccm_bluetooth_8 LK_EVP_aead_aes_128_ccm_bluetooth_8 EVP_aead_aes_128_ccm_matter LK_EVP_aead_aes_128_ccm_matter @@ -1223,6 +1108,7 @@ EVP_aead_aes_256_gcm_tls13 LK_EVP_aead_aes_256_gcm_tls13 EVP_aead_chacha20_poly1305 LK_EVP_aead_chacha20_poly1305 EVP_aead_des_ede3_cbc_sha1_tls LK_EVP_aead_des_ede3_cbc_sha1_tls EVP_aead_des_ede3_cbc_sha1_tls_implicit_iv LK_EVP_aead_des_ede3_cbc_sha1_tls_implicit_iv +EVP_aead_null_sha1_tls LK_EVP_aead_null_sha1_tls EVP_aead_xchacha20_poly1305 LK_EVP_aead_xchacha20_poly1305 EVP_aes_128_cbc LK_EVP_aes_128_cbc EVP_aes_128_cfb LK_EVP_aes_128_cfb @@ -1282,24 +1168,15 @@ EVP_md5_sha1 LK_EVP_md5_sha1 EVP_parse_digest_algorithm LK_EVP_parse_digest_algorithm EVP_parse_private_key LK_EVP_parse_private_key EVP_parse_public_key LK_EVP_parse_public_key -EVP_rc2_40_cbc LK_EVP_rc2_40_cbc EVP_rc2_cbc LK_EVP_rc2_cbc EVP_rc4 LK_EVP_rc4 EVP_sha1 LK_EVP_sha1 EVP_sha1_final_with_secret_suffix LK_EVP_sha1_final_with_secret_suffix EVP_sha224 LK_EVP_sha224 EVP_sha256 LK_EVP_sha256 -EVP_sha256_final_with_secret_suffix LK_EVP_sha256_final_with_secret_suffix EVP_sha384 LK_EVP_sha384 EVP_sha512 LK_EVP_sha512 EVP_sha512_256 LK_EVP_sha512_256 -EVP_tls_cbc_copy_mac LK_EVP_tls_cbc_copy_mac -EVP_tls_cbc_digest_record LK_EVP_tls_cbc_digest_record -EVP_tls_cbc_record_digest_supported LK_EVP_tls_cbc_record_digest_supported -EVP_tls_cbc_remove_padding LK_EVP_tls_cbc_remove_padding -EXTENDED_KEY_USAGE_free LK_EXTENDED_KEY_USAGE_free -EXTENDED_KEY_USAGE_it LK_EXTENDED_KEY_USAGE_it -EXTENDED_KEY_USAGE_new LK_EXTENDED_KEY_USAGE_new FIPS_mode LK_FIPS_mode FIPS_mode_set LK_FIPS_mode_set FIPS_module_name LK_FIPS_module_name @@ -1308,22 +1185,13 @@ FIPS_read_counter LK_FIPS_read_counter FIPS_service_indicator_after_call LK_FIPS_service_indicator_after_call FIPS_service_indicator_before_call LK_FIPS_service_indicator_before_call FIPS_version LK_FIPS_version -GENERAL_NAMES_free LK_GENERAL_NAMES_free -GENERAL_NAMES_it LK_GENERAL_NAMES_it -GENERAL_NAMES_new LK_GENERAL_NAMES_new GENERAL_NAME_cmp LK_GENERAL_NAME_cmp GENERAL_NAME_dup LK_GENERAL_NAME_dup -GENERAL_NAME_free LK_GENERAL_NAME_free GENERAL_NAME_get0_otherName LK_GENERAL_NAME_get0_otherName GENERAL_NAME_get0_value LK_GENERAL_NAME_get0_value -GENERAL_NAME_it LK_GENERAL_NAME_it -GENERAL_NAME_new LK_GENERAL_NAME_new GENERAL_NAME_print LK_GENERAL_NAME_print GENERAL_NAME_set0_othername LK_GENERAL_NAME_set0_othername GENERAL_NAME_set0_value LK_GENERAL_NAME_set0_value -GENERAL_SUBTREE_free LK_GENERAL_SUBTREE_free -GENERAL_SUBTREE_it LK_GENERAL_SUBTREE_it -GENERAL_SUBTREE_new LK_GENERAL_SUBTREE_new HKDF LK_HKDF HKDF_expand LK_HKDF_expand HKDF_extract LK_HKDF_extract @@ -1349,9 +1217,6 @@ HRSS_marshal_public_key LK_HRSS_marshal_public_key HRSS_parse_public_key LK_HRSS_parse_public_key HRSS_poly3_invert LK_HRSS_poly3_invert HRSS_poly3_mul LK_HRSS_poly3_mul -ISSUING_DIST_POINT_free LK_ISSUING_DIST_POINT_free -ISSUING_DIST_POINT_it LK_ISSUING_DIST_POINT_it -ISSUING_DIST_POINT_new LK_ISSUING_DIST_POINT_new KYBER_decap LK_KYBER_decap KYBER_encap LK_KYBER_encap KYBER_encap_external_entropy LK_KYBER_encap_external_entropy @@ -1362,699 +1227,65 @@ KYBER_marshal_public_key LK_KYBER_marshal_public_key KYBER_parse_private_key LK_KYBER_parse_private_key KYBER_parse_public_key LK_KYBER_parse_public_key KYBER_public_from_private LK_KYBER_public_from_private -BIO_f_ssl LK_BIO_f_ssl -BIO_set_ssl LK_BIO_set_ssl -CBS_data LK_CBS_data -CBS_init LK_CBS_init -CBS_len LK_CBS_len -DTLS_client_method LK_DTLS_client_method -DTLS_method LK_DTLS_method -DTLS_server_method LK_DTLS_server_method -DTLS_with_buffers_method LK_DTLS_with_buffers_method -DTLSv1_2_client_method LK_DTLSv1_2_client_method -DTLSv1_2_method LK_DTLSv1_2_method -DTLSv1_2_server_method LK_DTLSv1_2_server_method -DTLSv1_client_method LK_DTLSv1_client_method -DTLSv1_get_timeout LK_DTLSv1_get_timeout -DTLSv1_handle_timeout LK_DTLSv1_handle_timeout -DTLSv1_method LK_DTLSv1_method -DTLSv1_server_method LK_DTLSv1_server_method -DTLSv1_set_initial_timeout_duration LK_DTLSv1_set_initial_timeout_duration -ERR_GET_LIB LK_ERR_GET_LIB -ERR_GET_REASON LK_ERR_GET_REASON -ERR_load_SSL_strings LK_ERR_load_SSL_strings +MD4 LK_MD4 +MD4_Final LK_MD4_Final +MD4_Init LK_MD4_Init +MD4_Transform LK_MD4_Transform +MD4_Update LK_MD4_Update +MD5 LK_MD5 +MD5_Final LK_MD5_Final +MD5_Init LK_MD5_Init +MD5_Transform LK_MD5_Transform +MD5_Update LK_MD5_Update +NAME_CONSTRAINTS_check LK_NAME_CONSTRAINTS_check +NCONF_free LK_NCONF_free +NCONF_get_section LK_NCONF_get_section +NCONF_get_string LK_NCONF_get_string +NCONF_load LK_NCONF_load +NCONF_load_bio LK_NCONF_load_bio +NCONF_new LK_NCONF_new +NETSCAPE_SPKI_b64_decode LK_NETSCAPE_SPKI_b64_decode +NETSCAPE_SPKI_b64_encode LK_NETSCAPE_SPKI_b64_encode +NETSCAPE_SPKI_get_pubkey LK_NETSCAPE_SPKI_get_pubkey +NETSCAPE_SPKI_set_pubkey LK_NETSCAPE_SPKI_set_pubkey +NETSCAPE_SPKI_sign LK_NETSCAPE_SPKI_sign +NETSCAPE_SPKI_verify LK_NETSCAPE_SPKI_verify +OBJ_NAME_do_all LK_OBJ_NAME_do_all +OBJ_NAME_do_all_sorted LK_OBJ_NAME_do_all_sorted +OBJ_cbs2nid LK_OBJ_cbs2nid +OBJ_cleanup LK_OBJ_cleanup +OBJ_cmp LK_OBJ_cmp +OBJ_create LK_OBJ_create +OBJ_dup LK_OBJ_dup +OBJ_find_sigid_algs LK_OBJ_find_sigid_algs +OBJ_find_sigid_by_algs LK_OBJ_find_sigid_by_algs +OBJ_get0_data LK_OBJ_get0_data +OBJ_length LK_OBJ_length +OBJ_ln2nid LK_OBJ_ln2nid +OBJ_nid2cbb LK_OBJ_nid2cbb +OBJ_nid2ln LK_OBJ_nid2ln +OBJ_nid2obj LK_OBJ_nid2obj +OBJ_nid2sn LK_OBJ_nid2sn +OBJ_obj2nid LK_OBJ_obj2nid +OBJ_obj2txt LK_OBJ_obj2txt +OBJ_sn2nid LK_OBJ_sn2nid +OBJ_txt2nid LK_OBJ_txt2nid +OBJ_txt2obj LK_OBJ_txt2obj +OPENSSL_add_all_algorithms_conf LK_OPENSSL_add_all_algorithms_conf +OPENSSL_asprintf LK_OPENSSL_asprintf +OPENSSL_cleanse LK_OPENSSL_cleanse +OPENSSL_cleanup LK_OPENSSL_cleanup +OPENSSL_clear_free LK_OPENSSL_clear_free +OPENSSL_config LK_OPENSSL_config +OPENSSL_free LK_OPENSSL_free +OPENSSL_fromxdigit LK_OPENSSL_fromxdigit +OPENSSL_get_armcap_pointer_for_test LK_OPENSSL_get_armcap_pointer_for_test +OPENSSL_gmtime LK_OPENSSL_gmtime +OPENSSL_gmtime_adj LK_OPENSSL_gmtime_adj +OPENSSL_gmtime_diff LK_OPENSSL_gmtime_diff +OPENSSL_hash32 LK_OPENSSL_hash32 +OPENSSL_init_crypto LK_OPENSSL_init_crypto OPENSSL_init_ssl LK_OPENSSL_init_ssl -PEM_read_SSL_SESSION LK_PEM_read_SSL_SESSION -PEM_read_bio_SSL_SESSION LK_PEM_read_bio_SSL_SESSION -PEM_write_SSL_SESSION LK_PEM_write_SSL_SESSION -PEM_write_bio_SSL_SESSION LK_PEM_write_bio_SSL_SESSION -SSL_CIPHER_description LK_SSL_CIPHER_description -SSL_CIPHER_get_auth_nid LK_SSL_CIPHER_get_auth_nid -SSL_CIPHER_get_bits LK_SSL_CIPHER_get_bits -SSL_CIPHER_get_cipher_nid LK_SSL_CIPHER_get_cipher_nid -SSL_CIPHER_get_digest_nid LK_SSL_CIPHER_get_digest_nid -SSL_CIPHER_get_handshake_digest LK_SSL_CIPHER_get_handshake_digest -SSL_CIPHER_get_id LK_SSL_CIPHER_get_id -SSL_CIPHER_get_kx_name LK_SSL_CIPHER_get_kx_name -SSL_CIPHER_get_kx_nid LK_SSL_CIPHER_get_kx_nid -SSL_CIPHER_get_max_version LK_SSL_CIPHER_get_max_version -SSL_CIPHER_get_min_version LK_SSL_CIPHER_get_min_version -SSL_CIPHER_get_name LK_SSL_CIPHER_get_name -SSL_CIPHER_get_prf_nid LK_SSL_CIPHER_get_prf_nid -SSL_CIPHER_get_protocol_id LK_SSL_CIPHER_get_protocol_id -SSL_CIPHER_get_version LK_SSL_CIPHER_get_version -SSL_CIPHER_is_aead LK_SSL_CIPHER_is_aead -SSL_CIPHER_is_block_cipher LK_SSL_CIPHER_is_block_cipher -SSL_CIPHER_standard_name LK_SSL_CIPHER_standard_name -SSL_COMP_add_compression_method LK_SSL_COMP_add_compression_method -SSL_COMP_free_compression_methods LK_SSL_COMP_free_compression_methods -SSL_COMP_get0_name LK_SSL_COMP_get0_name -SSL_COMP_get_compression_methods LK_SSL_COMP_get_compression_methods -SSL_COMP_get_id LK_SSL_COMP_get_id -SSL_COMP_get_name LK_SSL_COMP_get_name -SSL_CREDENTIAL_free LK_SSL_CREDENTIAL_free -SSL_CREDENTIAL_get_ex_data LK_SSL_CREDENTIAL_get_ex_data -SSL_CREDENTIAL_get_ex_new_index LK_SSL_CREDENTIAL_get_ex_new_index -SSL_CREDENTIAL_new_delegated LK_SSL_CREDENTIAL_new_delegated -SSL_CREDENTIAL_new_x509 LK_SSL_CREDENTIAL_new_x509 -SSL_CREDENTIAL_set1_cert_chain LK_SSL_CREDENTIAL_set1_cert_chain -SSL_CREDENTIAL_set1_delegated_credential LK_SSL_CREDENTIAL_set1_delegated_credential -SSL_CREDENTIAL_set1_ocsp_response LK_SSL_CREDENTIAL_set1_ocsp_response -SSL_CREDENTIAL_set1_private_key LK_SSL_CREDENTIAL_set1_private_key -SSL_CREDENTIAL_set1_signed_cert_timestamp_list LK_SSL_CREDENTIAL_set1_signed_cert_timestamp_list -SSL_CREDENTIAL_set1_signing_algorithm_prefs LK_SSL_CREDENTIAL_set1_signing_algorithm_prefs -SSL_CREDENTIAL_set_ex_data LK_SSL_CREDENTIAL_set_ex_data -SSL_CREDENTIAL_set_private_key_method LK_SSL_CREDENTIAL_set_private_key_method -SSL_CREDENTIAL_up_ref LK_SSL_CREDENTIAL_up_ref -SSL_CTX_add0_chain_cert LK_SSL_CTX_add0_chain_cert -SSL_CTX_add1_chain_cert LK_SSL_CTX_add1_chain_cert -SSL_CTX_add1_credential LK_SSL_CTX_add1_credential -SSL_CTX_add_cert_compression_alg LK_SSL_CTX_add_cert_compression_alg -SSL_CTX_add_client_CA LK_SSL_CTX_add_client_CA -SSL_CTX_add_extra_chain_cert LK_SSL_CTX_add_extra_chain_cert -SSL_CTX_add_session LK_SSL_CTX_add_session -SSL_CTX_check_private_key LK_SSL_CTX_check_private_key -SSL_CTX_cipher_in_group LK_SSL_CTX_cipher_in_group -SSL_CTX_clear_chain_certs LK_SSL_CTX_clear_chain_certs -SSL_CTX_clear_extra_chain_certs LK_SSL_CTX_clear_extra_chain_certs -SSL_CTX_clear_mode LK_SSL_CTX_clear_mode -SSL_CTX_clear_options LK_SSL_CTX_clear_options -SSL_CTX_enable_ocsp_stapling LK_SSL_CTX_enable_ocsp_stapling -SSL_CTX_enable_signed_cert_timestamps LK_SSL_CTX_enable_signed_cert_timestamps -SSL_CTX_enable_tls_channel_id LK_SSL_CTX_enable_tls_channel_id -SSL_CTX_flush_sessions LK_SSL_CTX_flush_sessions -SSL_CTX_free LK_SSL_CTX_free -SSL_CTX_get0_certificate LK_SSL_CTX_get0_certificate -SSL_CTX_get0_chain LK_SSL_CTX_get0_chain -SSL_CTX_get0_chain_certs LK_SSL_CTX_get0_chain_certs -SSL_CTX_get0_param LK_SSL_CTX_get0_param -SSL_CTX_get0_privatekey LK_SSL_CTX_get0_privatekey -SSL_CTX_get_cert_store LK_SSL_CTX_get_cert_store -SSL_CTX_get_ciphers LK_SSL_CTX_get_ciphers -SSL_CTX_get_client_CA_list LK_SSL_CTX_get_client_CA_list -SSL_CTX_get_default_passwd_cb LK_SSL_CTX_get_default_passwd_cb -SSL_CTX_get_default_passwd_cb_userdata LK_SSL_CTX_get_default_passwd_cb_userdata -SSL_CTX_get_ex_data LK_SSL_CTX_get_ex_data -SSL_CTX_get_ex_new_index LK_SSL_CTX_get_ex_new_index -SSL_CTX_get_extra_chain_certs LK_SSL_CTX_get_extra_chain_certs -SSL_CTX_get_info_callback LK_SSL_CTX_get_info_callback -SSL_CTX_get_keylog_callback LK_SSL_CTX_get_keylog_callback -SSL_CTX_get_max_cert_list LK_SSL_CTX_get_max_cert_list -SSL_CTX_get_max_proto_version LK_SSL_CTX_get_max_proto_version -SSL_CTX_get_min_proto_version LK_SSL_CTX_get_min_proto_version -SSL_CTX_get_mode LK_SSL_CTX_get_mode -SSL_CTX_get_num_tickets LK_SSL_CTX_get_num_tickets -SSL_CTX_get_options LK_SSL_CTX_get_options -SSL_CTX_get_quiet_shutdown LK_SSL_CTX_get_quiet_shutdown -SSL_CTX_get_read_ahead LK_SSL_CTX_get_read_ahead -SSL_CTX_get_session_cache_mode LK_SSL_CTX_get_session_cache_mode -SSL_CTX_get_timeout LK_SSL_CTX_get_timeout -SSL_CTX_get_tlsext_ticket_keys LK_SSL_CTX_get_tlsext_ticket_keys -SSL_CTX_get_verify_callback LK_SSL_CTX_get_verify_callback -SSL_CTX_get_verify_depth LK_SSL_CTX_get_verify_depth -SSL_CTX_get_verify_mode LK_SSL_CTX_get_verify_mode -SSL_CTX_load_verify_locations LK_SSL_CTX_load_verify_locations -SSL_CTX_need_tmp_RSA LK_SSL_CTX_need_tmp_RSA -SSL_CTX_new LK_SSL_CTX_new -SSL_CTX_remove_session LK_SSL_CTX_remove_session -SSL_CTX_sess_accept LK_SSL_CTX_sess_accept -SSL_CTX_sess_accept_good LK_SSL_CTX_sess_accept_good -SSL_CTX_sess_accept_renegotiate LK_SSL_CTX_sess_accept_renegotiate -SSL_CTX_sess_cache_full LK_SSL_CTX_sess_cache_full -SSL_CTX_sess_cb_hits LK_SSL_CTX_sess_cb_hits -SSL_CTX_sess_connect LK_SSL_CTX_sess_connect -SSL_CTX_sess_connect_good LK_SSL_CTX_sess_connect_good -SSL_CTX_sess_connect_renegotiate LK_SSL_CTX_sess_connect_renegotiate -SSL_CTX_sess_get_cache_size LK_SSL_CTX_sess_get_cache_size -SSL_CTX_sess_get_get_cb LK_SSL_CTX_sess_get_get_cb -SSL_CTX_sess_get_new_cb LK_SSL_CTX_sess_get_new_cb -SSL_CTX_sess_get_remove_cb LK_SSL_CTX_sess_get_remove_cb -SSL_CTX_sess_hits LK_SSL_CTX_sess_hits -SSL_CTX_sess_misses LK_SSL_CTX_sess_misses -SSL_CTX_sess_number LK_SSL_CTX_sess_number -SSL_CTX_sess_set_cache_size LK_SSL_CTX_sess_set_cache_size -SSL_CTX_sess_set_get_cb LK_SSL_CTX_sess_set_get_cb -SSL_CTX_sess_set_new_cb LK_SSL_CTX_sess_set_new_cb -SSL_CTX_sess_set_remove_cb LK_SSL_CTX_sess_set_remove_cb -SSL_CTX_sess_timeouts LK_SSL_CTX_sess_timeouts -SSL_CTX_set0_buffer_pool LK_SSL_CTX_set0_buffer_pool -SSL_CTX_set0_chain LK_SSL_CTX_set0_chain -SSL_CTX_set0_client_CAs LK_SSL_CTX_set0_client_CAs -SSL_CTX_set0_verify_cert_store LK_SSL_CTX_set0_verify_cert_store -SSL_CTX_set1_chain LK_SSL_CTX_set1_chain -SSL_CTX_set1_curves LK_SSL_CTX_set1_curves -SSL_CTX_set1_curves_list LK_SSL_CTX_set1_curves_list -SSL_CTX_set1_ech_keys LK_SSL_CTX_set1_ech_keys -SSL_CTX_set1_group_ids LK_SSL_CTX_set1_group_ids -SSL_CTX_set1_groups LK_SSL_CTX_set1_groups -SSL_CTX_set1_groups_list LK_SSL_CTX_set1_groups_list -SSL_CTX_set1_param LK_SSL_CTX_set1_param -SSL_CTX_set1_sigalgs LK_SSL_CTX_set1_sigalgs -SSL_CTX_set1_sigalgs_list LK_SSL_CTX_set1_sigalgs_list -SSL_CTX_set1_tls_channel_id LK_SSL_CTX_set1_tls_channel_id -SSL_CTX_set1_verify_cert_store LK_SSL_CTX_set1_verify_cert_store -SSL_CTX_set_allow_unknown_alpn_protos LK_SSL_CTX_set_allow_unknown_alpn_protos -SSL_CTX_set_alpn_protos LK_SSL_CTX_set_alpn_protos -SSL_CTX_set_alpn_select_cb LK_SSL_CTX_set_alpn_select_cb -SSL_CTX_set_cert_cb LK_SSL_CTX_set_cert_cb -SSL_CTX_set_cert_store LK_SSL_CTX_set_cert_store -SSL_CTX_set_cert_verify_callback LK_SSL_CTX_set_cert_verify_callback -SSL_CTX_set_chain_and_key LK_SSL_CTX_set_chain_and_key -SSL_CTX_set_cipher_list LK_SSL_CTX_set_cipher_list -SSL_CTX_set_client_CA_list LK_SSL_CTX_set_client_CA_list -SSL_CTX_set_client_cert_cb LK_SSL_CTX_set_client_cert_cb -SSL_CTX_set_compliance_policy LK_SSL_CTX_set_compliance_policy -SSL_CTX_set_current_time_cb LK_SSL_CTX_set_current_time_cb -SSL_CTX_set_custom_verify LK_SSL_CTX_set_custom_verify -SSL_CTX_set_default_passwd_cb LK_SSL_CTX_set_default_passwd_cb -SSL_CTX_set_default_passwd_cb_userdata LK_SSL_CTX_set_default_passwd_cb_userdata -SSL_CTX_set_default_verify_paths LK_SSL_CTX_set_default_verify_paths -SSL_CTX_set_dos_protection_cb LK_SSL_CTX_set_dos_protection_cb -SSL_CTX_set_early_data_enabled LK_SSL_CTX_set_early_data_enabled -SSL_CTX_set_ex_data LK_SSL_CTX_set_ex_data -SSL_CTX_set_false_start_allowed_without_alpn LK_SSL_CTX_set_false_start_allowed_without_alpn -SSL_CTX_set_grease_enabled LK_SSL_CTX_set_grease_enabled -SSL_CTX_set_info_callback LK_SSL_CTX_set_info_callback -SSL_CTX_set_keylog_callback LK_SSL_CTX_set_keylog_callback -SSL_CTX_set_max_cert_list LK_SSL_CTX_set_max_cert_list -SSL_CTX_set_max_proto_version LK_SSL_CTX_set_max_proto_version -SSL_CTX_set_max_send_fragment LK_SSL_CTX_set_max_send_fragment -SSL_CTX_set_min_proto_version LK_SSL_CTX_set_min_proto_version -SSL_CTX_set_mode LK_SSL_CTX_set_mode -SSL_CTX_set_msg_callback LK_SSL_CTX_set_msg_callback -SSL_CTX_set_msg_callback_arg LK_SSL_CTX_set_msg_callback_arg -SSL_CTX_set_next_proto_select_cb LK_SSL_CTX_set_next_proto_select_cb -SSL_CTX_set_next_protos_advertised_cb LK_SSL_CTX_set_next_protos_advertised_cb -SSL_CTX_set_num_tickets LK_SSL_CTX_set_num_tickets -SSL_CTX_set_ocsp_response LK_SSL_CTX_set_ocsp_response -SSL_CTX_set_options LK_SSL_CTX_set_options -SSL_CTX_set_permute_extensions LK_SSL_CTX_set_permute_extensions -SSL_CTX_set_private_key_method LK_SSL_CTX_set_private_key_method -SSL_CTX_set_psk_client_callback LK_SSL_CTX_set_psk_client_callback -SSL_CTX_set_psk_server_callback LK_SSL_CTX_set_psk_server_callback -SSL_CTX_set_purpose LK_SSL_CTX_set_purpose -SSL_CTX_set_quic_method LK_SSL_CTX_set_quic_method -SSL_CTX_set_quiet_shutdown LK_SSL_CTX_set_quiet_shutdown -SSL_CTX_set_read_ahead LK_SSL_CTX_set_read_ahead -SSL_CTX_set_record_protocol_version LK_SSL_CTX_set_record_protocol_version -SSL_CTX_set_retain_only_sha256_of_client_certs LK_SSL_CTX_set_retain_only_sha256_of_client_certs -SSL_CTX_set_reverify_on_resume LK_SSL_CTX_set_reverify_on_resume -SSL_CTX_set_select_certificate_cb LK_SSL_CTX_set_select_certificate_cb -SSL_CTX_set_session_cache_mode LK_SSL_CTX_set_session_cache_mode -SSL_CTX_set_session_id_context LK_SSL_CTX_set_session_id_context -SSL_CTX_set_session_psk_dhe_timeout LK_SSL_CTX_set_session_psk_dhe_timeout -SSL_CTX_set_signed_cert_timestamp_list LK_SSL_CTX_set_signed_cert_timestamp_list -SSL_CTX_set_signing_algorithm_prefs LK_SSL_CTX_set_signing_algorithm_prefs -SSL_CTX_set_srtp_profiles LK_SSL_CTX_set_srtp_profiles -SSL_CTX_set_strict_cipher_list LK_SSL_CTX_set_strict_cipher_list -SSL_CTX_set_ticket_aead_method LK_SSL_CTX_set_ticket_aead_method -SSL_CTX_set_timeout LK_SSL_CTX_set_timeout -SSL_CTX_set_tls_channel_id_enabled LK_SSL_CTX_set_tls_channel_id_enabled -SSL_CTX_set_tlsext_servername_arg LK_SSL_CTX_set_tlsext_servername_arg -SSL_CTX_set_tlsext_servername_callback LK_SSL_CTX_set_tlsext_servername_callback -SSL_CTX_set_tlsext_status_arg LK_SSL_CTX_set_tlsext_status_arg -SSL_CTX_set_tlsext_status_cb LK_SSL_CTX_set_tlsext_status_cb -SSL_CTX_set_tlsext_ticket_key_cb LK_SSL_CTX_set_tlsext_ticket_key_cb -SSL_CTX_set_tlsext_ticket_keys LK_SSL_CTX_set_tlsext_ticket_keys -SSL_CTX_set_tlsext_use_srtp LK_SSL_CTX_set_tlsext_use_srtp -SSL_CTX_set_tmp_dh LK_SSL_CTX_set_tmp_dh -SSL_CTX_set_tmp_dh_callback LK_SSL_CTX_set_tmp_dh_callback -SSL_CTX_set_tmp_ecdh LK_SSL_CTX_set_tmp_ecdh -SSL_CTX_set_tmp_rsa LK_SSL_CTX_set_tmp_rsa -SSL_CTX_set_tmp_rsa_callback LK_SSL_CTX_set_tmp_rsa_callback -SSL_CTX_set_trust LK_SSL_CTX_set_trust -SSL_CTX_set_verify LK_SSL_CTX_set_verify -SSL_CTX_set_verify_algorithm_prefs LK_SSL_CTX_set_verify_algorithm_prefs -SSL_CTX_set_verify_depth LK_SSL_CTX_set_verify_depth -SSL_CTX_up_ref LK_SSL_CTX_up_ref -SSL_CTX_use_PrivateKey LK_SSL_CTX_use_PrivateKey -SSL_CTX_use_PrivateKey_ASN1 LK_SSL_CTX_use_PrivateKey_ASN1 -SSL_CTX_use_PrivateKey_file LK_SSL_CTX_use_PrivateKey_file -SSL_CTX_use_RSAPrivateKey LK_SSL_CTX_use_RSAPrivateKey -SSL_CTX_use_RSAPrivateKey_ASN1 LK_SSL_CTX_use_RSAPrivateKey_ASN1 -SSL_CTX_use_RSAPrivateKey_file LK_SSL_CTX_use_RSAPrivateKey_file -SSL_CTX_use_certificate LK_SSL_CTX_use_certificate -SSL_CTX_use_certificate_ASN1 LK_SSL_CTX_use_certificate_ASN1 -SSL_CTX_use_certificate_chain_file LK_SSL_CTX_use_certificate_chain_file -SSL_CTX_use_certificate_file LK_SSL_CTX_use_certificate_file -SSL_CTX_use_psk_identity_hint LK_SSL_CTX_use_psk_identity_hint -SSL_ECH_KEYS_add LK_SSL_ECH_KEYS_add -SSL_ECH_KEYS_free LK_SSL_ECH_KEYS_free -SSL_ECH_KEYS_has_duplicate_config_id LK_SSL_ECH_KEYS_has_duplicate_config_id -SSL_ECH_KEYS_marshal_retry_configs LK_SSL_ECH_KEYS_marshal_retry_configs -SSL_ECH_KEYS_new LK_SSL_ECH_KEYS_new -SSL_ECH_KEYS_up_ref LK_SSL_ECH_KEYS_up_ref -SSL_SESSION_copy_without_early_data LK_SSL_SESSION_copy_without_early_data -SSL_SESSION_early_data_capable LK_SSL_SESSION_early_data_capable -SSL_SESSION_free LK_SSL_SESSION_free -SSL_SESSION_from_bytes LK_SSL_SESSION_from_bytes -SSL_SESSION_get0_cipher LK_SSL_SESSION_get0_cipher -SSL_SESSION_get0_id_context LK_SSL_SESSION_get0_id_context -SSL_SESSION_get0_ocsp_response LK_SSL_SESSION_get0_ocsp_response -SSL_SESSION_get0_peer LK_SSL_SESSION_get0_peer -SSL_SESSION_get0_peer_certificates LK_SSL_SESSION_get0_peer_certificates -SSL_SESSION_get0_peer_sha256 LK_SSL_SESSION_get0_peer_sha256 -SSL_SESSION_get0_signed_cert_timestamp_list LK_SSL_SESSION_get0_signed_cert_timestamp_list -SSL_SESSION_get0_ticket LK_SSL_SESSION_get0_ticket -SSL_SESSION_get_ex_data LK_SSL_SESSION_get_ex_data -SSL_SESSION_get_ex_new_index LK_SSL_SESSION_get_ex_new_index -SSL_SESSION_get_id LK_SSL_SESSION_get_id -SSL_SESSION_get_master_key LK_SSL_SESSION_get_master_key -SSL_SESSION_get_protocol_version LK_SSL_SESSION_get_protocol_version -SSL_SESSION_get_ticket_lifetime_hint LK_SSL_SESSION_get_ticket_lifetime_hint -SSL_SESSION_get_time LK_SSL_SESSION_get_time -SSL_SESSION_get_timeout LK_SSL_SESSION_get_timeout -SSL_SESSION_get_version LK_SSL_SESSION_get_version -SSL_SESSION_has_peer_sha256 LK_SSL_SESSION_has_peer_sha256 -SSL_SESSION_has_ticket LK_SSL_SESSION_has_ticket -SSL_SESSION_is_resumable LK_SSL_SESSION_is_resumable -SSL_SESSION_new LK_SSL_SESSION_new -SSL_SESSION_set1_id LK_SSL_SESSION_set1_id -SSL_SESSION_set1_id_context LK_SSL_SESSION_set1_id_context -SSL_SESSION_set_ex_data LK_SSL_SESSION_set_ex_data -SSL_SESSION_set_protocol_version LK_SSL_SESSION_set_protocol_version -SSL_SESSION_set_ticket LK_SSL_SESSION_set_ticket -SSL_SESSION_set_time LK_SSL_SESSION_set_time -SSL_SESSION_set_timeout LK_SSL_SESSION_set_timeout -SSL_SESSION_should_be_single_use LK_SSL_SESSION_should_be_single_use -SSL_SESSION_to_bytes LK_SSL_SESSION_to_bytes -SSL_SESSION_to_bytes_for_ticket LK_SSL_SESSION_to_bytes_for_ticket -SSL_SESSION_up_ref LK_SSL_SESSION_up_ref -SSL_accept LK_SSL_accept -SSL_add0_chain_cert LK_SSL_add0_chain_cert -SSL_add1_chain_cert LK_SSL_add1_chain_cert -SSL_add1_credential LK_SSL_add1_credential -SSL_add_application_settings LK_SSL_add_application_settings -SSL_add_bio_cert_subjects_to_stack LK_SSL_add_bio_cert_subjects_to_stack -SSL_add_client_CA LK_SSL_add_client_CA -SSL_add_file_cert_subjects_to_stack LK_SSL_add_file_cert_subjects_to_stack -SSL_alert_desc_string LK_SSL_alert_desc_string -SSL_alert_desc_string_long LK_SSL_alert_desc_string_long -SSL_alert_from_verify_result LK_SSL_alert_from_verify_result -SSL_alert_type_string LK_SSL_alert_type_string -SSL_alert_type_string_long LK_SSL_alert_type_string_long -SSL_cache_hit LK_SSL_cache_hit -SSL_can_release_private_key LK_SSL_can_release_private_key -SSL_certs_clear LK_SSL_certs_clear -SSL_check_private_key LK_SSL_check_private_key -SSL_clear LK_SSL_clear -SSL_clear_chain_certs LK_SSL_clear_chain_certs -SSL_clear_mode LK_SSL_clear_mode -SSL_clear_options LK_SSL_clear_options -SSL_connect LK_SSL_connect -SSL_cutthrough_complete LK_SSL_cutthrough_complete -SSL_do_handshake LK_SSL_do_handshake -SSL_dup_CA_list LK_SSL_dup_CA_list -SSL_early_callback_ctx_extension_get LK_SSL_early_callback_ctx_extension_get -SSL_early_data_accepted LK_SSL_early_data_accepted -SSL_early_data_reason_string LK_SSL_early_data_reason_string -SSL_ech_accepted LK_SSL_ech_accepted -SSL_enable_ocsp_stapling LK_SSL_enable_ocsp_stapling -SSL_enable_signed_cert_timestamps LK_SSL_enable_signed_cert_timestamps -SSL_enable_tls_channel_id LK_SSL_enable_tls_channel_id -SSL_error_description LK_SSL_error_description -SSL_export_keying_material LK_SSL_export_keying_material -SSL_free LK_SSL_free -SSL_generate_key_block LK_SSL_generate_key_block -SSL_get0_alpn_selected LK_SSL_get0_alpn_selected -SSL_get0_certificate_types LK_SSL_get0_certificate_types -SSL_get0_chain LK_SSL_get0_chain -SSL_get0_chain_certs LK_SSL_get0_chain_certs -SSL_get0_ech_name_override LK_SSL_get0_ech_name_override -SSL_get0_ech_retry_configs LK_SSL_get0_ech_retry_configs -SSL_get0_next_proto_negotiated LK_SSL_get0_next_proto_negotiated -SSL_get0_ocsp_response LK_SSL_get0_ocsp_response -SSL_get0_param LK_SSL_get0_param -SSL_get0_peer_application_settings LK_SSL_get0_peer_application_settings -SSL_get0_peer_certificates LK_SSL_get0_peer_certificates -SSL_get0_peer_delegation_algorithms LK_SSL_get0_peer_delegation_algorithms -SSL_get0_peer_verify_algorithms LK_SSL_get0_peer_verify_algorithms -SSL_get0_selected_credential LK_SSL_get0_selected_credential -SSL_get0_server_requested_CAs LK_SSL_get0_server_requested_CAs -SSL_get0_session_id_context LK_SSL_get0_session_id_context -SSL_get0_signed_cert_timestamp_list LK_SSL_get0_signed_cert_timestamp_list -SSL_get1_session LK_SSL_get1_session -SSL_get_SSL_CTX LK_SSL_get_SSL_CTX -SSL_get_all_cipher_names LK_SSL_get_all_cipher_names -SSL_get_all_curve_names LK_SSL_get_all_curve_names -SSL_get_all_group_names LK_SSL_get_all_group_names -SSL_get_all_signature_algorithm_names LK_SSL_get_all_signature_algorithm_names -SSL_get_all_standard_cipher_names LK_SSL_get_all_standard_cipher_names -SSL_get_all_version_names LK_SSL_get_all_version_names -SSL_get_certificate LK_SSL_get_certificate -SSL_get_cipher_by_value LK_SSL_get_cipher_by_value -SSL_get_cipher_list LK_SSL_get_cipher_list -SSL_get_ciphers LK_SSL_get_ciphers -SSL_get_client_CA_list LK_SSL_get_client_CA_list -SSL_get_client_random LK_SSL_get_client_random -SSL_get_current_cipher LK_SSL_get_current_cipher -SSL_get_current_compression LK_SSL_get_current_compression -SSL_get_current_expansion LK_SSL_get_current_expansion -SSL_get_curve_id LK_SSL_get_curve_id -SSL_get_curve_name LK_SSL_get_curve_name -SSL_get_default_timeout LK_SSL_get_default_timeout -SSL_get_early_data_reason LK_SSL_get_early_data_reason -SSL_get_error LK_SSL_get_error -SSL_get_ex_data LK_SSL_get_ex_data -SSL_get_ex_data_X509_STORE_CTX_idx LK_SSL_get_ex_data_X509_STORE_CTX_idx -SSL_get_ex_new_index LK_SSL_get_ex_new_index -SSL_get_extms_support LK_SSL_get_extms_support -SSL_get_fd LK_SSL_get_fd -SSL_get_finished LK_SSL_get_finished -SSL_get_group_id LK_SSL_get_group_id -SSL_get_group_name LK_SSL_get_group_name -SSL_get_info_callback LK_SSL_get_info_callback -SSL_get_ivs LK_SSL_get_ivs -SSL_get_key_block_len LK_SSL_get_key_block_len -SSL_get_max_cert_list LK_SSL_get_max_cert_list -SSL_get_max_proto_version LK_SSL_get_max_proto_version -SSL_get_min_proto_version LK_SSL_get_min_proto_version -SSL_get_mode LK_SSL_get_mode -SSL_get_negotiated_group LK_SSL_get_negotiated_group -SSL_get_options LK_SSL_get_options -SSL_get_peer_cert_chain LK_SSL_get_peer_cert_chain -SSL_get_peer_certificate LK_SSL_get_peer_certificate -SSL_get_peer_finished LK_SSL_get_peer_finished -SSL_get_peer_full_cert_chain LK_SSL_get_peer_full_cert_chain -SSL_get_peer_quic_transport_params LK_SSL_get_peer_quic_transport_params -SSL_get_peer_signature_algorithm LK_SSL_get_peer_signature_algorithm -SSL_get_pending_cipher LK_SSL_get_pending_cipher -SSL_get_privatekey LK_SSL_get_privatekey -SSL_get_psk_identity LK_SSL_get_psk_identity -SSL_get_psk_identity_hint LK_SSL_get_psk_identity_hint -SSL_get_quiet_shutdown LK_SSL_get_quiet_shutdown -SSL_get_rbio LK_SSL_get_rbio -SSL_get_read_ahead LK_SSL_get_read_ahead -SSL_get_read_sequence LK_SSL_get_read_sequence -SSL_get_rfd LK_SSL_get_rfd -SSL_get_secure_renegotiation_support LK_SSL_get_secure_renegotiation_support -SSL_get_selected_srtp_profile LK_SSL_get_selected_srtp_profile -SSL_get_server_random LK_SSL_get_server_random -SSL_get_server_tmp_key LK_SSL_get_server_tmp_key -SSL_get_servername LK_SSL_get_servername -SSL_get_servername_type LK_SSL_get_servername_type -SSL_get_session LK_SSL_get_session -SSL_get_shared_ciphers LK_SSL_get_shared_ciphers -SSL_get_shared_sigalgs LK_SSL_get_shared_sigalgs -SSL_get_shutdown LK_SSL_get_shutdown -SSL_get_signature_algorithm_digest LK_SSL_get_signature_algorithm_digest -SSL_get_signature_algorithm_key_type LK_SSL_get_signature_algorithm_key_type -SSL_get_signature_algorithm_name LK_SSL_get_signature_algorithm_name -SSL_get_srtp_profiles LK_SSL_get_srtp_profiles -SSL_get_ticket_age_skew LK_SSL_get_ticket_age_skew -SSL_get_tls_channel_id LK_SSL_get_tls_channel_id -SSL_get_tls_unique LK_SSL_get_tls_unique -SSL_get_tlsext_status_ocsp_resp LK_SSL_get_tlsext_status_ocsp_resp -SSL_get_tlsext_status_type LK_SSL_get_tlsext_status_type -SSL_get_verify_callback LK_SSL_get_verify_callback -SSL_get_verify_depth LK_SSL_get_verify_depth -SSL_get_verify_mode LK_SSL_get_verify_mode -SSL_get_verify_result LK_SSL_get_verify_result -SSL_get_version LK_SSL_get_version -SSL_get_wbio LK_SSL_get_wbio -SSL_get_wfd LK_SSL_get_wfd -SSL_get_write_sequence LK_SSL_get_write_sequence -SSL_has_application_settings LK_SSL_has_application_settings -SSL_has_pending LK_SSL_has_pending -SSL_in_early_data LK_SSL_in_early_data -SSL_in_false_start LK_SSL_in_false_start -SSL_in_init LK_SSL_in_init -SSL_is_dtls LK_SSL_is_dtls -SSL_is_init_finished LK_SSL_is_init_finished -SSL_is_server LK_SSL_is_server -SSL_is_signature_algorithm_rsa_pss LK_SSL_is_signature_algorithm_rsa_pss -SSL_key_update LK_SSL_key_update -SSL_library_init LK_SSL_library_init -SSL_load_client_CA_file LK_SSL_load_client_CA_file -SSL_load_error_strings LK_SSL_load_error_strings -SSL_magic_pending_session_ptr LK_SSL_magic_pending_session_ptr -SSL_marshal_ech_config LK_SSL_marshal_ech_config -SSL_max_seal_overhead LK_SSL_max_seal_overhead -SSL_need_tmp_RSA LK_SSL_need_tmp_RSA -SSL_new LK_SSL_new -SSL_num_renegotiations LK_SSL_num_renegotiations -SSL_peek LK_SSL_peek -SSL_pending LK_SSL_pending -SSL_process_quic_post_handshake LK_SSL_process_quic_post_handshake -SSL_process_tls13_new_session_ticket LK_SSL_process_tls13_new_session_ticket -SSL_provide_quic_data LK_SSL_provide_quic_data -SSL_quic_max_handshake_flight_len LK_SSL_quic_max_handshake_flight_len -SSL_quic_read_level LK_SSL_quic_read_level -SSL_quic_write_level LK_SSL_quic_write_level -SSL_read LK_SSL_read -SSL_renegotiate LK_SSL_renegotiate -SSL_renegotiate_pending LK_SSL_renegotiate_pending -SSL_request_handshake_hints LK_SSL_request_handshake_hints -SSL_reset_early_data_reject LK_SSL_reset_early_data_reject -SSL_select_next_proto LK_SSL_select_next_proto -SSL_send_fatal_alert LK_SSL_send_fatal_alert -SSL_serialize_capabilities LK_SSL_serialize_capabilities -SSL_serialize_handshake_hints LK_SSL_serialize_handshake_hints -SSL_session_reused LK_SSL_session_reused -SSL_set0_chain LK_SSL_set0_chain -SSL_set0_client_CAs LK_SSL_set0_client_CAs -SSL_set0_rbio LK_SSL_set0_rbio -SSL_set0_verify_cert_store LK_SSL_set0_verify_cert_store -SSL_set0_wbio LK_SSL_set0_wbio -SSL_set1_chain LK_SSL_set1_chain -SSL_set1_curves LK_SSL_set1_curves -SSL_set1_curves_list LK_SSL_set1_curves_list -SSL_set1_ech_config_list LK_SSL_set1_ech_config_list -SSL_set1_group_ids LK_SSL_set1_group_ids -SSL_set1_groups LK_SSL_set1_groups -SSL_set1_groups_list LK_SSL_set1_groups_list -SSL_set1_host LK_SSL_set1_host -SSL_set1_param LK_SSL_set1_param -SSL_set1_sigalgs LK_SSL_set1_sigalgs -SSL_set1_sigalgs_list LK_SSL_set1_sigalgs_list -SSL_set1_tls_channel_id LK_SSL_set1_tls_channel_id -SSL_set1_verify_cert_store LK_SSL_set1_verify_cert_store -SSL_set_SSL_CTX LK_SSL_set_SSL_CTX -SSL_set_accept_state LK_SSL_set_accept_state -SSL_set_alpn_protos LK_SSL_set_alpn_protos -SSL_set_alps_use_new_codepoint LK_SSL_set_alps_use_new_codepoint -SSL_set_bio LK_SSL_set_bio -SSL_set_cert_cb LK_SSL_set_cert_cb -SSL_set_chain_and_key LK_SSL_set_chain_and_key -SSL_set_check_client_certificate_type LK_SSL_set_check_client_certificate_type -SSL_set_check_ecdsa_curve LK_SSL_set_check_ecdsa_curve -SSL_set_cipher_list LK_SSL_set_cipher_list -SSL_set_client_CA_list LK_SSL_set_client_CA_list -SSL_set_compliance_policy LK_SSL_set_compliance_policy -SSL_set_connect_state LK_SSL_set_connect_state -SSL_set_custom_verify LK_SSL_set_custom_verify -SSL_set_early_data_enabled LK_SSL_set_early_data_enabled -SSL_set_enable_ech_grease LK_SSL_set_enable_ech_grease -SSL_set_enforce_rsa_key_usage LK_SSL_set_enforce_rsa_key_usage -SSL_set_ex_data LK_SSL_set_ex_data -SSL_set_fd LK_SSL_set_fd -SSL_set_handshake_hints LK_SSL_set_handshake_hints -SSL_set_hostflags LK_SSL_set_hostflags -SSL_set_info_callback LK_SSL_set_info_callback -SSL_set_jdk11_workaround LK_SSL_set_jdk11_workaround -SSL_set_max_cert_list LK_SSL_set_max_cert_list -SSL_set_max_proto_version LK_SSL_set_max_proto_version -SSL_set_max_send_fragment LK_SSL_set_max_send_fragment -SSL_set_min_proto_version LK_SSL_set_min_proto_version -SSL_set_mode LK_SSL_set_mode -SSL_set_msg_callback LK_SSL_set_msg_callback -SSL_set_msg_callback_arg LK_SSL_set_msg_callback_arg -SSL_set_mtu LK_SSL_set_mtu -SSL_set_ocsp_response LK_SSL_set_ocsp_response -SSL_set_options LK_SSL_set_options -SSL_set_permute_extensions LK_SSL_set_permute_extensions -SSL_set_private_key_method LK_SSL_set_private_key_method -SSL_set_psk_client_callback LK_SSL_set_psk_client_callback -SSL_set_psk_server_callback LK_SSL_set_psk_server_callback -SSL_set_purpose LK_SSL_set_purpose -SSL_set_quic_early_data_context LK_SSL_set_quic_early_data_context -SSL_set_quic_method LK_SSL_set_quic_method -SSL_set_quic_transport_params LK_SSL_set_quic_transport_params -SSL_set_quic_use_legacy_codepoint LK_SSL_set_quic_use_legacy_codepoint -SSL_set_quiet_shutdown LK_SSL_set_quiet_shutdown -SSL_set_read_ahead LK_SSL_set_read_ahead -SSL_set_renegotiate_mode LK_SSL_set_renegotiate_mode -SSL_set_retain_only_sha256_of_client_certs LK_SSL_set_retain_only_sha256_of_client_certs -SSL_set_rfd LK_SSL_set_rfd -SSL_set_session LK_SSL_set_session -SSL_set_session_id_context LK_SSL_set_session_id_context -SSL_set_shed_handshake_config LK_SSL_set_shed_handshake_config -SSL_set_shutdown LK_SSL_set_shutdown -SSL_set_signed_cert_timestamp_list LK_SSL_set_signed_cert_timestamp_list -SSL_set_signing_algorithm_prefs LK_SSL_set_signing_algorithm_prefs -SSL_set_srtp_profiles LK_SSL_set_srtp_profiles -SSL_set_state LK_SSL_set_state -SSL_set_strict_cipher_list LK_SSL_set_strict_cipher_list -SSL_set_tls_channel_id_enabled LK_SSL_set_tls_channel_id_enabled -SSL_set_tlsext_host_name LK_SSL_set_tlsext_host_name -SSL_set_tlsext_status_ocsp_resp LK_SSL_set_tlsext_status_ocsp_resp -SSL_set_tlsext_status_type LK_SSL_set_tlsext_status_type -SSL_set_tlsext_use_srtp LK_SSL_set_tlsext_use_srtp -SSL_set_tmp_dh LK_SSL_set_tmp_dh -SSL_set_tmp_dh_callback LK_SSL_set_tmp_dh_callback -SSL_set_tmp_ecdh LK_SSL_set_tmp_ecdh -SSL_set_tmp_rsa LK_SSL_set_tmp_rsa -SSL_set_tmp_rsa_callback LK_SSL_set_tmp_rsa_callback -SSL_set_trust LK_SSL_set_trust -SSL_set_verify LK_SSL_set_verify -SSL_set_verify_algorithm_prefs LK_SSL_set_verify_algorithm_prefs -SSL_set_verify_depth LK_SSL_set_verify_depth -SSL_set_wfd LK_SSL_set_wfd -SSL_shutdown LK_SSL_shutdown -SSL_state LK_SSL_state -SSL_state_string LK_SSL_state_string -SSL_state_string_long LK_SSL_state_string_long -SSL_total_renegotiations LK_SSL_total_renegotiations -SSL_use_PrivateKey LK_SSL_use_PrivateKey -SSL_use_PrivateKey_ASN1 LK_SSL_use_PrivateKey_ASN1 -SSL_use_PrivateKey_file LK_SSL_use_PrivateKey_file -SSL_use_RSAPrivateKey LK_SSL_use_RSAPrivateKey -SSL_use_RSAPrivateKey_ASN1 LK_SSL_use_RSAPrivateKey_ASN1 -SSL_use_RSAPrivateKey_file LK_SSL_use_RSAPrivateKey_file -SSL_use_certificate LK_SSL_use_certificate -SSL_use_certificate_ASN1 LK_SSL_use_certificate_ASN1 -SSL_use_certificate_file LK_SSL_use_certificate_file -SSL_use_psk_identity_hint LK_SSL_use_psk_identity_hint -SSL_used_hello_retry_request LK_SSL_used_hello_retry_request -SSL_version LK_SSL_version -SSL_want LK_SSL_want -SSL_was_key_usage_invalid LK_SSL_was_key_usage_invalid -SSL_write LK_SSL_write -SSLv23_client_method LK_SSLv23_client_method -SSLv23_method LK_SSLv23_method -SSLv23_server_method LK_SSLv23_server_method -TLS_client_method LK_TLS_client_method -TLS_method LK_TLS_method -TLS_server_method LK_TLS_server_method -TLS_with_buffers_method LK_TLS_with_buffers_method -TLSv1_1_client_method LK_TLSv1_1_client_method -TLSv1_1_method LK_TLSv1_1_method -TLSv1_1_server_method LK_TLSv1_1_server_method -TLSv1_2_client_method LK_TLSv1_2_client_method -TLSv1_2_method LK_TLSv1_2_method -TLSv1_2_server_method LK_TLSv1_2_server_method -TLSv1_client_method LK_TLSv1_client_method -TLSv1_method LK_TLSv1_method -TLSv1_server_method LK_TLSv1_server_method -d2i_SSL_SESSION LK_d2i_SSL_SESSION -d2i_SSL_SESSION_bio LK_d2i_SSL_SESSION_bio -i2d_SSL_SESSION LK_i2d_SSL_SESSION -i2d_SSL_SESSION_bio LK_i2d_SSL_SESSION_bio -sk_CRYPTO_BUFFER_call_copy_func LK_sk_CRYPTO_BUFFER_call_copy_func -sk_CRYPTO_BUFFER_call_free_func LK_sk_CRYPTO_BUFFER_call_free_func -sk_CRYPTO_BUFFER_deep_copy LK_sk_CRYPTO_BUFFER_deep_copy -sk_CRYPTO_BUFFER_new_null LK_sk_CRYPTO_BUFFER_new_null -sk_CRYPTO_BUFFER_num LK_sk_CRYPTO_BUFFER_num -sk_CRYPTO_BUFFER_pop LK_sk_CRYPTO_BUFFER_pop -sk_CRYPTO_BUFFER_push LK_sk_CRYPTO_BUFFER_push -sk_CRYPTO_BUFFER_set LK_sk_CRYPTO_BUFFER_set -sk_CRYPTO_BUFFER_value LK_sk_CRYPTO_BUFFER_value -sk_SRTP_PROTECTION_PROFILE_new_null LK_sk_SRTP_PROTECTION_PROFILE_new_null -sk_SRTP_PROTECTION_PROFILE_num LK_sk_SRTP_PROTECTION_PROFILE_num -sk_SRTP_PROTECTION_PROFILE_push LK_sk_SRTP_PROTECTION_PROFILE_push -sk_SSL_CIPHER_call_cmp_func LK_sk_SSL_CIPHER_call_cmp_func -sk_SSL_CIPHER_delete LK_sk_SSL_CIPHER_delete -sk_SSL_CIPHER_dup LK_sk_SSL_CIPHER_dup -sk_SSL_CIPHER_find LK_sk_SSL_CIPHER_find -sk_SSL_CIPHER_new_null LK_sk_SSL_CIPHER_new_null -sk_SSL_CIPHER_num LK_sk_SSL_CIPHER_num -sk_SSL_CIPHER_push LK_sk_SSL_CIPHER_push -sk_SSL_CIPHER_value LK_sk_SSL_CIPHER_value -sk_X509_NAME_call_cmp_func LK_sk_X509_NAME_call_cmp_func -sk_X509_NAME_call_copy_func LK_sk_X509_NAME_call_copy_func -sk_X509_NAME_call_free_func LK_sk_X509_NAME_call_free_func -sk_X509_NAME_deep_copy LK_sk_X509_NAME_deep_copy -sk_X509_NAME_find LK_sk_X509_NAME_find -sk_X509_NAME_new LK_sk_X509_NAME_new -sk_X509_NAME_new_null LK_sk_X509_NAME_new_null -sk_X509_NAME_num LK_sk_X509_NAME_num -sk_X509_NAME_pop_free LK_sk_X509_NAME_pop_free -sk_X509_NAME_set LK_sk_X509_NAME_set -sk_X509_NAME_set_cmp_func LK_sk_X509_NAME_set_cmp_func -sk_X509_NAME_sort LK_sk_X509_NAME_sort -sk_X509_NAME_value LK_sk_X509_NAME_value -sk_X509_call_free_func LK_sk_X509_call_free_func -sk_X509_new_null LK_sk_X509_new_null -sk_X509_num LK_sk_X509_num -sk_X509_pop_free LK_sk_X509_pop_free -sk_X509_shift LK_sk_X509_shift -sk_X509_value LK_sk_X509_value -MD4 LK_MD4 -MD4_Final LK_MD4_Final -MD4_Init LK_MD4_Init -MD4_Transform LK_MD4_Transform -MD4_Update LK_MD4_Update -MD5 LK_MD5 -MD5_Final LK_MD5_Final -MD5_Init LK_MD5_Init -MD5_Transform LK_MD5_Transform -MD5_Update LK_MD5_Update -METHOD_ref LK_METHOD_ref -METHOD_unref LK_METHOD_unref -NAME_CONSTRAINTS_check LK_NAME_CONSTRAINTS_check -NAME_CONSTRAINTS_free LK_NAME_CONSTRAINTS_free -NAME_CONSTRAINTS_it LK_NAME_CONSTRAINTS_it -NAME_CONSTRAINTS_new LK_NAME_CONSTRAINTS_new -NCONF_free LK_NCONF_free -NCONF_get_section LK_NCONF_get_section -NCONF_get_string LK_NCONF_get_string -NCONF_load LK_NCONF_load -NCONF_load_bio LK_NCONF_load_bio -NCONF_new LK_NCONF_new -NETSCAPE_SPKAC_free LK_NETSCAPE_SPKAC_free -NETSCAPE_SPKAC_it LK_NETSCAPE_SPKAC_it -NETSCAPE_SPKAC_new LK_NETSCAPE_SPKAC_new -NETSCAPE_SPKI_b64_decode LK_NETSCAPE_SPKI_b64_decode -NETSCAPE_SPKI_b64_encode LK_NETSCAPE_SPKI_b64_encode -NETSCAPE_SPKI_free LK_NETSCAPE_SPKI_free -NETSCAPE_SPKI_get_pubkey LK_NETSCAPE_SPKI_get_pubkey -NETSCAPE_SPKI_it LK_NETSCAPE_SPKI_it -NETSCAPE_SPKI_new LK_NETSCAPE_SPKI_new -NETSCAPE_SPKI_set_pubkey LK_NETSCAPE_SPKI_set_pubkey -NETSCAPE_SPKI_sign LK_NETSCAPE_SPKI_sign -NETSCAPE_SPKI_verify LK_NETSCAPE_SPKI_verify -NOTICEREF_free LK_NOTICEREF_free -NOTICEREF_it LK_NOTICEREF_it -NOTICEREF_new LK_NOTICEREF_new -OBJ_NAME_do_all LK_OBJ_NAME_do_all -OBJ_NAME_do_all_sorted LK_OBJ_NAME_do_all_sorted -OBJ_cbs2nid LK_OBJ_cbs2nid -OBJ_cleanup LK_OBJ_cleanup -OBJ_cmp LK_OBJ_cmp -OBJ_create LK_OBJ_create -OBJ_dup LK_OBJ_dup -OBJ_find_sigid_algs LK_OBJ_find_sigid_algs -OBJ_find_sigid_by_algs LK_OBJ_find_sigid_by_algs -OBJ_get0_data LK_OBJ_get0_data -OBJ_get_undef LK_OBJ_get_undef -OBJ_length LK_OBJ_length -OBJ_ln2nid LK_OBJ_ln2nid -OBJ_nid2cbb LK_OBJ_nid2cbb -OBJ_nid2ln LK_OBJ_nid2ln -OBJ_nid2obj LK_OBJ_nid2obj -OBJ_nid2sn LK_OBJ_nid2sn -OBJ_obj2nid LK_OBJ_obj2nid -OBJ_obj2txt LK_OBJ_obj2txt -OBJ_sn2nid LK_OBJ_sn2nid -OBJ_txt2nid LK_OBJ_txt2nid -OBJ_txt2obj LK_OBJ_txt2obj -OPENSSL_add_all_algorithms_conf LK_OPENSSL_add_all_algorithms_conf -OPENSSL_asprintf LK_OPENSSL_asprintf -OPENSSL_calloc LK_OPENSSL_calloc -OPENSSL_cleanse LK_OPENSSL_cleanse -OPENSSL_cleanup LK_OPENSSL_cleanup -OPENSSL_clear_free LK_OPENSSL_clear_free -OPENSSL_config LK_OPENSSL_config -OPENSSL_cpuid_setup LK_OPENSSL_cpuid_setup -OPENSSL_free LK_OPENSSL_free -OPENSSL_fromxdigit LK_OPENSSL_fromxdigit -OPENSSL_get_ia32cap LK_OPENSSL_get_ia32cap -OPENSSL_gmtime LK_OPENSSL_gmtime -OPENSSL_gmtime_adj LK_OPENSSL_gmtime_adj -OPENSSL_gmtime_diff LK_OPENSSL_gmtime_diff -OPENSSL_hash32 LK_OPENSSL_hash32 -OPENSSL_ia32cap_P LK_OPENSSL_ia32cap_P -OPENSSL_init_crypto LK_OPENSSL_init_crypto OPENSSL_isalnum LK_OPENSSL_isalnum OPENSSL_isalpha LK_OPENSSL_isalpha OPENSSL_isdigit LK_OPENSSL_isdigit @@ -2075,29 +1306,9 @@ OPENSSL_memdup LK_OPENSSL_memdup OPENSSL_no_config LK_OPENSSL_no_config OPENSSL_posix_to_tm LK_OPENSSL_posix_to_tm OPENSSL_realloc LK_OPENSSL_realloc +OPENSSL_reset_malloc_counter_for_testing LK_OPENSSL_reset_malloc_counter_for_testing OPENSSL_secure_clear_free LK_OPENSSL_secure_clear_free OPENSSL_secure_malloc LK_OPENSSL_secure_malloc -OPENSSL_sk_deep_copy LK_OPENSSL_sk_deep_copy -OPENSSL_sk_delete LK_OPENSSL_sk_delete -OPENSSL_sk_delete_if LK_OPENSSL_sk_delete_if -OPENSSL_sk_delete_ptr LK_OPENSSL_sk_delete_ptr -OPENSSL_sk_dup LK_OPENSSL_sk_dup -OPENSSL_sk_find LK_OPENSSL_sk_find -OPENSSL_sk_free LK_OPENSSL_sk_free -OPENSSL_sk_insert LK_OPENSSL_sk_insert -OPENSSL_sk_is_sorted LK_OPENSSL_sk_is_sorted -OPENSSL_sk_new LK_OPENSSL_sk_new -OPENSSL_sk_new_null LK_OPENSSL_sk_new_null -OPENSSL_sk_num LK_OPENSSL_sk_num -OPENSSL_sk_pop LK_OPENSSL_sk_pop -OPENSSL_sk_pop_free_ex LK_OPENSSL_sk_pop_free_ex -OPENSSL_sk_push LK_OPENSSL_sk_push -OPENSSL_sk_set LK_OPENSSL_sk_set -OPENSSL_sk_set_cmp_func LK_OPENSSL_sk_set_cmp_func -OPENSSL_sk_shift LK_OPENSSL_sk_shift -OPENSSL_sk_sort LK_OPENSSL_sk_sort -OPENSSL_sk_value LK_OPENSSL_sk_value -OPENSSL_sk_zero LK_OPENSSL_sk_zero OPENSSL_strcasecmp LK_OPENSSL_strcasecmp OPENSSL_strdup LK_OPENSSL_strdup OPENSSL_strhash LK_OPENSSL_strhash @@ -2111,10 +1322,6 @@ OPENSSL_tm_to_posix LK_OPENSSL_tm_to_posix OPENSSL_tolower LK_OPENSSL_tolower OPENSSL_vasprintf LK_OPENSSL_vasprintf OPENSSL_vasprintf_internal LK_OPENSSL_vasprintf_internal -OPENSSL_zalloc LK_OPENSSL_zalloc -OTHERNAME_free LK_OTHERNAME_free -OTHERNAME_it LK_OTHERNAME_it -OTHERNAME_new LK_OTHERNAME_new OpenSSL_add_all_algorithms LK_OpenSSL_add_all_algorithms OpenSSL_add_all_ciphers LK_OpenSSL_add_all_ciphers OpenSSL_add_all_digests LK_OpenSSL_add_all_digests @@ -2131,99 +1338,21 @@ PEM_def_callback LK_PEM_def_callback PEM_do_header LK_PEM_do_header PEM_get_EVP_CIPHER_INFO LK_PEM_get_EVP_CIPHER_INFO PEM_read LK_PEM_read -PEM_read_DHparams LK_PEM_read_DHparams -PEM_read_DSAPrivateKey LK_PEM_read_DSAPrivateKey -PEM_read_DSA_PUBKEY LK_PEM_read_DSA_PUBKEY -PEM_read_DSAparams LK_PEM_read_DSAparams -PEM_read_ECPrivateKey LK_PEM_read_ECPrivateKey -PEM_read_EC_PUBKEY LK_PEM_read_EC_PUBKEY -PEM_read_PKCS7 LK_PEM_read_PKCS7 -PEM_read_PKCS8 LK_PEM_read_PKCS8 -PEM_read_PKCS8_PRIV_KEY_INFO LK_PEM_read_PKCS8_PRIV_KEY_INFO -PEM_read_PUBKEY LK_PEM_read_PUBKEY -PEM_read_PrivateKey LK_PEM_read_PrivateKey -PEM_read_RSAPrivateKey LK_PEM_read_RSAPrivateKey -PEM_read_RSAPublicKey LK_PEM_read_RSAPublicKey -PEM_read_RSA_PUBKEY LK_PEM_read_RSA_PUBKEY -PEM_read_X509 LK_PEM_read_X509 -PEM_read_X509_AUX LK_PEM_read_X509_AUX -PEM_read_X509_CRL LK_PEM_read_X509_CRL -PEM_read_X509_REQ LK_PEM_read_X509_REQ PEM_read_bio LK_PEM_read_bio -PEM_read_bio_DHparams LK_PEM_read_bio_DHparams -PEM_read_bio_DSAPrivateKey LK_PEM_read_bio_DSAPrivateKey -PEM_read_bio_DSA_PUBKEY LK_PEM_read_bio_DSA_PUBKEY -PEM_read_bio_DSAparams LK_PEM_read_bio_DSAparams -PEM_read_bio_ECPrivateKey LK_PEM_read_bio_ECPrivateKey -PEM_read_bio_EC_PUBKEY LK_PEM_read_bio_EC_PUBKEY -PEM_read_bio_PKCS7 LK_PEM_read_bio_PKCS7 -PEM_read_bio_PKCS8 LK_PEM_read_bio_PKCS8 -PEM_read_bio_PKCS8_PRIV_KEY_INFO LK_PEM_read_bio_PKCS8_PRIV_KEY_INFO -PEM_read_bio_PUBKEY LK_PEM_read_bio_PUBKEY -PEM_read_bio_PrivateKey LK_PEM_read_bio_PrivateKey -PEM_read_bio_RSAPrivateKey LK_PEM_read_bio_RSAPrivateKey -PEM_read_bio_RSAPublicKey LK_PEM_read_bio_RSAPublicKey -PEM_read_bio_RSA_PUBKEY LK_PEM_read_bio_RSA_PUBKEY -PEM_read_bio_X509 LK_PEM_read_bio_X509 -PEM_read_bio_X509_AUX LK_PEM_read_bio_X509_AUX -PEM_read_bio_X509_CRL LK_PEM_read_bio_X509_CRL -PEM_read_bio_X509_REQ LK_PEM_read_bio_X509_REQ PEM_write LK_PEM_write -PEM_write_DHparams LK_PEM_write_DHparams -PEM_write_DSAPrivateKey LK_PEM_write_DSAPrivateKey -PEM_write_DSA_PUBKEY LK_PEM_write_DSA_PUBKEY -PEM_write_DSAparams LK_PEM_write_DSAparams -PEM_write_ECPrivateKey LK_PEM_write_ECPrivateKey -PEM_write_EC_PUBKEY LK_PEM_write_EC_PUBKEY -PEM_write_PKCS7 LK_PEM_write_PKCS7 -PEM_write_PKCS8 LK_PEM_write_PKCS8 PEM_write_PKCS8PrivateKey LK_PEM_write_PKCS8PrivateKey PEM_write_PKCS8PrivateKey_nid LK_PEM_write_PKCS8PrivateKey_nid -PEM_write_PKCS8_PRIV_KEY_INFO LK_PEM_write_PKCS8_PRIV_KEY_INFO -PEM_write_PUBKEY LK_PEM_write_PUBKEY -PEM_write_PrivateKey LK_PEM_write_PrivateKey -PEM_write_RSAPrivateKey LK_PEM_write_RSAPrivateKey -PEM_write_RSAPublicKey LK_PEM_write_RSAPublicKey -PEM_write_RSA_PUBKEY LK_PEM_write_RSA_PUBKEY -PEM_write_X509 LK_PEM_write_X509 -PEM_write_X509_AUX LK_PEM_write_X509_AUX -PEM_write_X509_CRL LK_PEM_write_X509_CRL -PEM_write_X509_REQ LK_PEM_write_X509_REQ -PEM_write_X509_REQ_NEW LK_PEM_write_X509_REQ_NEW PEM_write_bio LK_PEM_write_bio -PEM_write_bio_DHparams LK_PEM_write_bio_DHparams -PEM_write_bio_DSAPrivateKey LK_PEM_write_bio_DSAPrivateKey -PEM_write_bio_DSA_PUBKEY LK_PEM_write_bio_DSA_PUBKEY -PEM_write_bio_DSAparams LK_PEM_write_bio_DSAparams -PEM_write_bio_ECPrivateKey LK_PEM_write_bio_ECPrivateKey -PEM_write_bio_EC_PUBKEY LK_PEM_write_bio_EC_PUBKEY -PEM_write_bio_PKCS7 LK_PEM_write_bio_PKCS7 -PEM_write_bio_PKCS8 LK_PEM_write_bio_PKCS8 PEM_write_bio_PKCS8PrivateKey LK_PEM_write_bio_PKCS8PrivateKey PEM_write_bio_PKCS8PrivateKey_nid LK_PEM_write_bio_PKCS8PrivateKey_nid -PEM_write_bio_PKCS8_PRIV_KEY_INFO LK_PEM_write_bio_PKCS8_PRIV_KEY_INFO -PEM_write_bio_PUBKEY LK_PEM_write_bio_PUBKEY -PEM_write_bio_PrivateKey LK_PEM_write_bio_PrivateKey -PEM_write_bio_RSAPrivateKey LK_PEM_write_bio_RSAPrivateKey -PEM_write_bio_RSAPublicKey LK_PEM_write_bio_RSAPublicKey -PEM_write_bio_RSA_PUBKEY LK_PEM_write_bio_RSA_PUBKEY -PEM_write_bio_X509 LK_PEM_write_bio_X509 -PEM_write_bio_X509_AUX LK_PEM_write_bio_X509_AUX -PEM_write_bio_X509_CRL LK_PEM_write_bio_X509_CRL -PEM_write_bio_X509_REQ LK_PEM_write_bio_X509_REQ -PEM_write_bio_X509_REQ_NEW LK_PEM_write_bio_X509_REQ_NEW PKCS12_PBE_add LK_PKCS12_PBE_add PKCS12_create LK_PKCS12_create PKCS12_free LK_PKCS12_free PKCS12_get_key_and_certs LK_PKCS12_get_key_and_certs PKCS12_parse LK_PKCS12_parse PKCS12_verify_mac LK_PKCS12_verify_mac -PKCS1_MGF1 LK_PKCS1_MGF1 PKCS5_PBKDF2_HMAC LK_PKCS5_PBKDF2_HMAC PKCS5_PBKDF2_HMAC_SHA1 LK_PKCS5_PBKDF2_HMAC_SHA1 -PKCS5_pbe2_decrypt_init LK_PKCS5_pbe2_decrypt_init -PKCS5_pbe2_encrypt_init LK_PKCS5_pbe2_encrypt_init -PKCS7_bundle_CRLs LK_PKCS7_bundle_CRLs PKCS7_bundle_certificates LK_PKCS7_bundle_certificates PKCS7_bundle_raw_certificates LK_PKCS7_bundle_raw_certificates PKCS7_free LK_PKCS7_free @@ -2239,33 +1368,15 @@ PKCS7_type_is_encrypted LK_PKCS7_type_is_encrypted PKCS7_type_is_enveloped LK_PKCS7_type_is_enveloped PKCS7_type_is_signed LK_PKCS7_type_is_signed PKCS7_type_is_signedAndEnveloped LK_PKCS7_type_is_signedAndEnveloped -PKCS8_PRIV_KEY_INFO_free LK_PKCS8_PRIV_KEY_INFO_free -PKCS8_PRIV_KEY_INFO_it LK_PKCS8_PRIV_KEY_INFO_it -PKCS8_PRIV_KEY_INFO_new LK_PKCS8_PRIV_KEY_INFO_new PKCS8_decrypt LK_PKCS8_decrypt PKCS8_encrypt LK_PKCS8_encrypt PKCS8_marshal_encrypted_private_key LK_PKCS8_marshal_encrypted_private_key PKCS8_parse_encrypted_private_key LK_PKCS8_parse_encrypted_private_key -POLICYINFO_free LK_POLICYINFO_free -POLICYINFO_it LK_POLICYINFO_it -POLICYINFO_new LK_POLICYINFO_new -POLICYQUALINFO_free LK_POLICYQUALINFO_free -POLICYQUALINFO_it LK_POLICYQUALINFO_it -POLICYQUALINFO_new LK_POLICYQUALINFO_new -POLICY_CONSTRAINTS_free LK_POLICY_CONSTRAINTS_free -POLICY_CONSTRAINTS_it LK_POLICY_CONSTRAINTS_it -POLICY_CONSTRAINTS_new LK_POLICY_CONSTRAINTS_new -POLICY_MAPPINGS_it LK_POLICY_MAPPINGS_it -POLICY_MAPPING_free LK_POLICY_MAPPING_free -POLICY_MAPPING_it LK_POLICY_MAPPING_it -POLICY_MAPPING_new LK_POLICY_MAPPING_new RAND_OpenSSL LK_RAND_OpenSSL RAND_SSLeay LK_RAND_SSLeay RAND_add LK_RAND_add RAND_bytes LK_RAND_bytes -RAND_bytes_with_additional_data LK_RAND_bytes_with_additional_data RAND_cleanup LK_RAND_cleanup -RAND_disable_fork_unsafe_buffering LK_RAND_disable_fork_unsafe_buffering RAND_egd LK_RAND_egd RAND_enable_fork_unsafe_buffering LK_RAND_enable_fork_unsafe_buffering RAND_file_name LK_RAND_file_name @@ -2274,6 +1385,7 @@ RAND_get_system_entropy_for_custom_prng LK_RAND_get_system_entropy_for_custom_pr RAND_load_file LK_RAND_load_file RAND_poll LK_RAND_poll RAND_pseudo_bytes LK_RAND_pseudo_bytes +RAND_reset_for_fuzzing LK_RAND_reset_for_fuzzing RAND_seed LK_RAND_seed RAND_set_rand_method LK_RAND_set_rand_method RAND_status LK_RAND_status @@ -2287,17 +1399,12 @@ RIPEMD160_Transform LK_RIPEMD160_Transform RIPEMD160_Update LK_RIPEMD160_Update RSAPrivateKey_dup LK_RSAPrivateKey_dup RSAPublicKey_dup LK_RSAPublicKey_dup -RSAZ_1024_mod_exp_avx2 LK_RSAZ_1024_mod_exp_avx2 -RSA_PSS_PARAMS_free LK_RSA_PSS_PARAMS_free -RSA_PSS_PARAMS_it LK_RSA_PSS_PARAMS_it -RSA_PSS_PARAMS_new LK_RSA_PSS_PARAMS_new RSA_add_pkcs1_prefix LK_RSA_add_pkcs1_prefix RSA_bits LK_RSA_bits RSA_blinding_on LK_RSA_blinding_on RSA_check_fips LK_RSA_check_fips RSA_check_key LK_RSA_check_key RSA_decrypt LK_RSA_decrypt -RSA_default_method LK_RSA_default_method RSA_encrypt LK_RSA_encrypt RSA_flags LK_RSA_flags RSA_free LK_RSA_free @@ -2323,21 +1430,10 @@ RSA_marshal_private_key LK_RSA_marshal_private_key RSA_marshal_public_key LK_RSA_marshal_public_key RSA_new LK_RSA_new RSA_new_method LK_RSA_new_method -RSA_new_method_no_e LK_RSA_new_method_no_e -RSA_new_private_key LK_RSA_new_private_key -RSA_new_private_key_large_e LK_RSA_new_private_key_large_e -RSA_new_private_key_no_crt LK_RSA_new_private_key_no_crt -RSA_new_private_key_no_e LK_RSA_new_private_key_no_e -RSA_new_public_key LK_RSA_new_public_key -RSA_new_public_key_large_e LK_RSA_new_public_key_large_e RSA_padding_add_PKCS1_OAEP LK_RSA_padding_add_PKCS1_OAEP RSA_padding_add_PKCS1_OAEP_mgf1 LK_RSA_padding_add_PKCS1_OAEP_mgf1 RSA_padding_add_PKCS1_PSS LK_RSA_padding_add_PKCS1_PSS RSA_padding_add_PKCS1_PSS_mgf1 LK_RSA_padding_add_PKCS1_PSS_mgf1 -RSA_padding_add_PKCS1_type_1 LK_RSA_padding_add_PKCS1_type_1 -RSA_padding_add_none LK_RSA_padding_add_none -RSA_padding_check_PKCS1_OAEP_mgf1 LK_RSA_padding_check_PKCS1_OAEP_mgf1 -RSA_padding_check_PKCS1_type_1 LK_RSA_padding_check_PKCS1_type_1 RSA_parse_private_key LK_RSA_parse_private_key RSA_parse_public_key LK_RSA_parse_public_key RSA_print LK_RSA_print @@ -2397,18 +1493,527 @@ SPAKE2_CTX_free LK_SPAKE2_CTX_free SPAKE2_CTX_new LK_SPAKE2_CTX_new SPAKE2_generate_msg LK_SPAKE2_generate_msg SPAKE2_process_msg LK_SPAKE2_process_msg -SPX_generate_key LK_SPX_generate_key -SPX_generate_key_from_seed LK_SPX_generate_key_from_seed -SPX_sign LK_SPX_sign -SPX_verify LK_SPX_verify +SSL_CIPHER_description LK_SSL_CIPHER_description +SSL_CIPHER_get_auth_nid LK_SSL_CIPHER_get_auth_nid +SSL_CIPHER_get_bits LK_SSL_CIPHER_get_bits +SSL_CIPHER_get_cipher_nid LK_SSL_CIPHER_get_cipher_nid +SSL_CIPHER_get_digest_nid LK_SSL_CIPHER_get_digest_nid +SSL_CIPHER_get_id LK_SSL_CIPHER_get_id +SSL_CIPHER_get_kx_name LK_SSL_CIPHER_get_kx_name +SSL_CIPHER_get_kx_nid LK_SSL_CIPHER_get_kx_nid +SSL_CIPHER_get_max_version LK_SSL_CIPHER_get_max_version +SSL_CIPHER_get_min_version LK_SSL_CIPHER_get_min_version +SSL_CIPHER_get_name LK_SSL_CIPHER_get_name +SSL_CIPHER_get_prf_nid LK_SSL_CIPHER_get_prf_nid +SSL_CIPHER_get_protocol_id LK_SSL_CIPHER_get_protocol_id +SSL_CIPHER_get_value LK_SSL_CIPHER_get_value +SSL_CIPHER_get_version LK_SSL_CIPHER_get_version +SSL_CIPHER_is_aead LK_SSL_CIPHER_is_aead +SSL_CIPHER_is_block_cipher LK_SSL_CIPHER_is_block_cipher +SSL_CIPHER_standard_name LK_SSL_CIPHER_standard_name +SSL_COMP_add_compression_method LK_SSL_COMP_add_compression_method +SSL_COMP_free_compression_methods LK_SSL_COMP_free_compression_methods +SSL_COMP_get0_name LK_SSL_COMP_get0_name +SSL_COMP_get_compression_methods LK_SSL_COMP_get_compression_methods +SSL_COMP_get_id LK_SSL_COMP_get_id +SSL_COMP_get_name LK_SSL_COMP_get_name +SSL_CTX_add0_chain_cert LK_SSL_CTX_add0_chain_cert +SSL_CTX_add1_chain_cert LK_SSL_CTX_add1_chain_cert +SSL_CTX_add_cert_compression_alg LK_SSL_CTX_add_cert_compression_alg +SSL_CTX_add_client_CA LK_SSL_CTX_add_client_CA +SSL_CTX_add_extra_chain_cert LK_SSL_CTX_add_extra_chain_cert +SSL_CTX_add_session LK_SSL_CTX_add_session +SSL_CTX_check_private_key LK_SSL_CTX_check_private_key +SSL_CTX_cipher_in_group LK_SSL_CTX_cipher_in_group +SSL_CTX_clear_chain_certs LK_SSL_CTX_clear_chain_certs +SSL_CTX_clear_extra_chain_certs LK_SSL_CTX_clear_extra_chain_certs +SSL_CTX_clear_mode LK_SSL_CTX_clear_mode +SSL_CTX_clear_options LK_SSL_CTX_clear_options +SSL_CTX_enable_ocsp_stapling LK_SSL_CTX_enable_ocsp_stapling +SSL_CTX_enable_signed_cert_timestamps LK_SSL_CTX_enable_signed_cert_timestamps +SSL_CTX_enable_tls_channel_id LK_SSL_CTX_enable_tls_channel_id +SSL_CTX_flush_sessions LK_SSL_CTX_flush_sessions +SSL_CTX_free LK_SSL_CTX_free +SSL_CTX_get0_certificate LK_SSL_CTX_get0_certificate +SSL_CTX_get0_chain_certs LK_SSL_CTX_get0_chain_certs +SSL_CTX_get0_param LK_SSL_CTX_get0_param +SSL_CTX_get0_privatekey LK_SSL_CTX_get0_privatekey +SSL_CTX_get_cert_store LK_SSL_CTX_get_cert_store +SSL_CTX_get_ciphers LK_SSL_CTX_get_ciphers +SSL_CTX_get_default_passwd_cb LK_SSL_CTX_get_default_passwd_cb +SSL_CTX_get_default_passwd_cb_userdata LK_SSL_CTX_get_default_passwd_cb_userdata +SSL_CTX_get_ex_data LK_SSL_CTX_get_ex_data +SSL_CTX_get_ex_new_index LK_SSL_CTX_get_ex_new_index +SSL_CTX_get_extra_chain_certs LK_SSL_CTX_get_extra_chain_certs +SSL_CTX_get_max_cert_list LK_SSL_CTX_get_max_cert_list +SSL_CTX_get_max_proto_version LK_SSL_CTX_get_max_proto_version +SSL_CTX_get_min_proto_version LK_SSL_CTX_get_min_proto_version +SSL_CTX_get_mode LK_SSL_CTX_get_mode +SSL_CTX_get_num_tickets LK_SSL_CTX_get_num_tickets +SSL_CTX_get_options LK_SSL_CTX_get_options +SSL_CTX_get_quiet_shutdown LK_SSL_CTX_get_quiet_shutdown +SSL_CTX_get_read_ahead LK_SSL_CTX_get_read_ahead +SSL_CTX_get_session_cache_mode LK_SSL_CTX_get_session_cache_mode +SSL_CTX_get_timeout LK_SSL_CTX_get_timeout +SSL_CTX_get_tlsext_ticket_keys LK_SSL_CTX_get_tlsext_ticket_keys +SSL_CTX_get_verify_depth LK_SSL_CTX_get_verify_depth +SSL_CTX_get_verify_mode LK_SSL_CTX_get_verify_mode +SSL_CTX_load_verify_locations LK_SSL_CTX_load_verify_locations +SSL_CTX_need_tmp_RSA LK_SSL_CTX_need_tmp_RSA +SSL_CTX_new LK_SSL_CTX_new +SSL_CTX_remove_session LK_SSL_CTX_remove_session +SSL_CTX_sess_accept LK_SSL_CTX_sess_accept +SSL_CTX_sess_accept_good LK_SSL_CTX_sess_accept_good +SSL_CTX_sess_accept_renegotiate LK_SSL_CTX_sess_accept_renegotiate +SSL_CTX_sess_cache_full LK_SSL_CTX_sess_cache_full +SSL_CTX_sess_cb_hits LK_SSL_CTX_sess_cb_hits +SSL_CTX_sess_connect LK_SSL_CTX_sess_connect +SSL_CTX_sess_connect_good LK_SSL_CTX_sess_connect_good +SSL_CTX_sess_connect_renegotiate LK_SSL_CTX_sess_connect_renegotiate +SSL_CTX_sess_get_cache_size LK_SSL_CTX_sess_get_cache_size +SSL_CTX_sess_hits LK_SSL_CTX_sess_hits +SSL_CTX_sess_misses LK_SSL_CTX_sess_misses +SSL_CTX_sess_number LK_SSL_CTX_sess_number +SSL_CTX_sess_set_cache_size LK_SSL_CTX_sess_set_cache_size +SSL_CTX_sess_set_get_cb LK_SSL_CTX_sess_set_get_cb +SSL_CTX_sess_set_new_cb LK_SSL_CTX_sess_set_new_cb +SSL_CTX_sess_set_remove_cb LK_SSL_CTX_sess_set_remove_cb +SSL_CTX_sess_timeouts LK_SSL_CTX_sess_timeouts +SSL_CTX_set0_buffer_pool LK_SSL_CTX_set0_buffer_pool +SSL_CTX_set0_client_CAs LK_SSL_CTX_set0_client_CAs +SSL_CTX_set0_verify_cert_store LK_SSL_CTX_set0_verify_cert_store +SSL_CTX_set1_curves LK_SSL_CTX_set1_curves +SSL_CTX_set1_curves_list LK_SSL_CTX_set1_curves_list +SSL_CTX_set1_ech_keys LK_SSL_CTX_set1_ech_keys +SSL_CTX_set1_groups LK_SSL_CTX_set1_groups +SSL_CTX_set1_groups_list LK_SSL_CTX_set1_groups_list +SSL_CTX_set1_param LK_SSL_CTX_set1_param +SSL_CTX_set1_sigalgs LK_SSL_CTX_set1_sigalgs +SSL_CTX_set1_sigalgs_list LK_SSL_CTX_set1_sigalgs_list +SSL_CTX_set1_tls_channel_id LK_SSL_CTX_set1_tls_channel_id +SSL_CTX_set1_verify_cert_store LK_SSL_CTX_set1_verify_cert_store +SSL_CTX_set_aes_hw_override_for_testing LK_SSL_CTX_set_aes_hw_override_for_testing +SSL_CTX_set_allow_unknown_alpn_protos LK_SSL_CTX_set_allow_unknown_alpn_protos +SSL_CTX_set_alpn_protos LK_SSL_CTX_set_alpn_protos +SSL_CTX_set_alpn_select_cb LK_SSL_CTX_set_alpn_select_cb +SSL_CTX_set_cert_cb LK_SSL_CTX_set_cert_cb +SSL_CTX_set_cert_store LK_SSL_CTX_set_cert_store +SSL_CTX_set_cert_verify_callback LK_SSL_CTX_set_cert_verify_callback +SSL_CTX_set_chain_and_key LK_SSL_CTX_set_chain_and_key +SSL_CTX_set_cipher_list LK_SSL_CTX_set_cipher_list +SSL_CTX_set_client_CA_list LK_SSL_CTX_set_client_CA_list +SSL_CTX_set_client_cert_cb LK_SSL_CTX_set_client_cert_cb +SSL_CTX_set_compliance_policy LK_SSL_CTX_set_compliance_policy +SSL_CTX_set_current_time_cb LK_SSL_CTX_set_current_time_cb +SSL_CTX_set_custom_verify LK_SSL_CTX_set_custom_verify +SSL_CTX_set_default_passwd_cb LK_SSL_CTX_set_default_passwd_cb +SSL_CTX_set_default_passwd_cb_userdata LK_SSL_CTX_set_default_passwd_cb_userdata +SSL_CTX_set_default_verify_paths LK_SSL_CTX_set_default_verify_paths +SSL_CTX_set_dos_protection_cb LK_SSL_CTX_set_dos_protection_cb +SSL_CTX_set_early_data_enabled LK_SSL_CTX_set_early_data_enabled +SSL_CTX_set_ex_data LK_SSL_CTX_set_ex_data +SSL_CTX_set_false_start_allowed_without_alpn LK_SSL_CTX_set_false_start_allowed_without_alpn +SSL_CTX_set_grease_enabled LK_SSL_CTX_set_grease_enabled +SSL_CTX_set_handoff_mode LK_SSL_CTX_set_handoff_mode +SSL_CTX_set_info_callback LK_SSL_CTX_set_info_callback +SSL_CTX_set_keylog_callback LK_SSL_CTX_set_keylog_callback +SSL_CTX_set_max_cert_list LK_SSL_CTX_set_max_cert_list +SSL_CTX_set_max_proto_version LK_SSL_CTX_set_max_proto_version +SSL_CTX_set_max_send_fragment LK_SSL_CTX_set_max_send_fragment +SSL_CTX_set_min_proto_version LK_SSL_CTX_set_min_proto_version +SSL_CTX_set_mode LK_SSL_CTX_set_mode +SSL_CTX_set_msg_callback LK_SSL_CTX_set_msg_callback +SSL_CTX_set_msg_callback_arg LK_SSL_CTX_set_msg_callback_arg +SSL_CTX_set_next_proto_select_cb LK_SSL_CTX_set_next_proto_select_cb +SSL_CTX_set_next_protos_advertised_cb LK_SSL_CTX_set_next_protos_advertised_cb +SSL_CTX_set_num_tickets LK_SSL_CTX_set_num_tickets +SSL_CTX_set_ocsp_response LK_SSL_CTX_set_ocsp_response +SSL_CTX_set_options LK_SSL_CTX_set_options +SSL_CTX_set_permute_extensions LK_SSL_CTX_set_permute_extensions +SSL_CTX_set_private_key_method LK_SSL_CTX_set_private_key_method +SSL_CTX_set_psk_client_callback LK_SSL_CTX_set_psk_client_callback +SSL_CTX_set_psk_server_callback LK_SSL_CTX_set_psk_server_callback +SSL_CTX_set_purpose LK_SSL_CTX_set_purpose +SSL_CTX_set_quic_method LK_SSL_CTX_set_quic_method +SSL_CTX_set_quiet_shutdown LK_SSL_CTX_set_quiet_shutdown +SSL_CTX_set_read_ahead LK_SSL_CTX_set_read_ahead +SSL_CTX_set_record_protocol_version LK_SSL_CTX_set_record_protocol_version +SSL_CTX_set_retain_only_sha256_of_client_certs LK_SSL_CTX_set_retain_only_sha256_of_client_certs +SSL_CTX_set_reverify_on_resume LK_SSL_CTX_set_reverify_on_resume +SSL_CTX_set_select_certificate_cb LK_SSL_CTX_set_select_certificate_cb +SSL_CTX_set_session_cache_mode LK_SSL_CTX_set_session_cache_mode +SSL_CTX_set_session_id_context LK_SSL_CTX_set_session_id_context +SSL_CTX_set_session_psk_dhe_timeout LK_SSL_CTX_set_session_psk_dhe_timeout +SSL_CTX_set_signed_cert_timestamp_list LK_SSL_CTX_set_signed_cert_timestamp_list +SSL_CTX_set_signing_algorithm_prefs LK_SSL_CTX_set_signing_algorithm_prefs +SSL_CTX_set_srtp_profiles LK_SSL_CTX_set_srtp_profiles +SSL_CTX_set_strict_cipher_list LK_SSL_CTX_set_strict_cipher_list +SSL_CTX_set_ticket_aead_method LK_SSL_CTX_set_ticket_aead_method +SSL_CTX_set_timeout LK_SSL_CTX_set_timeout +SSL_CTX_set_tls_channel_id_enabled LK_SSL_CTX_set_tls_channel_id_enabled +SSL_CTX_set_tlsext_servername_arg LK_SSL_CTX_set_tlsext_servername_arg +SSL_CTX_set_tlsext_servername_callback LK_SSL_CTX_set_tlsext_servername_callback +SSL_CTX_set_tlsext_status_arg LK_SSL_CTX_set_tlsext_status_arg +SSL_CTX_set_tlsext_status_cb LK_SSL_CTX_set_tlsext_status_cb +SSL_CTX_set_tlsext_ticket_key_cb LK_SSL_CTX_set_tlsext_ticket_key_cb +SSL_CTX_set_tlsext_ticket_keys LK_SSL_CTX_set_tlsext_ticket_keys +SSL_CTX_set_tlsext_use_srtp LK_SSL_CTX_set_tlsext_use_srtp +SSL_CTX_set_tmp_dh LK_SSL_CTX_set_tmp_dh +SSL_CTX_set_tmp_dh_callback LK_SSL_CTX_set_tmp_dh_callback +SSL_CTX_set_tmp_ecdh LK_SSL_CTX_set_tmp_ecdh +SSL_CTX_set_tmp_rsa LK_SSL_CTX_set_tmp_rsa +SSL_CTX_set_tmp_rsa_callback LK_SSL_CTX_set_tmp_rsa_callback +SSL_CTX_set_trust LK_SSL_CTX_set_trust +SSL_CTX_set_verify LK_SSL_CTX_set_verify +SSL_CTX_set_verify_algorithm_prefs LK_SSL_CTX_set_verify_algorithm_prefs +SSL_CTX_set_verify_depth LK_SSL_CTX_set_verify_depth +SSL_CTX_up_ref LK_SSL_CTX_up_ref +SSL_CTX_use_PrivateKey LK_SSL_CTX_use_PrivateKey +SSL_CTX_use_PrivateKey_ASN1 LK_SSL_CTX_use_PrivateKey_ASN1 +SSL_CTX_use_PrivateKey_file LK_SSL_CTX_use_PrivateKey_file +SSL_CTX_use_RSAPrivateKey LK_SSL_CTX_use_RSAPrivateKey +SSL_CTX_use_RSAPrivateKey_ASN1 LK_SSL_CTX_use_RSAPrivateKey_ASN1 +SSL_CTX_use_RSAPrivateKey_file LK_SSL_CTX_use_RSAPrivateKey_file +SSL_CTX_use_certificate LK_SSL_CTX_use_certificate +SSL_CTX_use_certificate_ASN1 LK_SSL_CTX_use_certificate_ASN1 +SSL_CTX_use_certificate_chain_file LK_SSL_CTX_use_certificate_chain_file +SSL_CTX_use_certificate_file LK_SSL_CTX_use_certificate_file +SSL_CTX_use_psk_identity_hint LK_SSL_CTX_use_psk_identity_hint +SSL_ECH_KEYS_add LK_SSL_ECH_KEYS_add +SSL_ECH_KEYS_free LK_SSL_ECH_KEYS_free +SSL_ECH_KEYS_has_duplicate_config_id LK_SSL_ECH_KEYS_has_duplicate_config_id +SSL_ECH_KEYS_marshal_retry_configs LK_SSL_ECH_KEYS_marshal_retry_configs +SSL_ECH_KEYS_new LK_SSL_ECH_KEYS_new +SSL_ECH_KEYS_up_ref LK_SSL_ECH_KEYS_up_ref +SSL_SESSION_copy_without_early_data LK_SSL_SESSION_copy_without_early_data +SSL_SESSION_dup LK_SSL_SESSION_dup +SSL_SESSION_early_data_capable LK_SSL_SESSION_early_data_capable +SSL_SESSION_free LK_SSL_SESSION_free +SSL_SESSION_from_bytes LK_SSL_SESSION_from_bytes +SSL_SESSION_get0_cipher LK_SSL_SESSION_get0_cipher +SSL_SESSION_get0_id_context LK_SSL_SESSION_get0_id_context +SSL_SESSION_get0_ocsp_response LK_SSL_SESSION_get0_ocsp_response +SSL_SESSION_get0_peer LK_SSL_SESSION_get0_peer +SSL_SESSION_get0_peer_sha256 LK_SSL_SESSION_get0_peer_sha256 +SSL_SESSION_get0_signed_cert_timestamp_list LK_SSL_SESSION_get0_signed_cert_timestamp_list +SSL_SESSION_get0_ticket LK_SSL_SESSION_get0_ticket +SSL_SESSION_get_ex_data LK_SSL_SESSION_get_ex_data +SSL_SESSION_get_ex_new_index LK_SSL_SESSION_get_ex_new_index +SSL_SESSION_get_id LK_SSL_SESSION_get_id +SSL_SESSION_get_master_key LK_SSL_SESSION_get_master_key +SSL_SESSION_get_protocol_version LK_SSL_SESSION_get_protocol_version +SSL_SESSION_get_ticket_lifetime_hint LK_SSL_SESSION_get_ticket_lifetime_hint +SSL_SESSION_get_time LK_SSL_SESSION_get_time +SSL_SESSION_get_timeout LK_SSL_SESSION_get_timeout +SSL_SESSION_get_version LK_SSL_SESSION_get_version +SSL_SESSION_has_peer_sha256 LK_SSL_SESSION_has_peer_sha256 +SSL_SESSION_has_ticket LK_SSL_SESSION_has_ticket +SSL_SESSION_is_resumable LK_SSL_SESSION_is_resumable +SSL_SESSION_new LK_SSL_SESSION_new +SSL_SESSION_parse LK_SSL_SESSION_parse +SSL_SESSION_set1_id LK_SSL_SESSION_set1_id +SSL_SESSION_set1_id_context LK_SSL_SESSION_set1_id_context +SSL_SESSION_set_ex_data LK_SSL_SESSION_set_ex_data +SSL_SESSION_set_protocol_version LK_SSL_SESSION_set_protocol_version +SSL_SESSION_set_ticket LK_SSL_SESSION_set_ticket +SSL_SESSION_set_time LK_SSL_SESSION_set_time +SSL_SESSION_set_timeout LK_SSL_SESSION_set_timeout +SSL_SESSION_should_be_single_use LK_SSL_SESSION_should_be_single_use +SSL_SESSION_to_bytes LK_SSL_SESSION_to_bytes +SSL_SESSION_to_bytes_for_ticket LK_SSL_SESSION_to_bytes_for_ticket +SSL_SESSION_up_ref LK_SSL_SESSION_up_ref +SSL_accept LK_SSL_accept +SSL_add0_chain_cert LK_SSL_add0_chain_cert +SSL_add1_chain_cert LK_SSL_add1_chain_cert +SSL_add_application_settings LK_SSL_add_application_settings +SSL_add_bio_cert_subjects_to_stack LK_SSL_add_bio_cert_subjects_to_stack +SSL_add_client_CA LK_SSL_add_client_CA SSL_add_dir_cert_subjects_to_stack LK_SSL_add_dir_cert_subjects_to_stack +SSL_add_file_cert_subjects_to_stack LK_SSL_add_file_cert_subjects_to_stack +SSL_alert_desc_string LK_SSL_alert_desc_string +SSL_alert_desc_string_long LK_SSL_alert_desc_string_long +SSL_alert_from_verify_result LK_SSL_alert_from_verify_result +SSL_alert_type_string LK_SSL_alert_type_string +SSL_alert_type_string_long LK_SSL_alert_type_string_long +SSL_apply_handback LK_SSL_apply_handback +SSL_apply_handoff LK_SSL_apply_handoff +SSL_cache_hit LK_SSL_cache_hit +SSL_can_release_private_key LK_SSL_can_release_private_key +SSL_certs_clear LK_SSL_certs_clear +SSL_check_private_key LK_SSL_check_private_key +SSL_clear LK_SSL_clear +SSL_clear_chain_certs LK_SSL_clear_chain_certs +SSL_clear_mode LK_SSL_clear_mode +SSL_clear_options LK_SSL_clear_options +SSL_connect LK_SSL_connect +SSL_cutthrough_complete LK_SSL_cutthrough_complete +SSL_decline_handoff LK_SSL_decline_handoff +SSL_delegated_credential_used LK_SSL_delegated_credential_used +SSL_do_handshake LK_SSL_do_handshake +SSL_dup_CA_list LK_SSL_dup_CA_list +SSL_early_callback_ctx_extension_get LK_SSL_early_callback_ctx_extension_get +SSL_early_data_accepted LK_SSL_early_data_accepted +SSL_early_data_reason_string LK_SSL_early_data_reason_string +SSL_ech_accepted LK_SSL_ech_accepted +SSL_enable_ocsp_stapling LK_SSL_enable_ocsp_stapling +SSL_enable_signed_cert_timestamps LK_SSL_enable_signed_cert_timestamps +SSL_enable_tls_channel_id LK_SSL_enable_tls_channel_id +SSL_error_description LK_SSL_error_description +SSL_export_keying_material LK_SSL_export_keying_material +SSL_free LK_SSL_free +SSL_generate_key_block LK_SSL_generate_key_block +SSL_get0_alpn_selected LK_SSL_get0_alpn_selected +SSL_get0_certificate_types LK_SSL_get0_certificate_types +SSL_get0_chain_certs LK_SSL_get0_chain_certs +SSL_get0_ech_name_override LK_SSL_get0_ech_name_override +SSL_get0_ech_retry_configs LK_SSL_get0_ech_retry_configs +SSL_get0_next_proto_negotiated LK_SSL_get0_next_proto_negotiated +SSL_get0_ocsp_response LK_SSL_get0_ocsp_response +SSL_get0_param LK_SSL_get0_param +SSL_get0_peer_application_settings LK_SSL_get0_peer_application_settings +SSL_get0_peer_delegation_algorithms LK_SSL_get0_peer_delegation_algorithms +SSL_get0_peer_verify_algorithms LK_SSL_get0_peer_verify_algorithms +SSL_get0_session_id_context LK_SSL_get0_session_id_context +SSL_get0_signed_cert_timestamp_list LK_SSL_get0_signed_cert_timestamp_list +SSL_get1_session LK_SSL_get1_session +SSL_get_SSL_CTX LK_SSL_get_SSL_CTX +SSL_get_certificate LK_SSL_get_certificate +SSL_get_cipher_by_value LK_SSL_get_cipher_by_value +SSL_get_cipher_list LK_SSL_get_cipher_list +SSL_get_ciphers LK_SSL_get_ciphers +SSL_get_client_CA_list LK_SSL_get_client_CA_list +SSL_get_client_random LK_SSL_get_client_random +SSL_get_current_cipher LK_SSL_get_current_cipher +SSL_get_current_compression LK_SSL_get_current_compression +SSL_get_current_expansion LK_SSL_get_current_expansion +SSL_get_curve_id LK_SSL_get_curve_id +SSL_get_curve_name LK_SSL_get_curve_name +SSL_get_default_timeout LK_SSL_get_default_timeout +SSL_get_early_data_reason LK_SSL_get_early_data_reason +SSL_get_error LK_SSL_get_error +SSL_get_ex_data LK_SSL_get_ex_data +SSL_get_ex_data_X509_STORE_CTX_idx LK_SSL_get_ex_data_X509_STORE_CTX_idx +SSL_get_ex_new_index LK_SSL_get_ex_new_index +SSL_get_extms_support LK_SSL_get_extms_support +SSL_get_fd LK_SSL_get_fd +SSL_get_finished LK_SSL_get_finished +SSL_get_ivs LK_SSL_get_ivs +SSL_get_key_block_len LK_SSL_get_key_block_len +SSL_get_max_cert_list LK_SSL_get_max_cert_list +SSL_get_max_proto_version LK_SSL_get_max_proto_version +SSL_get_min_proto_version LK_SSL_get_min_proto_version +SSL_get_mode LK_SSL_get_mode +SSL_get_options LK_SSL_get_options +SSL_get_peer_cert_chain LK_SSL_get_peer_cert_chain +SSL_get_peer_certificate LK_SSL_get_peer_certificate +SSL_get_peer_finished LK_SSL_get_peer_finished +SSL_get_peer_full_cert_chain LK_SSL_get_peer_full_cert_chain +SSL_get_peer_quic_transport_params LK_SSL_get_peer_quic_transport_params +SSL_get_peer_signature_algorithm LK_SSL_get_peer_signature_algorithm +SSL_get_pending_cipher LK_SSL_get_pending_cipher +SSL_get_privatekey LK_SSL_get_privatekey +SSL_get_psk_identity LK_SSL_get_psk_identity +SSL_get_psk_identity_hint LK_SSL_get_psk_identity_hint +SSL_get_quiet_shutdown LK_SSL_get_quiet_shutdown +SSL_get_rbio LK_SSL_get_rbio +SSL_get_read_ahead LK_SSL_get_read_ahead +SSL_get_read_sequence LK_SSL_get_read_sequence +SSL_get_rfd LK_SSL_get_rfd +SSL_get_secure_renegotiation_support LK_SSL_get_secure_renegotiation_support +SSL_get_selected_srtp_profile LK_SSL_get_selected_srtp_profile +SSL_get_server_random LK_SSL_get_server_random +SSL_get_server_tmp_key LK_SSL_get_server_tmp_key +SSL_get_servername LK_SSL_get_servername +SSL_get_servername_type LK_SSL_get_servername_type +SSL_get_session LK_SSL_get_session +SSL_get_shared_ciphers LK_SSL_get_shared_ciphers +SSL_get_shared_sigalgs LK_SSL_get_shared_sigalgs +SSL_get_shutdown LK_SSL_get_shutdown +SSL_get_signature_algorithm_digest LK_SSL_get_signature_algorithm_digest +SSL_get_signature_algorithm_key_type LK_SSL_get_signature_algorithm_key_type +SSL_get_signature_algorithm_name LK_SSL_get_signature_algorithm_name +SSL_get_srtp_profiles LK_SSL_get_srtp_profiles +SSL_get_ticket_age_skew LK_SSL_get_ticket_age_skew +SSL_get_tls_channel_id LK_SSL_get_tls_channel_id +SSL_get_tls_unique LK_SSL_get_tls_unique +SSL_get_tlsext_status_ocsp_resp LK_SSL_get_tlsext_status_ocsp_resp +SSL_get_tlsext_status_type LK_SSL_get_tlsext_status_type +SSL_get_traffic_secrets LK_SSL_get_traffic_secrets +SSL_get_verify_depth LK_SSL_get_verify_depth +SSL_get_verify_mode LK_SSL_get_verify_mode +SSL_get_verify_result LK_SSL_get_verify_result +SSL_get_version LK_SSL_get_version +SSL_get_wbio LK_SSL_get_wbio +SSL_get_wfd LK_SSL_get_wfd +SSL_get_write_sequence LK_SSL_get_write_sequence +SSL_has_application_settings LK_SSL_has_application_settings +SSL_has_pending LK_SSL_has_pending +SSL_in_early_data LK_SSL_in_early_data +SSL_in_false_start LK_SSL_in_false_start +SSL_in_init LK_SSL_in_init +SSL_is_dtls LK_SSL_is_dtls +SSL_is_init_finished LK_SSL_is_init_finished +SSL_is_server LK_SSL_is_server +SSL_is_signature_algorithm_rsa_pss LK_SSL_is_signature_algorithm_rsa_pss +SSL_key_update LK_SSL_key_update +SSL_library_init LK_SSL_library_init +SSL_load_client_CA_file LK_SSL_load_client_CA_file +SSL_load_error_strings LK_SSL_load_error_strings +SSL_magic_pending_session_ptr LK_SSL_magic_pending_session_ptr +SSL_marshal_ech_config LK_SSL_marshal_ech_config +SSL_max_seal_overhead LK_SSL_max_seal_overhead +SSL_need_tmp_RSA LK_SSL_need_tmp_RSA +SSL_new LK_SSL_new +SSL_num_renegotiations LK_SSL_num_renegotiations +SSL_peek LK_SSL_peek +SSL_pending LK_SSL_pending +SSL_process_quic_post_handshake LK_SSL_process_quic_post_handshake +SSL_process_tls13_new_session_ticket LK_SSL_process_tls13_new_session_ticket +SSL_provide_quic_data LK_SSL_provide_quic_data +SSL_quic_max_handshake_flight_len LK_SSL_quic_max_handshake_flight_len +SSL_quic_read_level LK_SSL_quic_read_level +SSL_quic_write_level LK_SSL_quic_write_level +SSL_read LK_SSL_read +SSL_renegotiate LK_SSL_renegotiate +SSL_renegotiate_pending LK_SSL_renegotiate_pending +SSL_request_handshake_hints LK_SSL_request_handshake_hints +SSL_reset_early_data_reject LK_SSL_reset_early_data_reject +SSL_select_next_proto LK_SSL_select_next_proto +SSL_send_fatal_alert LK_SSL_send_fatal_alert +SSL_serialize_capabilities LK_SSL_serialize_capabilities +SSL_serialize_handback LK_SSL_serialize_handback +SSL_serialize_handoff LK_SSL_serialize_handoff +SSL_serialize_handshake_hints LK_SSL_serialize_handshake_hints +SSL_session_reused LK_SSL_session_reused +SSL_set0_client_CAs LK_SSL_set0_client_CAs +SSL_set0_rbio LK_SSL_set0_rbio +SSL_set0_verify_cert_store LK_SSL_set0_verify_cert_store +SSL_set0_wbio LK_SSL_set0_wbio +SSL_set1_curves LK_SSL_set1_curves +SSL_set1_curves_list LK_SSL_set1_curves_list +SSL_set1_delegated_credential LK_SSL_set1_delegated_credential +SSL_set1_ech_config_list LK_SSL_set1_ech_config_list +SSL_set1_groups LK_SSL_set1_groups +SSL_set1_groups_list LK_SSL_set1_groups_list +SSL_set1_host LK_SSL_set1_host +SSL_set1_param LK_SSL_set1_param +SSL_set1_sigalgs LK_SSL_set1_sigalgs +SSL_set1_sigalgs_list LK_SSL_set1_sigalgs_list +SSL_set1_tls_channel_id LK_SSL_set1_tls_channel_id +SSL_set1_verify_cert_store LK_SSL_set1_verify_cert_store +SSL_set_SSL_CTX LK_SSL_set_SSL_CTX +SSL_set_accept_state LK_SSL_set_accept_state +SSL_set_aes_hw_override_for_testing LK_SSL_set_aes_hw_override_for_testing +SSL_set_alpn_protos LK_SSL_set_alpn_protos +SSL_set_bio LK_SSL_set_bio +SSL_set_cert_cb LK_SSL_set_cert_cb +SSL_set_chain_and_key LK_SSL_set_chain_and_key +SSL_set_cipher_list LK_SSL_set_cipher_list +SSL_set_client_CA_list LK_SSL_set_client_CA_list +SSL_set_compliance_policy LK_SSL_set_compliance_policy +SSL_set_connect_state LK_SSL_set_connect_state +SSL_set_custom_verify LK_SSL_set_custom_verify +SSL_set_early_data_enabled LK_SSL_set_early_data_enabled +SSL_set_enable_ech_grease LK_SSL_set_enable_ech_grease +SSL_set_enforce_rsa_key_usage LK_SSL_set_enforce_rsa_key_usage +SSL_set_ex_data LK_SSL_set_ex_data +SSL_set_fd LK_SSL_set_fd +SSL_set_handoff_mode LK_SSL_set_handoff_mode +SSL_set_handshake_hints LK_SSL_set_handshake_hints +SSL_set_hostflags LK_SSL_set_hostflags +SSL_set_info_callback LK_SSL_set_info_callback +SSL_set_jdk11_workaround LK_SSL_set_jdk11_workaround +SSL_set_max_cert_list LK_SSL_set_max_cert_list +SSL_set_max_proto_version LK_SSL_set_max_proto_version +SSL_set_max_send_fragment LK_SSL_set_max_send_fragment +SSL_set_min_proto_version LK_SSL_set_min_proto_version +SSL_set_mode LK_SSL_set_mode +SSL_set_msg_callback LK_SSL_set_msg_callback +SSL_set_msg_callback_arg LK_SSL_set_msg_callback_arg +SSL_set_mtu LK_SSL_set_mtu +SSL_set_ocsp_response LK_SSL_set_ocsp_response +SSL_set_options LK_SSL_set_options +SSL_set_permute_extensions LK_SSL_set_permute_extensions +SSL_set_private_key_method LK_SSL_set_private_key_method +SSL_set_psk_client_callback LK_SSL_set_psk_client_callback +SSL_set_psk_server_callback LK_SSL_set_psk_server_callback +SSL_set_purpose LK_SSL_set_purpose +SSL_set_quic_early_data_context LK_SSL_set_quic_early_data_context +SSL_set_quic_method LK_SSL_set_quic_method +SSL_set_quic_transport_params LK_SSL_set_quic_transport_params +SSL_set_quic_use_legacy_codepoint LK_SSL_set_quic_use_legacy_codepoint +SSL_set_quiet_shutdown LK_SSL_set_quiet_shutdown +SSL_set_read_ahead LK_SSL_set_read_ahead +SSL_set_renegotiate_mode LK_SSL_set_renegotiate_mode +SSL_set_retain_only_sha256_of_client_certs LK_SSL_set_retain_only_sha256_of_client_certs +SSL_set_rfd LK_SSL_set_rfd +SSL_set_session LK_SSL_set_session +SSL_set_session_id_context LK_SSL_set_session_id_context +SSL_set_shed_handshake_config LK_SSL_set_shed_handshake_config +SSL_set_shutdown LK_SSL_set_shutdown +SSL_set_signed_cert_timestamp_list LK_SSL_set_signed_cert_timestamp_list +SSL_set_signing_algorithm_prefs LK_SSL_set_signing_algorithm_prefs +SSL_set_srtp_profiles LK_SSL_set_srtp_profiles +SSL_set_state LK_SSL_set_state +SSL_set_strict_cipher_list LK_SSL_set_strict_cipher_list +SSL_set_tls_channel_id_enabled LK_SSL_set_tls_channel_id_enabled +SSL_set_tlsext_host_name LK_SSL_set_tlsext_host_name +SSL_set_tlsext_status_ocsp_resp LK_SSL_set_tlsext_status_ocsp_resp +SSL_set_tlsext_status_type LK_SSL_set_tlsext_status_type +SSL_set_tlsext_use_srtp LK_SSL_set_tlsext_use_srtp +SSL_set_tmp_dh LK_SSL_set_tmp_dh +SSL_set_tmp_dh_callback LK_SSL_set_tmp_dh_callback +SSL_set_tmp_ecdh LK_SSL_set_tmp_ecdh +SSL_set_tmp_rsa LK_SSL_set_tmp_rsa +SSL_set_tmp_rsa_callback LK_SSL_set_tmp_rsa_callback +SSL_set_trust LK_SSL_set_trust +SSL_set_verify LK_SSL_set_verify +SSL_set_verify_algorithm_prefs LK_SSL_set_verify_algorithm_prefs +SSL_set_verify_depth LK_SSL_set_verify_depth +SSL_set_wfd LK_SSL_set_wfd +SSL_shutdown LK_SSL_shutdown +SSL_state LK_SSL_state +SSL_state_string LK_SSL_state_string +SSL_state_string_long LK_SSL_state_string_long +SSL_total_renegotiations LK_SSL_total_renegotiations +SSL_use_PrivateKey LK_SSL_use_PrivateKey +SSL_use_PrivateKey_ASN1 LK_SSL_use_PrivateKey_ASN1 +SSL_use_PrivateKey_file LK_SSL_use_PrivateKey_file +SSL_use_RSAPrivateKey LK_SSL_use_RSAPrivateKey +SSL_use_RSAPrivateKey_ASN1 LK_SSL_use_RSAPrivateKey_ASN1 +SSL_use_RSAPrivateKey_file LK_SSL_use_RSAPrivateKey_file +SSL_use_certificate LK_SSL_use_certificate +SSL_use_certificate_ASN1 LK_SSL_use_certificate_ASN1 +SSL_use_certificate_file LK_SSL_use_certificate_file +SSL_use_psk_identity_hint LK_SSL_use_psk_identity_hint +SSL_used_hello_retry_request LK_SSL_used_hello_retry_request +SSL_version LK_SSL_version +SSL_want LK_SSL_want +SSL_was_key_usage_invalid LK_SSL_was_key_usage_invalid +SSL_write LK_SSL_write SSLeay LK_SSLeay SSLeay_version LK_SSLeay_version +SSLv23_client_method LK_SSLv23_client_method +SSLv23_method LK_SSLv23_method +SSLv23_server_method LK_SSLv23_server_method +STACK_OF LK_STACK_OF +TLS_client_method LK_TLS_client_method +TLS_method LK_TLS_method +TLS_server_method LK_TLS_server_method +TLS_with_buffers_method LK_TLS_with_buffers_method +TLSv1_1_client_method LK_TLSv1_1_client_method +TLSv1_1_method LK_TLSv1_1_method +TLSv1_1_server_method LK_TLSv1_1_server_method +TLSv1_2_client_method LK_TLSv1_2_client_method +TLSv1_2_method LK_TLSv1_2_method +TLSv1_2_server_method LK_TLSv1_2_server_method +TLSv1_client_method LK_TLSv1_client_method +TLSv1_method LK_TLSv1_method +TLSv1_server_method LK_TLSv1_server_method TRUST_TOKEN_CLIENT_add_key LK_TRUST_TOKEN_CLIENT_add_key TRUST_TOKEN_CLIENT_begin_issuance LK_TRUST_TOKEN_CLIENT_begin_issuance TRUST_TOKEN_CLIENT_begin_issuance_over_message LK_TRUST_TOKEN_CLIENT_begin_issuance_over_message TRUST_TOKEN_CLIENT_begin_redemption LK_TRUST_TOKEN_CLIENT_begin_redemption -TRUST_TOKEN_CLIENT_finish_issuance LK_TRUST_TOKEN_CLIENT_finish_issuance TRUST_TOKEN_CLIENT_finish_redemption LK_TRUST_TOKEN_CLIENT_finish_redemption TRUST_TOKEN_CLIENT_free LK_TRUST_TOKEN_CLIENT_free TRUST_TOKEN_CLIENT_new LK_TRUST_TOKEN_CLIENT_new @@ -2432,9 +2037,9 @@ TRUST_TOKEN_generate_key LK_TRUST_TOKEN_generate_key TRUST_TOKEN_new LK_TRUST_TOKEN_new TRUST_TOKEN_pst_v1_pmb LK_TRUST_TOKEN_pst_v1_pmb TRUST_TOKEN_pst_v1_voprf LK_TRUST_TOKEN_pst_v1_voprf -USERNOTICE_free LK_USERNOTICE_free -USERNOTICE_it LK_USERNOTICE_it -USERNOTICE_new LK_USERNOTICE_new +TYPE_get_ex_data LK_TYPE_get_ex_data +TYPE_get_ex_new_index LK_TYPE_get_ex_new_index +TYPE_set_ex_data LK_TYPE_set_ex_data X25519 LK_X25519 X25519_keypair LK_X25519_keypair X25519_public_from_private LK_X25519_public_from_private @@ -2454,27 +2059,17 @@ X509V3_EXT_nconf LK_X509V3_EXT_nconf X509V3_EXT_nconf_nid LK_X509V3_EXT_nconf_nid X509V3_EXT_print LK_X509V3_EXT_print X509V3_EXT_print_fp LK_X509V3_EXT_print_fp -X509V3_NAME_from_section LK_X509V3_NAME_from_section +X509V3_EXT_val_prn LK_X509V3_EXT_val_prn X509V3_add1_i2d LK_X509V3_add1_i2d X509V3_add_standard_extensions LK_X509V3_add_standard_extensions -X509V3_add_value LK_X509V3_add_value -X509V3_add_value_bool LK_X509V3_add_value_bool -X509V3_add_value_int LK_X509V3_add_value_int -X509V3_bool_from_string LK_X509V3_bool_from_string X509V3_conf_free LK_X509V3_conf_free X509V3_extensions_print LK_X509V3_extensions_print -X509V3_get_d2i LK_X509V3_get_d2i -X509V3_get_section LK_X509V3_get_section -X509V3_get_value_bool LK_X509V3_get_value_bool -X509V3_get_value_int LK_X509V3_get_value_int -X509V3_parse_list LK_X509V3_parse_list X509V3_set_ctx LK_X509V3_set_ctx X509V3_set_nconf LK_X509V3_set_nconf X509_ALGOR_cmp LK_X509_ALGOR_cmp X509_ALGOR_dup LK_X509_ALGOR_dup X509_ALGOR_free LK_X509_ALGOR_free X509_ALGOR_get0 LK_X509_ALGOR_get0 -X509_ALGOR_it LK_X509_ALGOR_it X509_ALGOR_new LK_X509_ALGOR_new X509_ALGOR_set0 LK_X509_ALGOR_set0 X509_ALGOR_set_md LK_X509_ALGOR_set_md @@ -2484,29 +2079,17 @@ X509_ATTRIBUTE_create_by_NID LK_X509_ATTRIBUTE_create_by_NID X509_ATTRIBUTE_create_by_OBJ LK_X509_ATTRIBUTE_create_by_OBJ X509_ATTRIBUTE_create_by_txt LK_X509_ATTRIBUTE_create_by_txt X509_ATTRIBUTE_dup LK_X509_ATTRIBUTE_dup -X509_ATTRIBUTE_free LK_X509_ATTRIBUTE_free X509_ATTRIBUTE_get0_data LK_X509_ATTRIBUTE_get0_data X509_ATTRIBUTE_get0_object LK_X509_ATTRIBUTE_get0_object X509_ATTRIBUTE_get0_type LK_X509_ATTRIBUTE_get0_type -X509_ATTRIBUTE_it LK_X509_ATTRIBUTE_it -X509_ATTRIBUTE_new LK_X509_ATTRIBUTE_new X509_ATTRIBUTE_set1_data LK_X509_ATTRIBUTE_set1_data X509_ATTRIBUTE_set1_object LK_X509_ATTRIBUTE_set1_object -X509_CERT_AUX_free LK_X509_CERT_AUX_free -X509_CERT_AUX_it LK_X509_CERT_AUX_it -X509_CERT_AUX_new LK_X509_CERT_AUX_new -X509_CERT_AUX_print LK_X509_CERT_AUX_print -X509_CINF_free LK_X509_CINF_free -X509_CINF_it LK_X509_CINF_it -X509_CINF_new LK_X509_CINF_new -X509_CRL_INFO_free LK_X509_CRL_INFO_free -X509_CRL_INFO_it LK_X509_CRL_INFO_it -X509_CRL_INFO_new LK_X509_CRL_INFO_new X509_CRL_add0_revoked LK_X509_CRL_add0_revoked X509_CRL_add1_ext_i2d LK_X509_CRL_add1_ext_i2d X509_CRL_add_ext LK_X509_CRL_add_ext X509_CRL_cmp LK_X509_CRL_cmp X509_CRL_delete_ext LK_X509_CRL_delete_ext +X509_CRL_diff LK_X509_CRL_diff X509_CRL_digest LK_X509_CRL_digest X509_CRL_dup LK_X509_CRL_dup X509_CRL_free LK_X509_CRL_free @@ -2528,7 +2111,6 @@ X509_CRL_get_lastUpdate LK_X509_CRL_get_lastUpdate X509_CRL_get_nextUpdate LK_X509_CRL_get_nextUpdate X509_CRL_get_signature_nid LK_X509_CRL_get_signature_nid X509_CRL_get_version LK_X509_CRL_get_version -X509_CRL_it LK_X509_CRL_it X509_CRL_match LK_X509_CRL_match X509_CRL_new LK_X509_CRL_new X509_CRL_print LK_X509_CRL_print @@ -2544,7 +2126,6 @@ X509_CRL_sign_ctx LK_X509_CRL_sign_ctx X509_CRL_sort LK_X509_CRL_sort X509_CRL_up_ref LK_X509_CRL_up_ref X509_CRL_verify LK_X509_CRL_verify -X509_EXTENSIONS_it LK_X509_EXTENSIONS_it X509_EXTENSION_create_by_NID LK_X509_EXTENSION_create_by_NID X509_EXTENSION_create_by_OBJ LK_X509_EXTENSION_create_by_OBJ X509_EXTENSION_dup LK_X509_EXTENSION_dup @@ -2552,19 +2133,20 @@ X509_EXTENSION_free LK_X509_EXTENSION_free X509_EXTENSION_get_critical LK_X509_EXTENSION_get_critical X509_EXTENSION_get_data LK_X509_EXTENSION_get_data X509_EXTENSION_get_object LK_X509_EXTENSION_get_object -X509_EXTENSION_it LK_X509_EXTENSION_it X509_EXTENSION_new LK_X509_EXTENSION_new X509_EXTENSION_set_critical LK_X509_EXTENSION_set_critical X509_EXTENSION_set_data LK_X509_EXTENSION_set_data X509_EXTENSION_set_object LK_X509_EXTENSION_set_object X509_INFO_free LK_X509_INFO_free -X509_LOOKUP_add_dir LK_X509_LOOKUP_add_dir +X509_INFO_new LK_X509_INFO_new +X509_LOOKUP_by_subject LK_X509_LOOKUP_by_subject X509_LOOKUP_ctrl LK_X509_LOOKUP_ctrl X509_LOOKUP_file LK_X509_LOOKUP_file X509_LOOKUP_free LK_X509_LOOKUP_free X509_LOOKUP_hash_dir LK_X509_LOOKUP_hash_dir -X509_LOOKUP_load_file LK_X509_LOOKUP_load_file -X509_NAME_ENTRIES_it LK_X509_NAME_ENTRIES_it +X509_LOOKUP_init LK_X509_LOOKUP_init +X509_LOOKUP_new LK_X509_LOOKUP_new +X509_LOOKUP_shutdown LK_X509_LOOKUP_shutdown X509_NAME_ENTRY_create_by_NID LK_X509_NAME_ENTRY_create_by_NID X509_NAME_ENTRY_create_by_OBJ LK_X509_NAME_ENTRY_create_by_OBJ X509_NAME_ENTRY_create_by_txt LK_X509_NAME_ENTRY_create_by_txt @@ -2572,12 +2154,10 @@ X509_NAME_ENTRY_dup LK_X509_NAME_ENTRY_dup X509_NAME_ENTRY_free LK_X509_NAME_ENTRY_free X509_NAME_ENTRY_get_data LK_X509_NAME_ENTRY_get_data X509_NAME_ENTRY_get_object LK_X509_NAME_ENTRY_get_object -X509_NAME_ENTRY_it LK_X509_NAME_ENTRY_it X509_NAME_ENTRY_new LK_X509_NAME_ENTRY_new X509_NAME_ENTRY_set LK_X509_NAME_ENTRY_set X509_NAME_ENTRY_set_data LK_X509_NAME_ENTRY_set_data X509_NAME_ENTRY_set_object LK_X509_NAME_ENTRY_set_object -X509_NAME_INTERNAL_it LK_X509_NAME_INTERNAL_it X509_NAME_add_entry LK_X509_NAME_add_entry X509_NAME_add_entry_by_NID LK_X509_NAME_add_entry_by_NID X509_NAME_add_entry_by_OBJ LK_X509_NAME_add_entry_by_OBJ @@ -2596,34 +2176,37 @@ X509_NAME_get_text_by_NID LK_X509_NAME_get_text_by_NID X509_NAME_get_text_by_OBJ LK_X509_NAME_get_text_by_OBJ X509_NAME_hash LK_X509_NAME_hash X509_NAME_hash_old LK_X509_NAME_hash_old -X509_NAME_it LK_X509_NAME_it X509_NAME_new LK_X509_NAME_new X509_NAME_oneline LK_X509_NAME_oneline X509_NAME_print LK_X509_NAME_print X509_NAME_print_ex LK_X509_NAME_print_ex X509_NAME_print_ex_fp LK_X509_NAME_print_ex_fp X509_NAME_set LK_X509_NAME_set -X509_OBJECT_free LK_X509_OBJECT_free X509_OBJECT_free_contents LK_X509_OBJECT_free_contents X509_OBJECT_get0_X509 LK_X509_OBJECT_get0_X509 X509_OBJECT_get_type LK_X509_OBJECT_get_type -X509_OBJECT_new LK_X509_OBJECT_new -X509_PUBKEY_free LK_X509_PUBKEY_free +X509_OBJECT_idx_by_subject LK_X509_OBJECT_idx_by_subject +X509_OBJECT_retrieve_by_subject LK_X509_OBJECT_retrieve_by_subject +X509_OBJECT_retrieve_match LK_X509_OBJECT_retrieve_match +X509_OBJECT_up_ref_count LK_X509_OBJECT_up_ref_count +X509_PKEY_free LK_X509_PKEY_free +X509_PKEY_new LK_X509_PKEY_new X509_PUBKEY_get LK_X509_PUBKEY_get -X509_PUBKEY_get0 LK_X509_PUBKEY_get0 X509_PUBKEY_get0_param LK_X509_PUBKEY_get0_param X509_PUBKEY_get0_public_key LK_X509_PUBKEY_get0_public_key -X509_PUBKEY_it LK_X509_PUBKEY_it -X509_PUBKEY_new LK_X509_PUBKEY_new X509_PUBKEY_set LK_X509_PUBKEY_set X509_PUBKEY_set0_param LK_X509_PUBKEY_set0_param +X509_PURPOSE_add LK_X509_PURPOSE_add +X509_PURPOSE_cleanup LK_X509_PURPOSE_cleanup X509_PURPOSE_get0 LK_X509_PURPOSE_get0 +X509_PURPOSE_get0_name LK_X509_PURPOSE_get0_name +X509_PURPOSE_get0_sname LK_X509_PURPOSE_get0_sname +X509_PURPOSE_get_by_id LK_X509_PURPOSE_get_by_id X509_PURPOSE_get_by_sname LK_X509_PURPOSE_get_by_sname +X509_PURPOSE_get_count LK_X509_PURPOSE_get_count X509_PURPOSE_get_id LK_X509_PURPOSE_get_id X509_PURPOSE_get_trust LK_X509_PURPOSE_get_trust -X509_REQ_INFO_free LK_X509_REQ_INFO_free -X509_REQ_INFO_it LK_X509_REQ_INFO_it -X509_REQ_INFO_new LK_X509_REQ_INFO_new +X509_PURPOSE_set LK_X509_PURPOSE_set X509_REQ_add1_attr LK_X509_REQ_add1_attr X509_REQ_add1_attr_by_NID LK_X509_REQ_add1_attr_by_NID X509_REQ_add1_attr_by_OBJ LK_X509_REQ_add1_attr_by_OBJ @@ -2636,7 +2219,6 @@ X509_REQ_digest LK_X509_REQ_digest X509_REQ_dup LK_X509_REQ_dup X509_REQ_extension_nid LK_X509_REQ_extension_nid X509_REQ_free LK_X509_REQ_free -X509_REQ_get0_pubkey LK_X509_REQ_get0_pubkey X509_REQ_get0_signature LK_X509_REQ_get0_signature X509_REQ_get1_email LK_X509_REQ_get1_email X509_REQ_get_attr LK_X509_REQ_get_attr @@ -2648,7 +2230,6 @@ X509_REQ_get_pubkey LK_X509_REQ_get_pubkey X509_REQ_get_signature_nid LK_X509_REQ_get_signature_nid X509_REQ_get_subject_name LK_X509_REQ_get_subject_name X509_REQ_get_version LK_X509_REQ_get_version -X509_REQ_it LK_X509_REQ_it X509_REQ_new LK_X509_REQ_new X509_REQ_print LK_X509_REQ_print X509_REQ_print_ex LK_X509_REQ_print_ex @@ -2665,7 +2246,6 @@ X509_REVOKED_add1_ext_i2d LK_X509_REVOKED_add1_ext_i2d X509_REVOKED_add_ext LK_X509_REVOKED_add_ext X509_REVOKED_delete_ext LK_X509_REVOKED_delete_ext X509_REVOKED_dup LK_X509_REVOKED_dup -X509_REVOKED_free LK_X509_REVOKED_free X509_REVOKED_get0_extensions LK_X509_REVOKED_get0_extensions X509_REVOKED_get0_revocationDate LK_X509_REVOKED_get0_revocationDate X509_REVOKED_get0_serialNumber LK_X509_REVOKED_get0_serialNumber @@ -2675,29 +2255,22 @@ X509_REVOKED_get_ext_by_OBJ LK_X509_REVOKED_get_ext_by_OBJ X509_REVOKED_get_ext_by_critical LK_X509_REVOKED_get_ext_by_critical X509_REVOKED_get_ext_count LK_X509_REVOKED_get_ext_count X509_REVOKED_get_ext_d2i LK_X509_REVOKED_get_ext_d2i -X509_REVOKED_it LK_X509_REVOKED_it -X509_REVOKED_new LK_X509_REVOKED_new X509_REVOKED_set_revocationDate LK_X509_REVOKED_set_revocationDate X509_REVOKED_set_serialNumber LK_X509_REVOKED_set_serialNumber -X509_SIG_free LK_X509_SIG_free X509_SIG_get0 LK_X509_SIG_get0 X509_SIG_getm LK_X509_SIG_getm -X509_SIG_it LK_X509_SIG_it -X509_SIG_new LK_X509_SIG_new X509_STORE_CTX_cleanup LK_X509_STORE_CTX_cleanup X509_STORE_CTX_free LK_X509_STORE_CTX_free X509_STORE_CTX_get0_cert LK_X509_STORE_CTX_get0_cert X509_STORE_CTX_get0_chain LK_X509_STORE_CTX_get0_chain X509_STORE_CTX_get0_current_crl LK_X509_STORE_CTX_get0_current_crl +X509_STORE_CTX_get0_current_issuer LK_X509_STORE_CTX_get0_current_issuer X509_STORE_CTX_get0_param LK_X509_STORE_CTX_get0_param X509_STORE_CTX_get0_parent_ctx LK_X509_STORE_CTX_get0_parent_ctx X509_STORE_CTX_get0_store LK_X509_STORE_CTX_get0_store X509_STORE_CTX_get0_untrusted LK_X509_STORE_CTX_get0_untrusted -X509_STORE_CTX_get1_certs LK_X509_STORE_CTX_get1_certs X509_STORE_CTX_get1_chain LK_X509_STORE_CTX_get1_chain -X509_STORE_CTX_get1_crls LK_X509_STORE_CTX_get1_crls X509_STORE_CTX_get1_issuer LK_X509_STORE_CTX_get1_issuer -X509_STORE_CTX_get_by_subject LK_X509_STORE_CTX_get_by_subject X509_STORE_CTX_get_chain LK_X509_STORE_CTX_get_chain X509_STORE_CTX_get_current_cert LK_X509_STORE_CTX_get_current_cert X509_STORE_CTX_get_error LK_X509_STORE_CTX_get_error @@ -2706,9 +2279,11 @@ X509_STORE_CTX_get_ex_data LK_X509_STORE_CTX_get_ex_data X509_STORE_CTX_get_ex_new_index LK_X509_STORE_CTX_get_ex_new_index X509_STORE_CTX_init LK_X509_STORE_CTX_init X509_STORE_CTX_new LK_X509_STORE_CTX_new +X509_STORE_CTX_purpose_inherit LK_X509_STORE_CTX_purpose_inherit X509_STORE_CTX_set0_crls LK_X509_STORE_CTX_set0_crls X509_STORE_CTX_set0_param LK_X509_STORE_CTX_set0_param X509_STORE_CTX_set0_trusted_stack LK_X509_STORE_CTX_set0_trusted_stack +X509_STORE_CTX_set_cert LK_X509_STORE_CTX_set_cert X509_STORE_CTX_set_chain LK_X509_STORE_CTX_set_chain X509_STORE_CTX_set_default LK_X509_STORE_CTX_set_default X509_STORE_CTX_set_depth LK_X509_STORE_CTX_set_depth @@ -2719,34 +2294,65 @@ X509_STORE_CTX_set_purpose LK_X509_STORE_CTX_set_purpose X509_STORE_CTX_set_time LK_X509_STORE_CTX_set_time X509_STORE_CTX_set_time_posix LK_X509_STORE_CTX_set_time_posix X509_STORE_CTX_set_trust LK_X509_STORE_CTX_set_trust +X509_STORE_CTX_set_verify LK_X509_STORE_CTX_set_verify X509_STORE_CTX_set_verify_cb LK_X509_STORE_CTX_set_verify_cb X509_STORE_CTX_trusted_stack LK_X509_STORE_CTX_trusted_stack +X509_STORE_CTX_zero LK_X509_STORE_CTX_zero X509_STORE_add_cert LK_X509_STORE_add_cert X509_STORE_add_crl LK_X509_STORE_add_crl X509_STORE_add_lookup LK_X509_STORE_add_lookup X509_STORE_free LK_X509_STORE_free X509_STORE_get0_objects LK_X509_STORE_get0_objects X509_STORE_get0_param LK_X509_STORE_get0_param -X509_STORE_get1_objects LK_X509_STORE_get1_objects +X509_STORE_get1_certs LK_X509_STORE_get1_certs +X509_STORE_get1_crls LK_X509_STORE_get1_crls +X509_STORE_get_by_subject LK_X509_STORE_get_by_subject +X509_STORE_get_cert_crl LK_X509_STORE_get_cert_crl +X509_STORE_get_check_crl LK_X509_STORE_get_check_crl +X509_STORE_get_check_issued LK_X509_STORE_get_check_issued +X509_STORE_get_check_revocation LK_X509_STORE_get_check_revocation +X509_STORE_get_cleanup LK_X509_STORE_get_cleanup +X509_STORE_get_get_crl LK_X509_STORE_get_get_crl +X509_STORE_get_get_issuer LK_X509_STORE_get_get_issuer +X509_STORE_get_lookup_certs LK_X509_STORE_get_lookup_certs +X509_STORE_get_lookup_crls LK_X509_STORE_get_lookup_crls +X509_STORE_get_verify LK_X509_STORE_get_verify +X509_STORE_get_verify_cb LK_X509_STORE_get_verify_cb X509_STORE_load_locations LK_X509_STORE_load_locations X509_STORE_new LK_X509_STORE_new X509_STORE_set1_param LK_X509_STORE_set1_param +X509_STORE_set_cert_crl LK_X509_STORE_set_cert_crl X509_STORE_set_check_crl LK_X509_STORE_set_check_crl +X509_STORE_set_check_issued LK_X509_STORE_set_check_issued +X509_STORE_set_check_revocation LK_X509_STORE_set_check_revocation +X509_STORE_set_cleanup LK_X509_STORE_set_cleanup X509_STORE_set_default_paths LK_X509_STORE_set_default_paths X509_STORE_set_depth LK_X509_STORE_set_depth X509_STORE_set_flags LK_X509_STORE_set_flags X509_STORE_set_get_crl LK_X509_STORE_set_get_crl +X509_STORE_set_get_issuer LK_X509_STORE_set_get_issuer +X509_STORE_set_lookup_certs LK_X509_STORE_set_lookup_certs +X509_STORE_set_lookup_crls LK_X509_STORE_set_lookup_crls X509_STORE_set_purpose LK_X509_STORE_set_purpose X509_STORE_set_trust LK_X509_STORE_set_trust +X509_STORE_set_verify LK_X509_STORE_set_verify X509_STORE_set_verify_cb LK_X509_STORE_set_verify_cb X509_STORE_up_ref LK_X509_STORE_up_ref -X509_VAL_free LK_X509_VAL_free -X509_VAL_it LK_X509_VAL_it -X509_VAL_new LK_X509_VAL_new +X509_TRUST_add LK_X509_TRUST_add +X509_TRUST_cleanup LK_X509_TRUST_cleanup +X509_TRUST_get0 LK_X509_TRUST_get0 +X509_TRUST_get0_name LK_X509_TRUST_get0_name +X509_TRUST_get_by_id LK_X509_TRUST_get_by_id +X509_TRUST_get_count LK_X509_TRUST_get_count +X509_TRUST_get_flags LK_X509_TRUST_get_flags +X509_TRUST_get_trust LK_X509_TRUST_get_trust +X509_TRUST_set LK_X509_TRUST_set X509_VERIFY_PARAM_add0_policy LK_X509_VERIFY_PARAM_add0_policy X509_VERIFY_PARAM_add1_host LK_X509_VERIFY_PARAM_add1_host X509_VERIFY_PARAM_clear_flags LK_X509_VERIFY_PARAM_clear_flags X509_VERIFY_PARAM_free LK_X509_VERIFY_PARAM_free +X509_VERIFY_PARAM_get0_name LK_X509_VERIFY_PARAM_get0_name +X509_VERIFY_PARAM_get0_peername LK_X509_VERIFY_PARAM_get0_peername X509_VERIFY_PARAM_get_depth LK_X509_VERIFY_PARAM_get_depth X509_VERIFY_PARAM_get_flags LK_X509_VERIFY_PARAM_get_flags X509_VERIFY_PARAM_inherit LK_X509_VERIFY_PARAM_inherit @@ -2757,6 +2363,7 @@ X509_VERIFY_PARAM_set1_email LK_X509_VERIFY_PARAM_set1_email X509_VERIFY_PARAM_set1_host LK_X509_VERIFY_PARAM_set1_host X509_VERIFY_PARAM_set1_ip LK_X509_VERIFY_PARAM_set1_ip X509_VERIFY_PARAM_set1_ip_asc LK_X509_VERIFY_PARAM_set1_ip_asc +X509_VERIFY_PARAM_set1_name LK_X509_VERIFY_PARAM_set1_name X509_VERIFY_PARAM_set1_policies LK_X509_VERIFY_PARAM_set1_policies X509_VERIFY_PARAM_set_depth LK_X509_VERIFY_PARAM_set_depth X509_VERIFY_PARAM_set_flags LK_X509_VERIFY_PARAM_set_flags @@ -2790,8 +2397,6 @@ X509_delete_ext LK_X509_delete_ext X509_digest LK_X509_digest X509_dup LK_X509_dup X509_email_free LK_X509_email_free -X509_find_by_issuer_and_serial LK_X509_find_by_issuer_and_serial -X509_find_by_subject LK_X509_find_by_subject X509_free LK_X509_free X509_get0_authority_issuer LK_X509_get0_authority_issuer X509_get0_authority_key_id LK_X509_get0_authority_key_id @@ -2799,7 +2404,6 @@ X509_get0_authority_serial LK_X509_get0_authority_serial X509_get0_extensions LK_X509_get0_extensions X509_get0_notAfter LK_X509_get0_notAfter X509_get0_notBefore LK_X509_get0_notBefore -X509_get0_pubkey LK_X509_get0_pubkey X509_get0_pubkey_bitstr LK_X509_get0_pubkey_bitstr X509_get0_serialNumber LK_X509_get0_serialNumber X509_get0_signature LK_X509_get0_signature @@ -2838,11 +2442,9 @@ X509_get_version LK_X509_get_version X509_getm_notAfter LK_X509_getm_notAfter X509_getm_notBefore LK_X509_getm_notBefore X509_gmtime_adj LK_X509_gmtime_adj -X509_is_valid_trust_id LK_X509_is_valid_trust_id X509_issuer_name_cmp LK_X509_issuer_name_cmp X509_issuer_name_hash LK_X509_issuer_name_hash X509_issuer_name_hash_old LK_X509_issuer_name_hash_old -X509_it LK_X509_it X509_keyid_get0 LK_X509_keyid_get0 X509_keyid_set1 LK_X509_keyid_set1 X509_load_cert_crl_file LK_X509_load_cert_crl_file @@ -2850,7 +2452,6 @@ X509_load_cert_file LK_X509_load_cert_file X509_load_crl_file LK_X509_load_crl_file X509_new LK_X509_new X509_parse_from_buffer LK_X509_parse_from_buffer -X509_policy_check LK_X509_policy_check X509_print LK_X509_print X509_print_ex LK_X509_print_ex X509_print_ex_fp LK_X509_print_ex_fp @@ -2884,202 +2485,42 @@ X509_up_ref LK_X509_up_ref X509_verify LK_X509_verify X509_verify_cert LK_X509_verify_cert X509_verify_cert_error_string LK_X509_verify_cert_error_string +X509at_add1_attr LK_X509at_add1_attr +X509at_add1_attr_by_NID LK_X509at_add1_attr_by_NID +X509at_add1_attr_by_OBJ LK_X509at_add1_attr_by_OBJ +X509at_add1_attr_by_txt LK_X509at_add1_attr_by_txt +X509at_delete_attr LK_X509at_delete_attr +X509at_get_attr LK_X509at_get_attr X509v3_add_ext LK_X509v3_add_ext X509v3_delete_ext LK_X509v3_delete_ext -X509v3_get_ext LK_X509v3_get_ext -X509v3_get_ext_by_NID LK_X509v3_get_ext_by_NID -X509v3_get_ext_by_OBJ LK_X509v3_get_ext_by_OBJ -X509v3_get_ext_by_critical LK_X509v3_get_ext_by_critical -X509v3_get_ext_count LK_X509v3_get_ext_count -__clang_call_terminate LK___clang_call_terminate +a2i_GENERAL_NAME LK_a2i_GENERAL_NAME a2i_IPADDRESS LK_a2i_IPADDRESS a2i_IPADDRESS_NC LK_a2i_IPADDRESS_NC -abi_test_bad_unwind_temporary LK_abi_test_bad_unwind_temporary -abi_test_bad_unwind_wrong_register LK_abi_test_bad_unwind_wrong_register -abi_test_clobber_r10 LK_abi_test_clobber_r10 -abi_test_clobber_r11 LK_abi_test_clobber_r11 -abi_test_clobber_r12 LK_abi_test_clobber_r12 -abi_test_clobber_r13 LK_abi_test_clobber_r13 -abi_test_clobber_r14 LK_abi_test_clobber_r14 -abi_test_clobber_r15 LK_abi_test_clobber_r15 -abi_test_clobber_r8 LK_abi_test_clobber_r8 -abi_test_clobber_r9 LK_abi_test_clobber_r9 -abi_test_clobber_rax LK_abi_test_clobber_rax -abi_test_clobber_rbp LK_abi_test_clobber_rbp -abi_test_clobber_rbx LK_abi_test_clobber_rbx -abi_test_clobber_rcx LK_abi_test_clobber_rcx -abi_test_clobber_rdi LK_abi_test_clobber_rdi -abi_test_clobber_rdx LK_abi_test_clobber_rdx -abi_test_clobber_rsi LK_abi_test_clobber_rsi -abi_test_clobber_xmm0 LK_abi_test_clobber_xmm0 -abi_test_clobber_xmm1 LK_abi_test_clobber_xmm1 -abi_test_clobber_xmm10 LK_abi_test_clobber_xmm10 -abi_test_clobber_xmm11 LK_abi_test_clobber_xmm11 -abi_test_clobber_xmm12 LK_abi_test_clobber_xmm12 -abi_test_clobber_xmm13 LK_abi_test_clobber_xmm13 -abi_test_clobber_xmm14 LK_abi_test_clobber_xmm14 -abi_test_clobber_xmm15 LK_abi_test_clobber_xmm15 -abi_test_clobber_xmm2 LK_abi_test_clobber_xmm2 -abi_test_clobber_xmm3 LK_abi_test_clobber_xmm3 -abi_test_clobber_xmm4 LK_abi_test_clobber_xmm4 -abi_test_clobber_xmm5 LK_abi_test_clobber_xmm5 -abi_test_clobber_xmm6 LK_abi_test_clobber_xmm6 -abi_test_clobber_xmm7 LK_abi_test_clobber_xmm7 -abi_test_clobber_xmm8 LK_abi_test_clobber_xmm8 -abi_test_clobber_xmm9 LK_abi_test_clobber_xmm9 -abi_test_get_and_clear_direction_flag LK_abi_test_get_and_clear_direction_flag -abi_test_set_direction_flag LK_abi_test_set_direction_flag -abi_test_trampoline LK_abi_test_trampoline -abi_test_unwind_return LK_abi_test_unwind_return -abi_test_unwind_start LK_abi_test_unwind_start -abi_test_unwind_stop LK_abi_test_unwind_stop -aes128gcmsiv_aes_ks LK_aes128gcmsiv_aes_ks -aes128gcmsiv_aes_ks_enc_x1 LK_aes128gcmsiv_aes_ks_enc_x1 -aes128gcmsiv_dec LK_aes128gcmsiv_dec -aes128gcmsiv_ecb_enc_block LK_aes128gcmsiv_ecb_enc_block -aes128gcmsiv_enc_msg_x4 LK_aes128gcmsiv_enc_msg_x4 -aes128gcmsiv_enc_msg_x8 LK_aes128gcmsiv_enc_msg_x8 -aes128gcmsiv_kdf LK_aes128gcmsiv_kdf -aes256gcmsiv_aes_ks LK_aes256gcmsiv_aes_ks -aes256gcmsiv_aes_ks_enc_x1 LK_aes256gcmsiv_aes_ks_enc_x1 -aes256gcmsiv_dec LK_aes256gcmsiv_dec -aes256gcmsiv_ecb_enc_block LK_aes256gcmsiv_ecb_enc_block -aes256gcmsiv_enc_msg_x4 LK_aes256gcmsiv_enc_msg_x4 -aes256gcmsiv_enc_msg_x8 LK_aes256gcmsiv_enc_msg_x8 -aes256gcmsiv_kdf LK_aes256gcmsiv_kdf -aes_ctr_set_key LK_aes_ctr_set_key -aes_hw_cbc_encrypt LK_aes_hw_cbc_encrypt -aes_hw_ctr32_encrypt_blocks LK_aes_hw_ctr32_encrypt_blocks -aes_hw_decrypt LK_aes_hw_decrypt -aes_hw_ecb_encrypt LK_aes_hw_ecb_encrypt -aes_hw_encrypt LK_aes_hw_encrypt -aes_hw_set_decrypt_key LK_aes_hw_set_decrypt_key -aes_hw_set_encrypt_key LK_aes_hw_set_encrypt_key -aes_nohw_cbc_encrypt LK_aes_nohw_cbc_encrypt -aes_nohw_ctr32_encrypt_blocks LK_aes_nohw_ctr32_encrypt_blocks -aes_nohw_decrypt LK_aes_nohw_decrypt -aes_nohw_encrypt LK_aes_nohw_encrypt -aes_nohw_set_decrypt_key LK_aes_nohw_set_decrypt_key -aes_nohw_set_encrypt_key LK_aes_nohw_set_encrypt_key -aesgcmsiv_htable6_init LK_aesgcmsiv_htable6_init -aesgcmsiv_htable_init LK_aesgcmsiv_htable_init -aesgcmsiv_htable_polyval LK_aesgcmsiv_htable_polyval -aesgcmsiv_polyval_horner LK_aesgcmsiv_polyval_horner -aesni_gcm_decrypt LK_aesni_gcm_decrypt -aesni_gcm_encrypt LK_aesni_gcm_encrypt -asn1_bit_string_length LK_asn1_bit_string_length -asn1_do_adb LK_asn1_do_adb -asn1_enc_free LK_asn1_enc_free -asn1_enc_init LK_asn1_enc_init -asn1_enc_restore LK_asn1_enc_restore -asn1_enc_save LK_asn1_enc_save -asn1_encoding_clear LK_asn1_encoding_clear asn1_generalizedtime_to_tm LK_asn1_generalizedtime_to_tm -asn1_get_choice_selector LK_asn1_get_choice_selector -asn1_get_field_ptr LK_asn1_get_field_ptr asn1_get_string_table_for_testing LK_asn1_get_string_table_for_testing -asn1_is_printable LK_asn1_is_printable -asn1_refcount_dec_and_test_zero LK_asn1_refcount_dec_and_test_zero -asn1_refcount_set_one LK_asn1_refcount_set_one -asn1_set_choice_selector LK_asn1_set_choice_selector -asn1_type_cleanup LK_asn1_type_cleanup -asn1_type_set0_string LK_asn1_type_set0_string -asn1_type_value_as_pointer LK_asn1_type_value_as_pointer asn1_utctime_to_tm LK_asn1_utctime_to_tm -beeu_mod_inverse_vartime LK_beeu_mod_inverse_vartime -bio_clear_socket_error LK_bio_clear_socket_error -bio_errno_should_retry LK_bio_errno_should_retry -bio_ip_and_port_to_socket_and_addr LK_bio_ip_and_port_to_socket_and_addr -bio_sock_error LK_bio_sock_error -bio_socket_nbio LK_bio_socket_nbio -bio_socket_should_retry LK_bio_socket_should_retry bn_abs_sub_consttime LK_bn_abs_sub_consttime -bn_add_words LK_bn_add_words -bn_assert_fits_in_bytes LK_bn_assert_fits_in_bytes -bn_big_endian_to_words LK_bn_big_endian_to_words -bn_copy_words LK_bn_copy_words bn_div_consttime LK_bn_div_consttime -bn_expand LK_bn_expand -bn_fits_in_words LK_bn_fits_in_words -bn_from_montgomery_small LK_bn_from_montgomery_small -bn_gather5 LK_bn_gather5 -bn_in_range_words LK_bn_in_range_words -bn_is_bit_set_words LK_bn_is_bit_set_words bn_is_relatively_prime LK_bn_is_relatively_prime -bn_jacobi LK_bn_jacobi bn_lcm_consttime LK_bn_lcm_consttime -bn_less_than_montgomery_R LK_bn_less_than_montgomery_R -bn_less_than_words LK_bn_less_than_words bn_miller_rabin_init LK_bn_miller_rabin_init bn_miller_rabin_iteration LK_bn_miller_rabin_iteration -bn_minimal_width LK_bn_minimal_width -bn_mod_add_consttime LK_bn_mod_add_consttime -bn_mod_add_words LK_bn_mod_add_words -bn_mod_exp_mont_small LK_bn_mod_exp_mont_small -bn_mod_inverse0_prime_mont_small LK_bn_mod_inverse0_prime_mont_small bn_mod_inverse_consttime LK_bn_mod_inverse_consttime -bn_mod_inverse_prime LK_bn_mod_inverse_prime -bn_mod_inverse_secret_prime LK_bn_mod_inverse_secret_prime -bn_mod_lshift1_consttime LK_bn_mod_lshift1_consttime -bn_mod_lshift_consttime LK_bn_mod_lshift_consttime -bn_mod_mul_montgomery_small LK_bn_mod_mul_montgomery_small -bn_mod_sub_consttime LK_bn_mod_sub_consttime -bn_mod_sub_words LK_bn_mod_sub_words bn_mod_u16_consttime LK_bn_mod_u16_consttime -bn_mont_ctx_cleanup LK_bn_mont_ctx_cleanup -bn_mont_ctx_init LK_bn_mont_ctx_init -bn_mont_ctx_set_RR_consttime LK_bn_mont_ctx_set_RR_consttime -bn_mont_n0 LK_bn_mont_n0 -bn_mul4x_mont LK_bn_mul4x_mont -bn_mul_add_words LK_bn_mul_add_words -bn_mul_comba4 LK_bn_mul_comba4 -bn_mul_comba8 LK_bn_mul_comba8 -bn_mul_consttime LK_bn_mul_consttime -bn_mul_mont LK_bn_mul_mont -bn_mul_mont_gather5 LK_bn_mul_mont_gather5 -bn_mul_mont_nohw LK_bn_mul_mont_nohw -bn_mul_small LK_bn_mul_small -bn_mul_words LK_bn_mul_words -bn_mulx4x_mont LK_bn_mulx4x_mont -bn_odd_number_is_obviously_composite LK_bn_odd_number_is_obviously_composite -bn_one_to_montgomery LK_bn_one_to_montgomery -bn_power5 LK_bn_power5 -bn_rand_range_words LK_bn_rand_range_words -bn_rand_secret_range LK_bn_rand_secret_range -bn_reduce_once LK_bn_reduce_once -bn_reduce_once_in_place LK_bn_reduce_once_in_place bn_resize_words LK_bn_resize_words -bn_rshift1_words LK_bn_rshift1_words bn_rshift_secret_shift LK_bn_rshift_secret_shift -bn_rshift_words LK_bn_rshift_words -bn_scatter5 LK_bn_scatter5 -bn_select_words LK_bn_select_words -bn_set_minimal_width LK_bn_set_minimal_width -bn_set_static_words LK_bn_set_static_words -bn_set_words LK_bn_set_words -bn_sqr8x_internal LK_bn_sqr8x_internal -bn_sqr8x_mont LK_bn_sqr8x_mont -bn_sqr_comba4 LK_bn_sqr_comba4 -bn_sqr_comba8 LK_bn_sqr_comba8 -bn_sqr_consttime LK_bn_sqr_consttime -bn_sqr_small LK_bn_sqr_small -bn_sqr_words LK_bn_sqr_words -bn_sqrx8x_internal LK_bn_sqrx8x_internal -bn_sub_words LK_bn_sub_words -bn_to_montgomery_small LK_bn_to_montgomery_small -bn_uadd_consttime LK_bn_uadd_consttime -bn_usub_consttime LK_bn_usub_consttime -bn_wexpand LK_bn_wexpand -bn_words_to_big_endian LK_bn_words_to_big_endian -boringssl_self_test_hmac_sha256 LK_boringssl_self_test_hmac_sha256 -boringssl_self_test_sha256 LK_boringssl_self_test_sha256 -boringssl_self_test_sha512 LK_boringssl_self_test_sha512 c2i_ASN1_BIT_STRING LK_c2i_ASN1_BIT_STRING c2i_ASN1_INTEGER LK_c2i_ASN1_INTEGER c2i_ASN1_OBJECT LK_c2i_ASN1_OBJECT -chacha20_poly1305_open LK_chacha20_poly1305_open -chacha20_poly1305_seal LK_chacha20_poly1305_seal -crypto_gcm_clmul_enabled LK_crypto_gcm_clmul_enabled +cbb_add_latin1 LK_cbb_add_latin1 +cbb_add_ucs2_be LK_cbb_add_ucs2_be +cbb_add_utf32_be LK_cbb_add_utf32_be +cbb_add_utf8 LK_cbb_add_utf8 +cbb_get_utf8_len LK_cbb_get_utf8_len +cbs_get_latin1 LK_cbs_get_latin1 +cbs_get_ucs2_be LK_cbs_get_ucs2_be +cbs_get_utf32_be LK_cbs_get_utf32_be +cbs_get_utf8 LK_cbs_get_utf8 d2i_ASN1_BIT_STRING LK_d2i_ASN1_BIT_STRING d2i_ASN1_BMPSTRING LK_d2i_ASN1_BMPSTRING d2i_ASN1_BOOLEAN LK_d2i_ASN1_BOOLEAN @@ -3102,12 +2543,7 @@ d2i_ASN1_UNIVERSALSTRING LK_d2i_ASN1_UNIVERSALSTRING d2i_ASN1_UTCTIME LK_d2i_ASN1_UTCTIME d2i_ASN1_UTF8STRING LK_d2i_ASN1_UTF8STRING d2i_ASN1_VISIBLESTRING LK_d2i_ASN1_VISIBLESTRING -d2i_AUTHORITY_INFO_ACCESS LK_d2i_AUTHORITY_INFO_ACCESS -d2i_AUTHORITY_KEYID LK_d2i_AUTHORITY_KEYID d2i_AutoPrivateKey LK_d2i_AutoPrivateKey -d2i_BASIC_CONSTRAINTS LK_d2i_BASIC_CONSTRAINTS -d2i_CERTIFICATEPOLICIES LK_d2i_CERTIFICATEPOLICIES -d2i_CRL_DIST_POINTS LK_d2i_CRL_DIST_POINTS d2i_DHparams LK_d2i_DHparams d2i_DHparams_bio LK_d2i_DHparams_bio d2i_DIRECTORYSTRING LK_d2i_DIRECTORYSTRING @@ -3129,12 +2565,6 @@ d2i_ECPrivateKey_fp LK_d2i_ECPrivateKey_fp d2i_EC_PUBKEY LK_d2i_EC_PUBKEY d2i_EC_PUBKEY_bio LK_d2i_EC_PUBKEY_bio d2i_EC_PUBKEY_fp LK_d2i_EC_PUBKEY_fp -d2i_EXTENDED_KEY_USAGE LK_d2i_EXTENDED_KEY_USAGE -d2i_GENERAL_NAME LK_d2i_GENERAL_NAME -d2i_GENERAL_NAMES LK_d2i_GENERAL_NAMES -d2i_ISSUING_DIST_POINT LK_d2i_ISSUING_DIST_POINT -d2i_NETSCAPE_SPKAC LK_d2i_NETSCAPE_SPKAC -d2i_NETSCAPE_SPKI LK_d2i_NETSCAPE_SPKI d2i_PKCS12 LK_d2i_PKCS12 d2i_PKCS12_bio LK_d2i_PKCS12_bio d2i_PKCS12_fp LK_d2i_PKCS12_fp @@ -3142,7 +2572,6 @@ d2i_PKCS7 LK_d2i_PKCS7 d2i_PKCS7_bio LK_d2i_PKCS7_bio d2i_PKCS8PrivateKey_bio LK_d2i_PKCS8PrivateKey_bio d2i_PKCS8PrivateKey_fp LK_d2i_PKCS8PrivateKey_fp -d2i_PKCS8_PRIV_KEY_INFO LK_d2i_PKCS8_PRIV_KEY_INFO d2i_PKCS8_PRIV_KEY_INFO_bio LK_d2i_PKCS8_PRIV_KEY_INFO_bio d2i_PKCS8_PRIV_KEY_INFO_fp LK_d2i_PKCS8_PRIV_KEY_INFO_fp d2i_PKCS8_bio LK_d2i_PKCS8_bio @@ -3160,159 +2589,35 @@ d2i_RSAPrivateKey_fp LK_d2i_RSAPrivateKey_fp d2i_RSAPublicKey LK_d2i_RSAPublicKey d2i_RSAPublicKey_bio LK_d2i_RSAPublicKey_bio d2i_RSAPublicKey_fp LK_d2i_RSAPublicKey_fp -d2i_RSA_PSS_PARAMS LK_d2i_RSA_PSS_PARAMS d2i_RSA_PUBKEY LK_d2i_RSA_PUBKEY d2i_RSA_PUBKEY_bio LK_d2i_RSA_PUBKEY_bio d2i_RSA_PUBKEY_fp LK_d2i_RSA_PUBKEY_fp +d2i_SSL_SESSION LK_d2i_SSL_SESSION +d2i_SSL_SESSION_bio LK_d2i_SSL_SESSION_bio d2i_X509 LK_d2i_X509 d2i_X509_ALGOR LK_d2i_X509_ALGOR -d2i_X509_ATTRIBUTE LK_d2i_X509_ATTRIBUTE d2i_X509_AUX LK_d2i_X509_AUX -d2i_X509_CERT_AUX LK_d2i_X509_CERT_AUX -d2i_X509_CINF LK_d2i_X509_CINF d2i_X509_CRL LK_d2i_X509_CRL -d2i_X509_CRL_INFO LK_d2i_X509_CRL_INFO d2i_X509_CRL_bio LK_d2i_X509_CRL_bio d2i_X509_CRL_fp LK_d2i_X509_CRL_fp d2i_X509_EXTENSION LK_d2i_X509_EXTENSION d2i_X509_EXTENSIONS LK_d2i_X509_EXTENSIONS d2i_X509_NAME LK_d2i_X509_NAME -d2i_X509_PUBKEY LK_d2i_X509_PUBKEY +d2i_X509_NAME_ENTRY LK_d2i_X509_NAME_ENTRY d2i_X509_REQ LK_d2i_X509_REQ -d2i_X509_REQ_INFO LK_d2i_X509_REQ_INFO d2i_X509_REQ_bio LK_d2i_X509_REQ_bio d2i_X509_REQ_fp LK_d2i_X509_REQ_fp -d2i_X509_REVOKED LK_d2i_X509_REVOKED -d2i_X509_SIG LK_d2i_X509_SIG -d2i_X509_VAL LK_d2i_X509_VAL d2i_X509_bio LK_d2i_X509_bio d2i_X509_fp LK_d2i_X509_fp -dh_asn1_meth LK_dh_asn1_meth -dh_check_params_fast LK_dh_check_params_fast -dh_compute_key_padded_no_self_test LK_dh_compute_key_padded_no_self_test -dh_pkey_meth LK_dh_pkey_meth -dsa_asn1_meth LK_dsa_asn1_meth -dsa_check_key LK_dsa_check_key -ec_GFp_mont_add LK_ec_GFp_mont_add -ec_GFp_mont_dbl LK_ec_GFp_mont_dbl -ec_GFp_mont_felem_exp LK_ec_GFp_mont_felem_exp -ec_GFp_mont_felem_from_bytes LK_ec_GFp_mont_felem_from_bytes -ec_GFp_mont_felem_mul LK_ec_GFp_mont_felem_mul -ec_GFp_mont_felem_reduce LK_ec_GFp_mont_felem_reduce -ec_GFp_mont_felem_sqr LK_ec_GFp_mont_felem_sqr -ec_GFp_mont_felem_to_bytes LK_ec_GFp_mont_felem_to_bytes -ec_GFp_mont_init_precomp LK_ec_GFp_mont_init_precomp -ec_GFp_mont_mul LK_ec_GFp_mont_mul -ec_GFp_mont_mul_base LK_ec_GFp_mont_mul_base -ec_GFp_mont_mul_batch LK_ec_GFp_mont_mul_batch -ec_GFp_mont_mul_precomp LK_ec_GFp_mont_mul_precomp -ec_GFp_mont_mul_public_batch LK_ec_GFp_mont_mul_public_batch -ec_GFp_nistp_recode_scalar_bits LK_ec_GFp_nistp_recode_scalar_bits -ec_GFp_simple_cmp_x_coordinate LK_ec_GFp_simple_cmp_x_coordinate -ec_GFp_simple_felem_from_bytes LK_ec_GFp_simple_felem_from_bytes -ec_GFp_simple_felem_to_bytes LK_ec_GFp_simple_felem_to_bytes -ec_GFp_simple_group_get_curve LK_ec_GFp_simple_group_get_curve -ec_GFp_simple_group_set_curve LK_ec_GFp_simple_group_set_curve -ec_GFp_simple_invert LK_ec_GFp_simple_invert -ec_GFp_simple_is_at_infinity LK_ec_GFp_simple_is_at_infinity -ec_GFp_simple_is_on_curve LK_ec_GFp_simple_is_on_curve -ec_GFp_simple_point_copy LK_ec_GFp_simple_point_copy -ec_GFp_simple_point_init LK_ec_GFp_simple_point_init -ec_GFp_simple_point_set_to_infinity LK_ec_GFp_simple_point_set_to_infinity -ec_GFp_simple_points_equal LK_ec_GFp_simple_points_equal -ec_affine_jacobian_equal LK_ec_affine_jacobian_equal -ec_affine_select LK_ec_affine_select -ec_affine_to_jacobian LK_ec_affine_to_jacobian -ec_asn1_meth LK_ec_asn1_meth -ec_bignum_to_felem LK_ec_bignum_to_felem ec_bignum_to_scalar LK_ec_bignum_to_scalar -ec_cmp_x_coordinate LK_ec_cmp_x_coordinate -ec_compute_wNAF LK_ec_compute_wNAF -ec_felem_add LK_ec_felem_add -ec_felem_equal LK_ec_felem_equal -ec_felem_from_bytes LK_ec_felem_from_bytes -ec_felem_neg LK_ec_felem_neg -ec_felem_non_zero_mask LK_ec_felem_non_zero_mask -ec_felem_one LK_ec_felem_one -ec_felem_select LK_ec_felem_select -ec_felem_sub LK_ec_felem_sub -ec_felem_to_bignum LK_ec_felem_to_bignum -ec_felem_to_bytes LK_ec_felem_to_bytes -ec_get_x_coordinate_as_bytes LK_ec_get_x_coordinate_as_bytes -ec_get_x_coordinate_as_scalar LK_ec_get_x_coordinate_as_scalar ec_hash_to_curve_p256_xmd_sha256_sswu LK_ec_hash_to_curve_p256_xmd_sha256_sswu ec_hash_to_curve_p384_xmd_sha384_sswu LK_ec_hash_to_curve_p384_xmd_sha384_sswu ec_hash_to_curve_p384_xmd_sha512_sswu_draft07 LK_ec_hash_to_curve_p384_xmd_sha512_sswu_draft07 ec_hash_to_scalar_p384_xmd_sha384 LK_ec_hash_to_scalar_p384_xmd_sha384 ec_hash_to_scalar_p384_xmd_sha512_draft07 LK_ec_hash_to_scalar_p384_xmd_sha512_draft07 -ec_init_precomp LK_ec_init_precomp -ec_jacobian_to_affine LK_ec_jacobian_to_affine -ec_jacobian_to_affine_batch LK_ec_jacobian_to_affine_batch -ec_pkey_meth LK_ec_pkey_meth -ec_point_byte_len LK_ec_point_byte_len -ec_point_from_uncompressed LK_ec_point_from_uncompressed -ec_point_mul_no_self_test LK_ec_point_mul_no_self_test -ec_point_mul_scalar LK_ec_point_mul_scalar -ec_point_mul_scalar_base LK_ec_point_mul_scalar_base -ec_point_mul_scalar_batch LK_ec_point_mul_scalar_batch -ec_point_mul_scalar_precomp LK_ec_point_mul_scalar_precomp ec_point_mul_scalar_public LK_ec_point_mul_scalar_public -ec_point_mul_scalar_public_batch LK_ec_point_mul_scalar_public_batch -ec_point_select LK_ec_point_select -ec_point_set_affine_coordinates LK_ec_point_set_affine_coordinates -ec_point_to_bytes LK_ec_point_to_bytes -ec_precomp_select LK_ec_precomp_select -ec_random_nonzero_scalar LK_ec_random_nonzero_scalar -ec_scalar_add LK_ec_scalar_add -ec_scalar_equal_vartime LK_ec_scalar_equal_vartime -ec_scalar_from_bytes LK_ec_scalar_from_bytes -ec_scalar_from_montgomery LK_ec_scalar_from_montgomery -ec_scalar_inv0_montgomery LK_ec_scalar_inv0_montgomery -ec_scalar_is_zero LK_ec_scalar_is_zero -ec_scalar_mul_montgomery LK_ec_scalar_mul_montgomery -ec_scalar_neg LK_ec_scalar_neg -ec_scalar_reduce LK_ec_scalar_reduce -ec_scalar_select LK_ec_scalar_select -ec_scalar_sub LK_ec_scalar_sub ec_scalar_to_bytes LK_ec_scalar_to_bytes -ec_scalar_to_montgomery LK_ec_scalar_to_montgomery -ec_scalar_to_montgomery_inv_vartime LK_ec_scalar_to_montgomery_inv_vartime -ec_set_to_safe_point LK_ec_set_to_safe_point -ec_simple_scalar_inv0_montgomery LK_ec_simple_scalar_inv0_montgomery -ec_simple_scalar_to_montgomery_inv_vartime LK_ec_simple_scalar_to_montgomery_inv_vartime -ecdsa_do_verify_no_self_test LK_ecdsa_do_verify_no_self_test -ecdsa_sign_with_nonce_for_known_answer_test LK_ecdsa_sign_with_nonce_for_known_answer_test -ecp_nistz256_avx2_select_w7 LK_ecp_nistz256_avx2_select_w7 -ecp_nistz256_mul_mont LK_ecp_nistz256_mul_mont -ecp_nistz256_neg LK_ecp_nistz256_neg -ecp_nistz256_ord_mul_mont LK_ecp_nistz256_ord_mul_mont -ecp_nistz256_ord_sqr_mont LK_ecp_nistz256_ord_sqr_mont -ecp_nistz256_point_add LK_ecp_nistz256_point_add -ecp_nistz256_point_add_affine LK_ecp_nistz256_point_add_affine -ecp_nistz256_point_double LK_ecp_nistz256_point_double -ecp_nistz256_select_w5 LK_ecp_nistz256_select_w5 -ecp_nistz256_select_w7 LK_ecp_nistz256_select_w7 -ecp_nistz256_sqr_mont LK_ecp_nistz256_sqr_mont -ed25519_asn1_meth LK_ed25519_asn1_meth -ed25519_pkey_meth LK_ed25519_pkey_meth -evp_pkey_set_method LK_evp_pkey_set_method -fiat_curve25519_adx_mul LK_fiat_curve25519_adx_mul -fiat_curve25519_adx_square LK_fiat_curve25519_adx_square -fiat_p256_adx_mul LK_fiat_p256_adx_mul -fiat_p256_adx_sqr LK_fiat_p256_adx_sqr -gcm_ghash_avx LK_gcm_ghash_avx -gcm_ghash_clmul LK_gcm_ghash_clmul -gcm_ghash_nohw LK_gcm_ghash_nohw -gcm_ghash_ssse3 LK_gcm_ghash_ssse3 -gcm_gmult_avx LK_gcm_gmult_avx -gcm_gmult_clmul LK_gcm_gmult_clmul -gcm_gmult_nohw LK_gcm_gmult_nohw -gcm_gmult_ssse3 LK_gcm_gmult_ssse3 -gcm_init_avx LK_gcm_init_avx -gcm_init_clmul LK_gcm_init_clmul -gcm_init_nohw LK_gcm_init_nohw -gcm_init_ssse3 LK_gcm_init_ssse3 -hkdf_pkey_meth LK_hkdf_pkey_meth +i2a_ACCESS_DESCRIPTION LK_i2a_ACCESS_DESCRIPTION i2a_ASN1_ENUMERATED LK_i2a_ASN1_ENUMERATED i2a_ASN1_INTEGER LK_i2a_ASN1_INTEGER i2a_ASN1_OBJECT LK_i2a_ASN1_OBJECT @@ -3341,11 +2646,6 @@ i2d_ASN1_UNIVERSALSTRING LK_i2d_ASN1_UNIVERSALSTRING i2d_ASN1_UTCTIME LK_i2d_ASN1_UTCTIME i2d_ASN1_UTF8STRING LK_i2d_ASN1_UTF8STRING i2d_ASN1_VISIBLESTRING LK_i2d_ASN1_VISIBLESTRING -i2d_AUTHORITY_INFO_ACCESS LK_i2d_AUTHORITY_INFO_ACCESS -i2d_AUTHORITY_KEYID LK_i2d_AUTHORITY_KEYID -i2d_BASIC_CONSTRAINTS LK_i2d_BASIC_CONSTRAINTS -i2d_CERTIFICATEPOLICIES LK_i2d_CERTIFICATEPOLICIES -i2d_CRL_DIST_POINTS LK_i2d_CRL_DIST_POINTS i2d_DHparams LK_i2d_DHparams i2d_DHparams_bio LK_i2d_DHparams_bio i2d_DIRECTORYSTRING LK_i2d_DIRECTORYSTRING @@ -3367,12 +2667,6 @@ i2d_ECPrivateKey_fp LK_i2d_ECPrivateKey_fp i2d_EC_PUBKEY LK_i2d_EC_PUBKEY i2d_EC_PUBKEY_bio LK_i2d_EC_PUBKEY_bio i2d_EC_PUBKEY_fp LK_i2d_EC_PUBKEY_fp -i2d_EXTENDED_KEY_USAGE LK_i2d_EXTENDED_KEY_USAGE -i2d_GENERAL_NAME LK_i2d_GENERAL_NAME -i2d_GENERAL_NAMES LK_i2d_GENERAL_NAMES -i2d_ISSUING_DIST_POINT LK_i2d_ISSUING_DIST_POINT -i2d_NETSCAPE_SPKAC LK_i2d_NETSCAPE_SPKAC -i2d_NETSCAPE_SPKI LK_i2d_NETSCAPE_SPKI i2d_PKCS12 LK_i2d_PKCS12 i2d_PKCS12_bio LK_i2d_PKCS12_bio i2d_PKCS12_fp LK_i2d_PKCS12_fp @@ -3384,7 +2678,6 @@ i2d_PKCS8PrivateKey_bio LK_i2d_PKCS8PrivateKey_bio i2d_PKCS8PrivateKey_fp LK_i2d_PKCS8PrivateKey_fp i2d_PKCS8PrivateKey_nid_bio LK_i2d_PKCS8PrivateKey_nid_bio i2d_PKCS8PrivateKey_nid_fp LK_i2d_PKCS8PrivateKey_nid_fp -i2d_PKCS8_PRIV_KEY_INFO LK_i2d_PKCS8_PRIV_KEY_INFO i2d_PKCS8_PRIV_KEY_INFO_bio LK_i2d_PKCS8_PRIV_KEY_INFO_bio i2d_PKCS8_PRIV_KEY_INFO_fp LK_i2d_PKCS8_PRIV_KEY_INFO_fp i2d_PKCS8_bio LK_i2d_PKCS8_bio @@ -3402,32 +2695,25 @@ i2d_RSAPrivateKey_fp LK_i2d_RSAPrivateKey_fp i2d_RSAPublicKey LK_i2d_RSAPublicKey i2d_RSAPublicKey_bio LK_i2d_RSAPublicKey_bio i2d_RSAPublicKey_fp LK_i2d_RSAPublicKey_fp -i2d_RSA_PSS_PARAMS LK_i2d_RSA_PSS_PARAMS i2d_RSA_PUBKEY LK_i2d_RSA_PUBKEY i2d_RSA_PUBKEY_bio LK_i2d_RSA_PUBKEY_bio i2d_RSA_PUBKEY_fp LK_i2d_RSA_PUBKEY_fp +i2d_SSL_SESSION LK_i2d_SSL_SESSION +i2d_SSL_SESSION_bio LK_i2d_SSL_SESSION_bio i2d_X509 LK_i2d_X509 i2d_X509_ALGOR LK_i2d_X509_ALGOR -i2d_X509_ATTRIBUTE LK_i2d_X509_ATTRIBUTE i2d_X509_AUX LK_i2d_X509_AUX -i2d_X509_CERT_AUX LK_i2d_X509_CERT_AUX -i2d_X509_CINF LK_i2d_X509_CINF i2d_X509_CRL LK_i2d_X509_CRL -i2d_X509_CRL_INFO LK_i2d_X509_CRL_INFO i2d_X509_CRL_bio LK_i2d_X509_CRL_bio i2d_X509_CRL_fp LK_i2d_X509_CRL_fp i2d_X509_CRL_tbs LK_i2d_X509_CRL_tbs i2d_X509_EXTENSION LK_i2d_X509_EXTENSION i2d_X509_EXTENSIONS LK_i2d_X509_EXTENSIONS i2d_X509_NAME LK_i2d_X509_NAME -i2d_X509_PUBKEY LK_i2d_X509_PUBKEY +i2d_X509_NAME_ENTRY LK_i2d_X509_NAME_ENTRY i2d_X509_REQ LK_i2d_X509_REQ -i2d_X509_REQ_INFO LK_i2d_X509_REQ_INFO i2d_X509_REQ_bio LK_i2d_X509_REQ_bio i2d_X509_REQ_fp LK_i2d_X509_REQ_fp -i2d_X509_REVOKED LK_i2d_X509_REVOKED -i2d_X509_SIG LK_i2d_X509_SIG -i2d_X509_VAL LK_i2d_X509_VAL i2d_X509_bio LK_i2d_X509_bio i2d_X509_fp LK_i2d_X509_fp i2d_X509_tbs LK_i2d_X509_tbs @@ -3441,196 +2727,42 @@ i2s_ASN1_OCTET_STRING LK_i2s_ASN1_OCTET_STRING i2t_ASN1_OBJECT LK_i2t_ASN1_OBJECT i2v_GENERAL_NAME LK_i2v_GENERAL_NAME i2v_GENERAL_NAMES LK_i2v_GENERAL_NAMES -k25519Precomp LK_k25519Precomp -kBoringSSLRSASqrtTwo LK_kBoringSSLRSASqrtTwo -kBoringSSLRSASqrtTwoLen LK_kBoringSSLRSASqrtTwoLen -kOpenSSLReasonStringData LK_kOpenSSLReasonStringData -kOpenSSLReasonValues LK_kOpenSSLReasonValues -kOpenSSLReasonValuesLen LK_kOpenSSLReasonValuesLen -md4_block_data_order LK_md4_block_data_order -md5_block_asm_data_order LK_md5_block_asm_data_order o2i_ECPublicKey LK_o2i_ECPublicKey -pkcs12_iterations_acceptable LK_pkcs12_iterations_acceptable -pkcs12_key_gen LK_pkcs12_key_gen -pkcs12_pbe_encrypt_init LK_pkcs12_pbe_encrypt_init -pkcs7_add_signed_data LK_pkcs7_add_signed_data -pkcs7_parse_header LK_pkcs7_parse_header -pkcs8_pbe_decrypt LK_pkcs8_pbe_decrypt -pmbtoken_exp1_blind LK_pmbtoken_exp1_blind -pmbtoken_exp1_client_key_from_bytes LK_pmbtoken_exp1_client_key_from_bytes -pmbtoken_exp1_derive_key_from_secret LK_pmbtoken_exp1_derive_key_from_secret -pmbtoken_exp1_generate_key LK_pmbtoken_exp1_generate_key pmbtoken_exp1_get_h_for_testing LK_pmbtoken_exp1_get_h_for_testing -pmbtoken_exp1_issuer_key_from_bytes LK_pmbtoken_exp1_issuer_key_from_bytes -pmbtoken_exp1_read LK_pmbtoken_exp1_read -pmbtoken_exp1_sign LK_pmbtoken_exp1_sign -pmbtoken_exp1_unblind LK_pmbtoken_exp1_unblind -pmbtoken_exp2_blind LK_pmbtoken_exp2_blind -pmbtoken_exp2_client_key_from_bytes LK_pmbtoken_exp2_client_key_from_bytes -pmbtoken_exp2_derive_key_from_secret LK_pmbtoken_exp2_derive_key_from_secret -pmbtoken_exp2_generate_key LK_pmbtoken_exp2_generate_key pmbtoken_exp2_get_h_for_testing LK_pmbtoken_exp2_get_h_for_testing -pmbtoken_exp2_issuer_key_from_bytes LK_pmbtoken_exp2_issuer_key_from_bytes -pmbtoken_exp2_read LK_pmbtoken_exp2_read -pmbtoken_exp2_sign LK_pmbtoken_exp2_sign -pmbtoken_exp2_unblind LK_pmbtoken_exp2_unblind -pmbtoken_pst1_blind LK_pmbtoken_pst1_blind -pmbtoken_pst1_client_key_from_bytes LK_pmbtoken_pst1_client_key_from_bytes -pmbtoken_pst1_derive_key_from_secret LK_pmbtoken_pst1_derive_key_from_secret -pmbtoken_pst1_generate_key LK_pmbtoken_pst1_generate_key pmbtoken_pst1_get_h_for_testing LK_pmbtoken_pst1_get_h_for_testing -pmbtoken_pst1_issuer_key_from_bytes LK_pmbtoken_pst1_issuer_key_from_bytes -pmbtoken_pst1_read LK_pmbtoken_pst1_read -pmbtoken_pst1_sign LK_pmbtoken_pst1_sign -pmbtoken_pst1_unblind LK_pmbtoken_pst1_unblind -poly_Rq_mul LK_poly_Rq_mul -rand_fork_unsafe_buffering_enabled LK_rand_fork_unsafe_buffering_enabled -rsa_asn1_meth LK_rsa_asn1_meth -rsa_check_public_key LK_rsa_check_public_key -rsa_default_private_transform LK_rsa_default_private_transform -rsa_default_sign_raw LK_rsa_default_sign_raw -rsa_default_size LK_rsa_default_size -rsa_invalidate_key LK_rsa_invalidate_key -rsa_pkey_meth LK_rsa_pkey_meth -rsa_private_transform LK_rsa_private_transform -rsa_private_transform_no_self_test LK_rsa_private_transform_no_self_test -rsa_sign_no_self_test LK_rsa_sign_no_self_test -rsa_verify_no_self_test LK_rsa_verify_no_self_test -rsa_verify_raw_no_self_test LK_rsa_verify_raw_no_self_test -rsaz_1024_gather5_avx2 LK_rsaz_1024_gather5_avx2 -rsaz_1024_mul_avx2 LK_rsaz_1024_mul_avx2 -rsaz_1024_norm2red_avx2 LK_rsaz_1024_norm2red_avx2 -rsaz_1024_red2norm_avx2 LK_rsaz_1024_red2norm_avx2 -rsaz_1024_scatter5_avx2 LK_rsaz_1024_scatter5_avx2 -rsaz_1024_sqr_avx2 LK_rsaz_1024_sqr_avx2 s2i_ASN1_INTEGER LK_s2i_ASN1_INTEGER s2i_ASN1_OCTET_STRING LK_s2i_ASN1_OCTET_STRING -sha1_block_data_order_avx LK_sha1_block_data_order_avx -sha1_block_data_order_avx2 LK_sha1_block_data_order_avx2 -sha1_block_data_order_hw LK_sha1_block_data_order_hw -sha1_block_data_order_nohw LK_sha1_block_data_order_nohw -sha1_block_data_order_ssse3 LK_sha1_block_data_order_ssse3 -sha256_block_data_order_avx LK_sha256_block_data_order_avx -sha256_block_data_order_hw LK_sha256_block_data_order_hw -sha256_block_data_order_nohw LK_sha256_block_data_order_nohw -sha256_block_data_order_ssse3 LK_sha256_block_data_order_ssse3 -sha512_block_data_order_avx LK_sha512_block_data_order_avx -sha512_block_data_order_nohw LK_sha512_block_data_order_nohw +sk_deep_copy LK_sk_deep_copy +sk_delete LK_sk_delete +sk_delete_if LK_sk_delete_if +sk_delete_ptr LK_sk_delete_ptr +sk_dup LK_sk_dup +sk_find LK_sk_find sk_free LK_sk_free +sk_insert LK_sk_insert +sk_is_sorted LK_sk_is_sorted +sk_new LK_sk_new sk_new_null LK_sk_new_null sk_num LK_sk_num sk_pop LK_sk_pop sk_pop_free LK_sk_pop_free sk_pop_free_ex LK_sk_pop_free_ex sk_push LK_sk_push +sk_set LK_sk_set +sk_set_cmp_func LK_sk_set_cmp_func +sk_shift LK_sk_shift +sk_sort LK_sk_sort sk_value LK_sk_value -spx_base_b LK_spx_base_b -spx_copy_keypair_addr LK_spx_copy_keypair_addr -spx_fors_pk_from_sig LK_spx_fors_pk_from_sig -spx_fors_sign LK_spx_fors_sign -spx_fors_sk_gen LK_spx_fors_sk_gen -spx_fors_treehash LK_spx_fors_treehash -spx_get_tree_index LK_spx_get_tree_index -spx_ht_sign LK_spx_ht_sign -spx_ht_verify LK_spx_ht_verify -spx_set_chain_addr LK_spx_set_chain_addr -spx_set_hash_addr LK_spx_set_hash_addr -spx_set_keypair_addr LK_spx_set_keypair_addr -spx_set_layer_addr LK_spx_set_layer_addr -spx_set_tree_addr LK_spx_set_tree_addr -spx_set_tree_height LK_spx_set_tree_height -spx_set_tree_index LK_spx_set_tree_index -spx_set_type LK_spx_set_type -spx_thash_f LK_spx_thash_f -spx_thash_h LK_spx_thash_h -spx_thash_hmsg LK_spx_thash_hmsg -spx_thash_prf LK_spx_thash_prf -spx_thash_prfmsg LK_spx_thash_prfmsg -spx_thash_tk LK_spx_thash_tk -spx_thash_tl LK_spx_thash_tl -spx_to_uint64 LK_spx_to_uint64 -spx_treehash LK_spx_treehash -spx_uint64_to_len_bytes LK_spx_uint64_to_len_bytes -spx_wots_pk_from_sig LK_spx_wots_pk_from_sig -spx_wots_pk_gen LK_spx_wots_pk_gen -spx_wots_sign LK_spx_wots_sign -spx_xmss_pk_from_sig LK_spx_xmss_pk_from_sig -spx_xmss_sign LK_spx_xmss_sign +sk_zero LK_sk_zero +ssl_cert_check_key_usage LK_ssl_cert_check_key_usage +ssl_client_hello_init LK_ssl_client_hello_init +ssl_decode_client_hello_inner LK_ssl_decode_client_hello_inner +ssl_is_valid_ech_public_name LK_ssl_is_valid_ech_public_name +ssl_session_serialize LK_ssl_session_serialize v2i_GENERAL_NAME LK_v2i_GENERAL_NAME v2i_GENERAL_NAMES LK_v2i_GENERAL_NAMES v2i_GENERAL_NAME_ex LK_v2i_GENERAL_NAME_ex -v3_akey_id LK_v3_akey_id -v3_alt LK_v3_alt -v3_bcons LK_v3_bcons -v3_cpols LK_v3_cpols -v3_crl_invdate LK_v3_crl_invdate -v3_crl_num LK_v3_crl_num -v3_crl_reason LK_v3_crl_reason -v3_crld LK_v3_crld -v3_delta_crl LK_v3_delta_crl -v3_ext_ku LK_v3_ext_ku -v3_freshest_crl LK_v3_freshest_crl -v3_idp LK_v3_idp -v3_info LK_v3_info -v3_inhibit_anyp LK_v3_inhibit_anyp -v3_key_usage LK_v3_key_usage -v3_name_constraints LK_v3_name_constraints -v3_ns_ia5_list LK_v3_ns_ia5_list -v3_nscert LK_v3_nscert -v3_ocsp_accresp LK_v3_ocsp_accresp -v3_ocsp_nocheck LK_v3_ocsp_nocheck -v3_policy_constraints LK_v3_policy_constraints -v3_policy_mappings LK_v3_policy_mappings -v3_sinfo LK_v3_sinfo -v3_skey_id LK_v3_skey_id -voprf_exp2_blind LK_voprf_exp2_blind -voprf_exp2_client_key_from_bytes LK_voprf_exp2_client_key_from_bytes -voprf_exp2_derive_key_from_secret LK_voprf_exp2_derive_key_from_secret -voprf_exp2_generate_key LK_voprf_exp2_generate_key -voprf_exp2_issuer_key_from_bytes LK_voprf_exp2_issuer_key_from_bytes -voprf_exp2_read LK_voprf_exp2_read -voprf_exp2_sign LK_voprf_exp2_sign -voprf_exp2_unblind LK_voprf_exp2_unblind -voprf_pst1_blind LK_voprf_pst1_blind -voprf_pst1_client_key_from_bytes LK_voprf_pst1_client_key_from_bytes -voprf_pst1_derive_key_from_secret LK_voprf_pst1_derive_key_from_secret -voprf_pst1_generate_key LK_voprf_pst1_generate_key -voprf_pst1_issuer_key_from_bytes LK_voprf_pst1_issuer_key_from_bytes -voprf_pst1_read LK_voprf_pst1_read -voprf_pst1_sign LK_voprf_pst1_sign -voprf_pst1_sign_with_proof_scalar_for_testing LK_voprf_pst1_sign_with_proof_scalar_for_testing -voprf_pst1_unblind LK_voprf_pst1_unblind -vpaes_cbc_encrypt LK_vpaes_cbc_encrypt -vpaes_ctr32_encrypt_blocks LK_vpaes_ctr32_encrypt_blocks -vpaes_decrypt LK_vpaes_decrypt -vpaes_encrypt LK_vpaes_encrypt -vpaes_set_decrypt_key LK_vpaes_set_decrypt_key -vpaes_set_encrypt_key LK_vpaes_set_encrypt_key -x25519_asn1_meth LK_x25519_asn1_meth -x25519_ge_add LK_x25519_ge_add -x25519_ge_frombytes_vartime LK_x25519_ge_frombytes_vartime -x25519_ge_p1p1_to_p2 LK_x25519_ge_p1p1_to_p2 -x25519_ge_p1p1_to_p3 LK_x25519_ge_p1p1_to_p3 -x25519_ge_p3_to_cached LK_x25519_ge_p3_to_cached -x25519_ge_scalarmult LK_x25519_ge_scalarmult -x25519_ge_scalarmult_base LK_x25519_ge_scalarmult_base -x25519_ge_scalarmult_base_adx LK_x25519_ge_scalarmult_base_adx -x25519_ge_scalarmult_small_precomp LK_x25519_ge_scalarmult_small_precomp -x25519_ge_sub LK_x25519_ge_sub -x25519_ge_tobytes LK_x25519_ge_tobytes -x25519_pkey_meth LK_x25519_pkey_meth -x25519_sc_reduce LK_x25519_sc_reduce -x25519_scalar_mult_adx LK_x25519_scalar_mult_adx -x509V3_add_value_asn1_string LK_x509V3_add_value_asn1_string -x509_check_issued_with_callback LK_x509_check_issued_with_callback -x509_digest_sign_algorithm LK_x509_digest_sign_algorithm -x509_digest_verify_init LK_x509_digest_verify_init -x509_print_rsa_pss_params LK_x509_print_rsa_pss_params -x509_rsa_ctx_to_pss LK_x509_rsa_ctx_to_pss -x509_rsa_pss_to_ctx LK_x509_rsa_pss_to_ctx -x509v3_a2i_ipadd LK_x509v3_a2i_ipadd x509v3_bytes_to_hex LK_x509v3_bytes_to_hex x509v3_cache_extensions LK_x509v3_cache_extensions -x509v3_conf_name_matches LK_x509v3_conf_name_matches -x509v3_hex_to_bytes LK_x509v3_hex_to_bytes x509v3_looks_like_dns_name LK_x509v3_looks_like_dns_name From c1be3f235f66814bbc52bb98e7752a9281febec8 Mon Sep 17 00:00:00 2001 From: aoife cassidy Date: Tue, 3 Sep 2024 10:14:34 -0700 Subject: [PATCH 022/274] bump: webrtc-sys{,-build}, libwebrtc, livekit (#417) --- libwebrtc/Cargo.toml | 4 ++-- livekit/Cargo.toml | 2 +- webrtc-sys/Cargo.toml | 4 ++-- webrtc-sys/build/Cargo.toml | 2 +- webrtc-sys/build/src/lib.rs | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/libwebrtc/Cargo.toml b/libwebrtc/Cargo.toml index 74ed6f6a5..36643a9ae 100644 --- a/libwebrtc/Cargo.toml +++ b/libwebrtc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libwebrtc" -version = "0.3.5" +version = "0.3.6" edition = "2021" homepage = "https://livekit.io" license = "Apache-2.0" @@ -18,7 +18,7 @@ thiserror = "1.0" jni = "0.21" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -webrtc-sys = { path = "../webrtc-sys", version = "0.3.3" } +webrtc-sys = { path = "../webrtc-sys", version = "0.3.4" } livekit-runtime = { path = "../livekit-runtime", version = "0.3.0" } lazy_static = "1.4" parking_lot = { version = "0.12" } diff --git a/livekit/Cargo.toml b/livekit/Cargo.toml index c03f5201d..e71563829 100644 --- a/livekit/Cargo.toml +++ b/livekit/Cargo.toml @@ -29,7 +29,7 @@ __lk-internal = [] [dependencies] livekit-runtime = { path = "../livekit-runtime", version = "0.3.0", default-features = false } livekit-api = { path = "../livekit-api", version = "0.4.0", default-features = false } -libwebrtc = { path = "../libwebrtc", version = "0.3.5" } +libwebrtc = { path = "../libwebrtc", version = "0.3.6" } livekit-protocol = { path = "../livekit-protocol", version = "0.3.5" } prost = "0.12" serde = { version = "1", features = ["derive"] } diff --git a/webrtc-sys/Cargo.toml b/webrtc-sys/Cargo.toml index e1f0a9263..0c7125376 100644 --- a/webrtc-sys/Cargo.toml +++ b/webrtc-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "webrtc-sys" -version = "0.3.3" +version = "0.3.4" edition = "2021" homepage = "https://livekit.io" license = "Apache-2.0" @@ -12,7 +12,7 @@ cxx = "1.0" log = "0.4" [build-dependencies] -webrtc-sys-build = { version = "0.3.3", path = "./build" } +webrtc-sys-build = { version = "0.3.4", path = "./build" } cxx-build = "1.0" glob = "0.3" cc = "1.0" diff --git a/webrtc-sys/build/Cargo.toml b/webrtc-sys/build/Cargo.toml index f86387082..6943be1a6 100644 --- a/webrtc-sys/build/Cargo.toml +++ b/webrtc-sys/build/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "webrtc-sys-build" -version = "0.3.3" +version = "0.3.4" edition = "2021" license = "Apache-2.0" description = "Build utilities when working with libwebrtc" diff --git a/webrtc-sys/build/src/lib.rs b/webrtc-sys/build/src/lib.rs index 2625b8f80..f4509ed44 100644 --- a/webrtc-sys/build/src/lib.rs +++ b/webrtc-sys/build/src/lib.rs @@ -26,7 +26,7 @@ use regex::Regex; use reqwest::StatusCode; pub const SCRATH_PATH: &str = "livekit_webrtc"; -pub const WEBRTC_TAG: &str = "webrtc-dac8015-3"; +pub const WEBRTC_TAG: &str = "webrtc-dac8015-4"; pub const IGNORE_DEFINES: [&str; 2] = ["CR_CLANG_REVISION", "CR_XCODE_VERSION"]; pub fn target_os() -> String { From 09503f4ae3245814ffcc4dc5eaf424b78f26dd8c Mon Sep 17 00:00:00 2001 From: Neil Dwyer Date: Tue, 3 Sep 2024 11:34:15 -0700 Subject: [PATCH 023/274] Cleanup media streams when finished (#411) * [WIP] Cleanup stream * . * . * revert previous idea * Handle dropped broadcaster * fmt * remove unused * remove useless log * handle based watching --- livekit-ffi/src/server/audio_stream.rs | 20 ++++++++++++-------- livekit-ffi/src/server/mod.rs | 24 ++++++++++++++++++++++-- livekit-ffi/src/server/room.rs | 14 +++++++++++++- livekit-ffi/src/server/video_stream.rs | 18 +++++++++++------- 4 files changed, 58 insertions(+), 18 deletions(-) diff --git a/livekit-ffi/src/server/audio_stream.rs b/livekit-ffi/src/server/audio_stream.rs index 9cb0f6c21..d0fd42972 100644 --- a/livekit-ffi/src/server/audio_stream.rs +++ b/livekit-ffi/src/server/audio_stream.rs @@ -24,7 +24,7 @@ pub struct FfiAudioStream { pub stream_type: proto::AudioStreamType, #[allow(dead_code)] - close_tx: oneshot::Sender<()>, // Close the stream on drop + self_dropped_tx: oneshot::Sender<()>, // Close the stream on drop } impl FfiHandle for FfiAudioStream {} @@ -44,18 +44,18 @@ impl FfiAudioStream { ) -> FfiResult { let ffi_track = server.retrieve_handle::(new_stream.track_handle)?.clone(); let rtc_track = ffi_track.track.rtc_track(); + let (self_dropped_tx, self_dropped_rx) = oneshot::channel(); let MediaStreamTrack::Audio(rtc_track) = rtc_track else { return Err(FfiError::InvalidRequest("not an audio track".into())); }; - let (close_tx, close_rx) = oneshot::channel(); let stream_type = new_stream.r#type(); let handle_id = server.next_id(); let audio_stream = match stream_type { #[cfg(not(target_arch = "wasm32"))] proto::AudioStreamType::AudioStreamNative => { - let audio_stream = Self { handle_id, stream_type, close_tx }; + let audio_stream = Self { handle_id, stream_type, self_dropped_tx }; let sample_rate = if new_stream.sample_rate == 0 { 48000 } else { new_stream.sample_rate as i32 }; @@ -69,7 +69,8 @@ impl FfiAudioStream { server, handle_id, native_stream, - close_rx, + self_dropped_rx, + server.watch_handle_dropped(new_stream.track_handle), )); server.watch_panic(handle); Ok::(audio_stream) @@ -91,11 +92,15 @@ impl FfiAudioStream { server: &'static server::FfiServer, stream_handle_id: FfiHandleId, mut native_stream: NativeAudioStream, - mut close_rx: oneshot::Receiver<()>, + mut self_dropped_rx: oneshot::Receiver<()>, + mut handle_dropped_rx: oneshot::Receiver<()>, ) { loop { tokio::select! { - _ = &mut close_rx => { + _ = &mut self_dropped_rx => { + break; + } + _ = &mut handle_dropped_rx => { break; } frame = native_stream.next() => { @@ -126,14 +131,13 @@ impl FfiAudioStream { } } } - if let Err(err) = server.send_event(proto::ffi_event::Message::AudioStreamEvent( proto::AudioStreamEvent { stream_handle: stream_handle_id, message: Some(proto::audio_stream_event::Message::Eos(proto::AudioStreamEos {})), }, )) { - log::warn!("failed to send audio EOS: {}", err); + log::warn!("failed to send audio eos: {}", err); } } } diff --git a/livekit-ffi/src/server/mod.rs b/livekit-ffi/src/server/mod.rs index 2d3cf6086..1a26fd6ae 100644 --- a/livekit-ffi/src/server/mod.rs +++ b/livekit-ffi/src/server/mod.rs @@ -13,6 +13,7 @@ // limitations under the License. use std::{ + collections::HashMap, error::Error, sync::{ atomic::{AtomicU64, Ordering}, @@ -26,7 +27,10 @@ use dashmap::{mapref::one::MappedRef, DashMap}; use downcast_rs::{impl_downcast, Downcast}; use livekit::webrtc::{native::audio_resampler::AudioResampler, prelude::*}; use parking_lot::{deadlock, Mutex}; -use tokio::task::JoinHandle; +use tokio::{ + sync::{broadcast, oneshot}, + task::JoinHandle, +}; use crate::{proto, proto::FfiEvent, FfiError, FfiHandleId, FfiResult, INVALID_HANDLE}; @@ -74,6 +78,7 @@ pub struct FfiServer { next_id: AtomicU64, config: Mutex>, logger: &'static logger::FfiLogger, + handle_dropped_txs: DashMap>>, } impl Default for FfiServer { @@ -111,6 +116,7 @@ impl Default for FfiServer { async_runtime, config: Default::default(), logger, + handle_dropped_txs: Default::default(), } } } @@ -142,6 +148,7 @@ impl FfiServer { // Drop all handles self.ffi_handles.clear(); + self.handle_dropped_txs.clear(); *self.config.lock() = None; // Invalidate the config } @@ -192,7 +199,20 @@ impl FfiServer { } pub fn drop_handle(&self, id: FfiHandleId) -> bool { - self.ffi_handles.remove(&id).is_some() + let existed = self.ffi_handles.remove(&id).is_some(); + self.handle_dropped_txs.remove(&id); + return existed; + } + + pub fn watch_handle_dropped(&self, handle: FfiHandleId) -> oneshot::Receiver<()> { + // Create vec if not exists + if self.handle_dropped_txs.get(&handle).is_none() { + self.handle_dropped_txs.insert(handle, Vec::new()); + } + let (tx, rx) = oneshot::channel::<()>(); + let mut tx_vec = self.handle_dropped_txs.get_mut(&handle).unwrap(); + tx_vec.push(tx); + return rx; } pub fn send_panic(&self, err: Box) { diff --git a/livekit-ffi/src/server/room.rs b/livekit-ffi/src/server/room.rs index 967254ecf..20a4c9b0c 100644 --- a/livekit-ffi/src/server/room.rs +++ b/livekit-ffi/src/server/room.rs @@ -12,10 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::collections::HashMap; use std::{collections::HashSet, slice, sync::Arc, time::Duration}; -use livekit::participant; use livekit::prelude::*; +use livekit::{participant, track}; use parking_lot::Mutex; use tokio::sync::{broadcast, mpsc, oneshot, Mutex as AsyncMutex}; use tokio::task::JoinHandle; @@ -70,6 +71,8 @@ pub struct RoomInner { pending_published_tracks: Mutex>, // Used to wait for the LocalTrackUnpublished event pending_unpublished_tracks: Mutex>, + + track_handle_lookup: Arc>>, } struct Handle { @@ -145,6 +148,7 @@ impl FfiRoom { dtmf_tx, pending_published_tracks: Default::default(), pending_unpublished_tracks: Default::default(), + track_handle_lookup: Default::default(), }); let (local_info, remote_infos) = @@ -815,10 +819,12 @@ async fn forward_event( } RoomEvent::TrackSubscribed { track, publication: _, participant } => { let handle_id = server.next_id(); + let track_sid = track.sid(); let ffi_track = FfiTrack { handle: handle_id, track: track.into() }; let track_info = proto::TrackInfo::from(&ffi_track); server.store_handle(ffi_track.handle, ffi_track); + inner.track_handle_lookup.lock().insert(track_sid, handle_id); let _ = send_event(proto::room_event::Message::TrackSubscribed(proto::TrackSubscribed { @@ -830,6 +836,12 @@ async fn forward_event( })); } RoomEvent::TrackUnsubscribed { track, publication: _, participant } => { + let track_sid = track.sid(); + if let Some(handle) = inner.track_handle_lookup.lock().remove(&track_sid) { + server.drop_handle(handle); + } else { + log::warn!("track {} was not found in the lookup table", track_sid); + } let _ = send_event(proto::room_event::Message::TrackUnsubscribed( proto::TrackUnsubscribed { participant_identity: participant.identity().to_string(), diff --git a/livekit-ffi/src/server/video_stream.rs b/livekit-ffi/src/server/video_stream.rs index 83fc79b54..4512e4778 100644 --- a/livekit-ffi/src/server/video_stream.rs +++ b/livekit-ffi/src/server/video_stream.rs @@ -24,7 +24,7 @@ pub struct FfiVideoStream { pub stream_type: proto::VideoStreamType, #[allow(dead_code)] - close_tx: oneshot::Sender<()>, // Close the stream on drop + self_dropped_tx: oneshot::Sender<()>, // Close the stream on drop } impl FfiHandle for FfiVideoStream {} @@ -49,20 +49,21 @@ impl FfiVideoStream { return Err(FfiError::InvalidRequest("not a video track".into())); }; - let (close_tx, close_rx) = oneshot::channel(); + let (self_dropped_tx, self_dropped_rx) = oneshot::channel(); let stream_type = new_stream.r#type(); let handle_id = server.next_id(); let stream = match stream_type { #[cfg(not(target_arch = "wasm32"))] proto::VideoStreamType::VideoStreamNative => { - let video_stream = Self { handle_id, close_tx, stream_type }; + let video_stream = Self { handle_id, self_dropped_tx, stream_type }; let handle = server.async_runtime.spawn(Self::native_video_stream_task( server, handle_id, new_stream.format.and_then(|_| Some(new_stream.format())), new_stream.normalize_stride, NativeVideoStream::new(rtc_track), - close_rx, + self_dropped_rx, + server.watch_handle_dropped(new_stream.track_handle), )); server.watch_panic(handle); Ok::(video_stream) @@ -86,11 +87,15 @@ impl FfiVideoStream { dst_type: Option, normalize_stride: bool, mut native_stream: NativeVideoStream, - mut close_rx: oneshot::Receiver<()>, + mut self_dropped_rx: oneshot::Receiver<()>, + mut handle_dropped_rx: oneshot::Receiver<()>, ) { loop { tokio::select! { - _ = &mut close_rx => { + _ = &mut self_dropped_rx => { + break; + } + _ = &mut handle_dropped_rx => { break; } frame = native_stream.next() => { @@ -98,7 +103,6 @@ impl FfiVideoStream { break; }; - let Ok((buffer, info)) = colorcvt::to_video_buffer_info(frame.buffer, dst_type, normalize_stride) else { log::error!("video stream failed to convert video frame to {:?}", dst_type); continue; From d7436d5e64425fd08255b5203e6c0f8002c6a021 Mon Sep 17 00:00:00 2001 From: aoife cassidy Date: Tue, 3 Sep 2024 14:56:07 -0700 Subject: [PATCH 024/274] actually bump livekit to 0.5.2 (#419) --- livekit/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/livekit/Cargo.toml b/livekit/Cargo.toml index e71563829..17cf43f12 100644 --- a/livekit/Cargo.toml +++ b/livekit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit" -version = "0.5.1" +version = "0.5.2" edition = "2021" license = "Apache-2.0" description = "Rust Client SDK for LiveKit" From c9770cc8153d2089433cd622cc903bf4d906cf51 Mon Sep 17 00:00:00 2001 From: aoife cassidy Date: Wed, 4 Sep 2024 14:52:14 -0700 Subject: [PATCH 025/274] fix comment for boringssl_prefix_symbols.txt (#423) --- webrtc-sys/libwebrtc/boringssl_prefix_symbols.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/webrtc-sys/libwebrtc/boringssl_prefix_symbols.txt b/webrtc-sys/libwebrtc/boringssl_prefix_symbols.txt index ab6604df3..69eeb5a59 100644 --- a/webrtc-sys/libwebrtc/boringssl_prefix_symbols.txt +++ b/webrtc-sys/libwebrtc/boringssl_prefix_symbols.txt @@ -2,7 +2,6 @@ # version m114_release, by grepping for OPENSSL_EXPORT: # # rg -NIU "^.*OPENSSL_EXPORT .*[ \n]\*?([\d\w_]+)\(.*$" -r '$1' | sort | uniq - AES_CMAC LK_AES_CMAC AES_cbc_encrypt LK_AES_cbc_encrypt AES_cfb128_encrypt LK_AES_cfb128_encrypt From 430f7042a7e76e7c16c60e6cec2c42980737768a Mon Sep 17 00:00:00 2001 From: aoife cassidy Date: Thu, 5 Sep 2024 00:59:30 -0700 Subject: [PATCH 026/274] bump packages (#424) --- libwebrtc/Cargo.toml | 4 ++-- livekit/Cargo.toml | 4 ++-- webrtc-sys/Cargo.toml | 4 ++-- webrtc-sys/build/Cargo.toml | 2 +- webrtc-sys/build/src/lib.rs | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/libwebrtc/Cargo.toml b/libwebrtc/Cargo.toml index 36643a9ae..28d7ad5f8 100644 --- a/libwebrtc/Cargo.toml +++ b/libwebrtc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libwebrtc" -version = "0.3.6" +version = "0.3.7" edition = "2021" homepage = "https://livekit.io" license = "Apache-2.0" @@ -18,7 +18,7 @@ thiserror = "1.0" jni = "0.21" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -webrtc-sys = { path = "../webrtc-sys", version = "0.3.4" } +webrtc-sys = { path = "../webrtc-sys", version = "0.3.5" } livekit-runtime = { path = "../livekit-runtime", version = "0.3.0" } lazy_static = "1.4" parking_lot = { version = "0.12" } diff --git a/livekit/Cargo.toml b/livekit/Cargo.toml index 17cf43f12..e57548b5a 100644 --- a/livekit/Cargo.toml +++ b/livekit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit" -version = "0.5.2" +version = "0.5.3" edition = "2021" license = "Apache-2.0" description = "Rust Client SDK for LiveKit" @@ -29,7 +29,7 @@ __lk-internal = [] [dependencies] livekit-runtime = { path = "../livekit-runtime", version = "0.3.0", default-features = false } livekit-api = { path = "../livekit-api", version = "0.4.0", default-features = false } -libwebrtc = { path = "../libwebrtc", version = "0.3.6" } +libwebrtc = { path = "../libwebrtc", version = "0.3.7" } livekit-protocol = { path = "../livekit-protocol", version = "0.3.5" } prost = "0.12" serde = { version = "1", features = ["derive"] } diff --git a/webrtc-sys/Cargo.toml b/webrtc-sys/Cargo.toml index 0c7125376..5b249ca33 100644 --- a/webrtc-sys/Cargo.toml +++ b/webrtc-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "webrtc-sys" -version = "0.3.4" +version = "0.3.5" edition = "2021" homepage = "https://livekit.io" license = "Apache-2.0" @@ -12,7 +12,7 @@ cxx = "1.0" log = "0.4" [build-dependencies] -webrtc-sys-build = { version = "0.3.4", path = "./build" } +webrtc-sys-build = { version = "0.3.5", path = "./build" } cxx-build = "1.0" glob = "0.3" cc = "1.0" diff --git a/webrtc-sys/build/Cargo.toml b/webrtc-sys/build/Cargo.toml index 6943be1a6..7248cd1bb 100644 --- a/webrtc-sys/build/Cargo.toml +++ b/webrtc-sys/build/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "webrtc-sys-build" -version = "0.3.4" +version = "0.3.5" edition = "2021" license = "Apache-2.0" description = "Build utilities when working with libwebrtc" diff --git a/webrtc-sys/build/src/lib.rs b/webrtc-sys/build/src/lib.rs index f4509ed44..f3eb2a5ea 100644 --- a/webrtc-sys/build/src/lib.rs +++ b/webrtc-sys/build/src/lib.rs @@ -26,7 +26,7 @@ use regex::Regex; use reqwest::StatusCode; pub const SCRATH_PATH: &str = "livekit_webrtc"; -pub const WEBRTC_TAG: &str = "webrtc-dac8015-4"; +pub const WEBRTC_TAG: &str = "webrtc-dac8015-5"; pub const IGNORE_DEFINES: [&str; 2] = ["CR_CLANG_REVISION", "CR_XCODE_VERSION"]; pub fn target_os() -> String { From c478f98c79e5e16e502d680ab0ed3a9b8b294241 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Thu, 5 Sep 2024 14:02:32 -0700 Subject: [PATCH 027/274] bump livekit-ffi v0.8.3 (#418) --- livekit-ffi/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index a27267bbf..5d8080836 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.8.2" +version = "0.8.3" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" From 0fe6ae76affc50a740ec628c4302d01648a21173 Mon Sep 17 00:00:00 2001 From: Neil Dwyer Date: Thu, 5 Sep 2024 15:39:55 -0700 Subject: [PATCH 028/274] Convenience API for iterating frames from a participant (#396) Convencience API for iterating frames --- Cargo.lock | 8 +- livekit-ffi/protocol/audio_frame.proto | 11 ++ livekit-ffi/protocol/ffi.proto | 29 ++--- livekit-ffi/protocol/video_frame.proto | 12 ++ livekit-ffi/src/livekit.proto.rs | 78 ++++++++++--- livekit-ffi/src/server/audio_stream.rs | 154 +++++++++++++++++++++++-- livekit-ffi/src/server/mod.rs | 1 + livekit-ffi/src/server/requests.rs | 31 ++++- livekit-ffi/src/server/room.rs | 6 - livekit-ffi/src/server/utils.rs | 62 ++++++++++ livekit-ffi/src/server/video_stream.rs | 141 +++++++++++++++++++++- 11 files changed, 482 insertions(+), 51 deletions(-) create mode 100644 livekit-ffi/src/server/utils.rs diff --git a/Cargo.lock b/Cargo.lock index cd647d60e..907ca7ae0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1490,7 +1490,7 @@ dependencies = [ [[package]] name = "libwebrtc" -version = "0.3.5" +version = "0.3.6" dependencies = [ "cxx", "env_logger", @@ -1546,7 +1546,7 @@ checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "livekit" -version = "0.5.1" +version = "0.5.2" dependencies = [ "futures-util", "lazy_static", @@ -3234,7 +3234,7 @@ checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" [[package]] name = "webrtc-sys" -version = "0.3.3" +version = "0.3.4" dependencies = [ "cc", "cxx", @@ -3247,7 +3247,7 @@ dependencies = [ [[package]] name = "webrtc-sys-build" -version = "0.3.3" +version = "0.3.4" dependencies = [ "fs2", "regex", diff --git a/livekit-ffi/protocol/audio_frame.proto b/livekit-ffi/protocol/audio_frame.proto index 35f6b8372..abe304ff5 100644 --- a/livekit-ffi/protocol/audio_frame.proto +++ b/livekit-ffi/protocol/audio_frame.proto @@ -18,6 +18,7 @@ package livekit.proto; option csharp_namespace = "LiveKit.Proto"; import "handle.proto"; +import "track.proto"; // Create a new AudioStream // AudioStream is used to receive audio frames from a track @@ -29,6 +30,16 @@ message NewAudioStreamRequest { } message NewAudioStreamResponse { OwnedAudioStream stream = 1; } +message AudioStreamFromParticipantRequest { + uint64 participant_handle = 1; + AudioStreamType type = 2; + optional TrackSource track_source = 3; + uint32 sample_rate = 5; + uint32 num_channels = 6; +} + +message AudioStreamFromParticipantResponse { OwnedAudioStream stream = 1; } + // Create a new AudioSource message NewAudioSourceRequest { AudioSourceType type = 1; diff --git a/livekit-ffi/protocol/ffi.proto b/livekit-ffi/protocol/ffi.proto index 5d4f9bc0a..ba87d8417 100644 --- a/livekit-ffi/protocol/ffi.proto +++ b/livekit-ffi/protocol/ffi.proto @@ -82,15 +82,16 @@ message FfiRequest { NewVideoSourceRequest new_video_source = 21; CaptureVideoFrameRequest capture_video_frame = 22; VideoConvertRequest video_convert = 23; + VideoStreamFromParticipantRequest video_stream_from_participant = 24; // Audio - NewAudioStreamRequest new_audio_stream = 24; - NewAudioSourceRequest new_audio_source = 25; - CaptureAudioFrameRequest capture_audio_frame = 26; - NewAudioResamplerRequest new_audio_resampler = 27; - RemixAndResampleRequest remix_and_resample = 28; - - E2eeRequest e2ee = 29; + NewAudioStreamRequest new_audio_stream = 25; + NewAudioSourceRequest new_audio_source = 26; + CaptureAudioFrameRequest capture_audio_frame = 27; + NewAudioResamplerRequest new_audio_resampler = 28; + RemixAndResampleRequest remix_and_resample = 29; + E2eeRequest e2ee = 30; + AudioStreamFromParticipantRequest audio_stream_from_participant = 31; } } @@ -125,14 +126,16 @@ message FfiResponse { NewVideoSourceResponse new_video_source = 21; CaptureVideoFrameResponse capture_video_frame = 22; VideoConvertResponse video_convert = 23; + VideoStreamFromParticipantResponse video_stream_from_participant = 24; // Audio - NewAudioStreamResponse new_audio_stream = 24; - NewAudioSourceResponse new_audio_source = 25; - CaptureAudioFrameResponse capture_audio_frame = 26; - NewAudioResamplerResponse new_audio_resampler = 27; - RemixAndResampleResponse remix_and_resample = 28; - E2eeResponse e2ee = 29; + NewAudioStreamResponse new_audio_stream = 25; + NewAudioSourceResponse new_audio_source = 26; + CaptureAudioFrameResponse capture_audio_frame = 27; + NewAudioResamplerResponse new_audio_resampler = 28; + RemixAndResampleResponse remix_and_resample = 29; + AudioStreamFromParticipantResponse audio_stream_from_participant = 30; + E2eeResponse e2ee = 31; } } diff --git a/livekit-ffi/protocol/video_frame.proto b/livekit-ffi/protocol/video_frame.proto index ffb2e0bde..090518b5f 100644 --- a/livekit-ffi/protocol/video_frame.proto +++ b/livekit-ffi/protocol/video_frame.proto @@ -18,6 +18,7 @@ package livekit.proto; option csharp_namespace = "LiveKit.Proto"; import "handle.proto"; +import "track.proto"; // Create a new VideoStream // VideoStream is used to receive video frames from a track @@ -30,6 +31,17 @@ message NewVideoStreamRequest { } message NewVideoStreamResponse { OwnedVideoStream stream = 1; } +// Request a video stream from a participant +message VideoStreamFromParticipantRequest { + uint64 participant_handle = 1; + VideoStreamType type = 2; + TrackSource track_source = 3; + optional VideoBufferType format = 4; + bool normalize_stride = 5; +} + +message VideoStreamFromParticipantResponse { OwnedVideoStream stream = 1;} + // Create a new VideoSource // VideoSource is used to send video frame to a track message NewVideoSourceRequest { diff --git a/livekit-ffi/src/livekit.proto.rs b/livekit-ffi/src/livekit.proto.rs index 141966bba..38351d72a 100644 --- a/livekit-ffi/src/livekit.proto.rs +++ b/livekit-ffi/src/livekit.proto.rs @@ -1,4 +1,5 @@ // @generated +// This file is @generated by prost-build. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FrameCryptor { @@ -1572,6 +1573,27 @@ pub struct NewVideoStreamResponse { #[prost(message, optional, tag="1")] pub stream: ::core::option::Option, } +/// Request a video stream from a participant +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct VideoStreamFromParticipantRequest { + #[prost(uint64, tag="1")] + pub participant_handle: u64, + #[prost(enumeration="VideoStreamType", tag="2")] + pub r#type: i32, + #[prost(enumeration="TrackSource", tag="3")] + pub track_source: i32, + #[prost(enumeration="VideoBufferType", optional, tag="4")] + pub format: ::core::option::Option, + #[prost(bool, tag="5")] + pub normalize_stride: bool, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct VideoStreamFromParticipantResponse { + #[prost(message, optional, tag="1")] + pub stream: ::core::option::Option, +} /// Create a new VideoSource /// VideoSource is used to send video frame to a track #[allow(clippy::derive_partial_eq_without_eq)] @@ -2891,6 +2913,26 @@ pub struct NewAudioStreamResponse { #[prost(message, optional, tag="1")] pub stream: ::core::option::Option, } +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct AudioStreamFromParticipantRequest { + #[prost(uint64, tag="1")] + pub participant_handle: u64, + #[prost(enumeration="AudioStreamType", tag="2")] + pub r#type: i32, + #[prost(enumeration="TrackSource", optional, tag="3")] + pub track_source: ::core::option::Option, + #[prost(uint32, tag="5")] + pub sample_rate: u32, + #[prost(uint32, tag="6")] + pub num_channels: u32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct AudioStreamFromParticipantResponse { + #[prost(message, optional, tag="1")] + pub stream: ::core::option::Option, +} /// Create a new AudioSource #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -3162,7 +3204,7 @@ impl AudioSourceType { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiRequest { - #[prost(oneof="ffi_request::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29")] + #[prost(oneof="ffi_request::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31")] pub message: ::core::option::Option, } /// Nested message and enum types in `FfiRequest`. @@ -3217,26 +3259,30 @@ pub mod ffi_request { CaptureVideoFrame(super::CaptureVideoFrameRequest), #[prost(message, tag="23")] VideoConvert(super::VideoConvertRequest), - /// Audio #[prost(message, tag="24")] - NewAudioStream(super::NewAudioStreamRequest), + VideoStreamFromParticipant(super::VideoStreamFromParticipantRequest), + /// Audio #[prost(message, tag="25")] - NewAudioSource(super::NewAudioSourceRequest), + NewAudioStream(super::NewAudioStreamRequest), #[prost(message, tag="26")] - CaptureAudioFrame(super::CaptureAudioFrameRequest), + NewAudioSource(super::NewAudioSourceRequest), #[prost(message, tag="27")] - NewAudioResampler(super::NewAudioResamplerRequest), + CaptureAudioFrame(super::CaptureAudioFrameRequest), #[prost(message, tag="28")] - RemixAndResample(super::RemixAndResampleRequest), + NewAudioResampler(super::NewAudioResamplerRequest), #[prost(message, tag="29")] + RemixAndResample(super::RemixAndResampleRequest), + #[prost(message, tag="30")] E2ee(super::E2eeRequest), + #[prost(message, tag="31")] + AudioStreamFromParticipant(super::AudioStreamFromParticipantRequest), } } /// This is the output of livekit_ffi_request function. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiResponse { - #[prost(oneof="ffi_response::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29")] + #[prost(oneof="ffi_response::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31")] pub message: ::core::option::Option, } /// Nested message and enum types in `FfiResponse`. @@ -3291,18 +3337,22 @@ pub mod ffi_response { CaptureVideoFrame(super::CaptureVideoFrameResponse), #[prost(message, tag="23")] VideoConvert(super::VideoConvertResponse), - /// Audio #[prost(message, tag="24")] - NewAudioStream(super::NewAudioStreamResponse), + VideoStreamFromParticipant(super::VideoStreamFromParticipantResponse), + /// Audio #[prost(message, tag="25")] - NewAudioSource(super::NewAudioSourceResponse), + NewAudioStream(super::NewAudioStreamResponse), #[prost(message, tag="26")] - CaptureAudioFrame(super::CaptureAudioFrameResponse), + NewAudioSource(super::NewAudioSourceResponse), #[prost(message, tag="27")] - NewAudioResampler(super::NewAudioResamplerResponse), + CaptureAudioFrame(super::CaptureAudioFrameResponse), #[prost(message, tag="28")] - RemixAndResample(super::RemixAndResampleResponse), + NewAudioResampler(super::NewAudioResamplerResponse), #[prost(message, tag="29")] + RemixAndResample(super::RemixAndResampleResponse), + #[prost(message, tag="30")] + AudioStreamFromParticipant(super::AudioStreamFromParticipantResponse), + #[prost(message, tag="31")] E2ee(super::E2eeResponse), } } diff --git a/livekit-ffi/src/server/audio_stream.rs b/livekit-ffi/src/server/audio_stream.rs index d0fd42972..bef90859a 100644 --- a/livekit-ffi/src/server/audio_stream.rs +++ b/livekit-ffi/src/server/audio_stream.rs @@ -13,10 +13,12 @@ // limitations under the License. use futures_util::StreamExt; +use livekit::track::Track; use livekit::webrtc::{audio_stream::native::NativeAudioStream, prelude::*}; -use tokio::sync::oneshot; +use tokio::sync::{broadcast, mpsc, oneshot}; use super::{room::FfiTrack, FfiHandle}; +use crate::server::utils; use crate::{proto, server, FfiError, FfiHandleId, FfiResult}; pub struct FfiAudioStream { @@ -38,7 +40,7 @@ impl FfiAudioStream { /// /// It is possible that the client receives an AudioFrame after the task is closed. The client /// musts ignore it. - pub fn setup( + pub fn from_track( server: &'static server::FfiServer, new_stream: proto::NewAudioStreamRequest, ) -> FfiResult { @@ -71,6 +73,7 @@ impl FfiAudioStream { native_stream, self_dropped_rx, server.watch_handle_dropped(new_stream.track_handle), + true, )); server.watch_panic(handle); Ok::(audio_stream) @@ -88,12 +91,141 @@ impl FfiAudioStream { }) } + pub fn from_participant( + server: &'static server::FfiServer, + request: proto::AudioStreamFromParticipantRequest, + ) -> FfiResult { + let (self_dropped_tx, self_dropped_rx) = oneshot::channel(); + let handle_id = server.next_id(); + let stream_type = request.r#type(); + let audio_stream = match stream_type { + #[cfg(not(target_arch = "wasm32"))] + proto::AudioStreamType::AudioStreamNative => { + let audio_stream = Self { handle_id, stream_type, self_dropped_tx }; + + let handle = server.async_runtime.spawn(Self::participant_audio_stream_task( + server, + request, + handle_id, + self_dropped_rx, + )); + server.watch_panic(handle); + Ok::(audio_stream) + } + _ => return Err(FfiError::InvalidRequest("unsupported audio stream type".into())), + }?; + + // Store the new audio stream and return the info + let info = proto::AudioStreamInfo::from(&audio_stream); + server.store_handle(handle_id, audio_stream); + + Ok(proto::OwnedAudioStream { + handle: Some(proto::FfiOwnedHandle { id: handle_id }), + info: Some(info), + }) + } + + async fn participant_audio_stream_task( + server: &'static server::FfiServer, + request: proto::AudioStreamFromParticipantRequest, + stream_handle: FfiHandleId, + mut self_dropped_rx: oneshot::Receiver<()>, + ) { + let ffi_participant = + utils::ffi_participant_from_handle(server, request.participant_handle); + let ffi_participant = match ffi_participant { + Ok(ffi_participant) => ffi_participant, + Err(err) => { + log::error!("failed to get participant: {}", err); + return; + } + }; + + let track_source = request.track_source(); + let (track_tx, mut track_rx) = mpsc::channel::(1); + let (track_finished_tx, _) = broadcast::channel::(1); + server.async_runtime.spawn(utils::track_changed_trigger( + ffi_participant, + track_source.into(), + track_tx, + track_finished_tx.clone(), + )); + // track_tx is no longer held, so the track_rx will be closed when track_changed_trigger is done + + loop { + let track = track_rx.recv().await; + if let Some(track) = track { + let rtc_track = track.rtc_track(); + let MediaStreamTrack::Audio(rtc_track) = rtc_track else { + continue; + }; + let (c_tx, c_rx) = oneshot::channel::<()>(); + let (handle_dropped_tx, handle_dropped_rx) = oneshot::channel::<()>(); + let (done_tx, mut done_rx) = oneshot::channel::<()>(); + let sample_rate = + if request.sample_rate == 0 { 48000 } else { request.sample_rate as i32 }; + + let num_channels = + if request.num_channels == 0 { 1 } else { request.num_channels as i32 }; + + let mut track_finished_rx = track_finished_tx.subscribe(); + server.async_runtime.spawn(async move { + tokio::select! { + t = track_finished_rx.recv() => { + let Ok(t) = t else { + return + }; + if t.sid() == track.sid() { + handle_dropped_tx.send(()).ok(); + return + } + } + } + }); + + server.async_runtime.spawn(async move { + Self::native_audio_stream_task( + server, + stream_handle, + NativeAudioStream::new(rtc_track, sample_rate, num_channels), + c_rx, + handle_dropped_rx, + false, + ) + .await; + let _ = done_tx.send(()); + }); + tokio::select! { + _ = &mut self_dropped_rx => { + let _ = c_tx.send(()); + return + } + _ = &mut done_rx => { + continue + } + } + } else { + // when tracks are done (i.e. the participant leaves the room), we are done + break; + } + } + if let Err(err) = server.send_event(proto::ffi_event::Message::AudioStreamEvent( + proto::AudioStreamEvent { + stream_handle: stream_handle, + message: Some(proto::audio_stream_event::Message::Eos(proto::AudioStreamEos {})), + }, + )) { + log::warn!("failed to send audio eos: {}", err); + } + } + async fn native_audio_stream_task( server: &'static server::FfiServer, stream_handle_id: FfiHandleId, mut native_stream: NativeAudioStream, mut self_dropped_rx: oneshot::Receiver<()>, mut handle_dropped_rx: oneshot::Receiver<()>, + send_eos: bool, ) { loop { tokio::select! { @@ -131,13 +263,17 @@ impl FfiAudioStream { } } } - if let Err(err) = server.send_event(proto::ffi_event::Message::AudioStreamEvent( - proto::AudioStreamEvent { - stream_handle: stream_handle_id, - message: Some(proto::audio_stream_event::Message::Eos(proto::AudioStreamEos {})), - }, - )) { - log::warn!("failed to send audio eos: {}", err); + if send_eos { + if let Err(err) = server.send_event(proto::ffi_event::Message::AudioStreamEvent( + proto::AudioStreamEvent { + stream_handle: stream_handle_id, + message: Some(proto::audio_stream_event::Message::Eos( + proto::AudioStreamEos {}, + )), + }, + )) { + log::warn!("failed to send audio eos: {}", err); + } } } } diff --git a/livekit-ffi/src/server/mod.rs b/livekit-ffi/src/server/mod.rs index 1a26fd6ae..74d00fcd3 100644 --- a/livekit-ffi/src/server/mod.rs +++ b/livekit-ffi/src/server/mod.rs @@ -40,6 +40,7 @@ pub mod colorcvt; pub mod logger; pub mod requests; pub mod room; +mod utils; pub mod video_source; pub mod video_stream; diff --git a/livekit-ffi/src/server/requests.rs b/livekit-ffi/src/server/requests.rs index a13ade6a6..705e86a1d 100644 --- a/livekit-ffi/src/server/requests.rs +++ b/livekit-ffi/src/server/requests.rs @@ -329,10 +329,18 @@ fn on_new_video_stream( server: &'static FfiServer, new_stream: proto::NewVideoStreamRequest, ) -> FfiResult { - let stream_info = video_stream::FfiVideoStream::setup(server, new_stream)?; + let stream_info = video_stream::FfiVideoStream::from_track(server, new_stream)?; Ok(proto::NewVideoStreamResponse { stream: Some(stream_info) }) } +fn on_video_stream_from_participant( + server: &'static FfiServer, + request: proto::VideoStreamFromParticipantRequest, +) -> FfiResult { + let stream_info = video_stream::FfiVideoStream::from_participant(server, request)?; + Ok(proto::VideoStreamFromParticipantResponse { stream: Some(stream_info) }) +} + /// Create a new video source, used to publish data to a track fn on_new_video_source( server: &'static FfiServer, @@ -387,10 +395,19 @@ fn on_new_audio_stream( server: &'static FfiServer, new_stream: proto::NewAudioStreamRequest, ) -> FfiResult { - let stream_info = audio_stream::FfiAudioStream::setup(server, new_stream)?; + let stream_info = audio_stream::FfiAudioStream::from_track(server, new_stream)?; Ok(proto::NewAudioStreamResponse { stream: Some(stream_info) }) } +// Create a new audio stream from a participant and track source +fn on_audio_stream_from_participant_stream( + server: &'static FfiServer, + request: proto::AudioStreamFromParticipantRequest, +) -> FfiResult { + let stream_info = audio_stream::FfiAudioStream::from_participant(server, request)?; + Ok(proto::AudioStreamFromParticipantResponse { stream: Some(stream_info) }) +} + /// Create a new audio source (used to publish audio frames to a track) fn on_new_audio_source( server: &'static FfiServer, @@ -699,6 +716,11 @@ pub fn handle_request( proto::ffi_request::Message::NewVideoStream(new_stream) => { proto::ffi_response::Message::NewVideoStream(on_new_video_stream(server, new_stream)?) } + proto::ffi_request::Message::VideoStreamFromParticipant(new_stream) => { + proto::ffi_response::Message::VideoStreamFromParticipant( + on_video_stream_from_participant(server, new_stream)?, + ) + } proto::ffi_request::Message::NewVideoSource(new_source) => { proto::ffi_response::Message::NewVideoSource(on_new_video_source(server, new_source)?) } @@ -714,6 +736,11 @@ pub fn handle_request( proto::ffi_request::Message::NewAudioSource(new_source) => { proto::ffi_response::Message::NewAudioSource(on_new_audio_source(server, new_source)?) } + proto::ffi_request::Message::AudioStreamFromParticipant(new_stream) => { + proto::ffi_response::Message::AudioStreamFromParticipant( + on_audio_stream_from_participant_stream(server, new_stream)?, + ) + } proto::ffi_request::Message::CaptureAudioFrame(push) => { proto::ffi_response::Message::CaptureAudioFrame(on_capture_audio_frame(server, push)?) } diff --git a/livekit-ffi/src/server/room.rs b/livekit-ffi/src/server/room.rs index 20a4c9b0c..cf6fe02f5 100644 --- a/livekit-ffi/src/server/room.rs +++ b/livekit-ffi/src/server/room.rs @@ -836,12 +836,6 @@ async fn forward_event( })); } RoomEvent::TrackUnsubscribed { track, publication: _, participant } => { - let track_sid = track.sid(); - if let Some(handle) = inner.track_handle_lookup.lock().remove(&track_sid) { - server.drop_handle(handle); - } else { - log::warn!("track {} was not found in the lookup table", track_sid); - } let _ = send_event(proto::room_event::Message::TrackUnsubscribed( proto::TrackUnsubscribed { participant_identity: participant.identity().to_string(), diff --git a/livekit-ffi/src/server/utils.rs b/livekit-ffi/src/server/utils.rs new file mode 100644 index 000000000..59f0e037d --- /dev/null +++ b/livekit-ffi/src/server/utils.rs @@ -0,0 +1,62 @@ +use livekit::prelude::{RoomEvent, Track, TrackSource}; +use tokio::sync::{broadcast, mpsc}; + +use super::room::FfiParticipant; +use crate::{server, FfiError, FfiHandleId}; + +pub async fn track_changed_trigger( + participant: FfiParticipant, + track_source: TrackSource, + track_tx: mpsc::Sender, + track_finished_tx: broadcast::Sender, +) { + for track_pub in participant.participant.track_publications().values() { + if track_pub.source() == track_source.into() { + if let Some(track) = track_pub.track() { + let _ = track_tx.send(track).await; + } + } + } + let room = &participant.room.room; + let mut room_event_rx = room.subscribe(); + while let Some(event) = room_event_rx.recv().await { + match event { + RoomEvent::TrackSubscribed { track, publication, participant: p } => { + if participant.participant.identity() != p.identity() { + continue; + } + if publication.source() == track_source.into() { + let _ = track_tx.send(track.into()).await; + } + } + RoomEvent::TrackUnsubscribed { track, publication, participant: p } => { + if p.identity() != participant.participant.identity() { + continue; + } + if publication.source() == track_source.into() { + let _ = track_finished_tx.send(track.into()); + } + } + RoomEvent::ParticipantDisconnected(p) => { + if p.identity() == participant.participant.identity() { + return; + } + } + RoomEvent::Disconnected { reason: _ } => { + break; + } + _ => {} + } + } +} + +pub fn ffi_participant_from_handle( + server: &'static server::FfiServer, + handle_id: FfiHandleId, +) -> Result { + let ffi_participant_handle = server.retrieve_handle::(handle_id); + if ffi_participant_handle.is_err() { + return Err(FfiError::InvalidRequest("participant not found".into())); + } + return Ok(ffi_participant_handle.unwrap().clone()); +} diff --git a/livekit-ffi/src/server/video_stream.rs b/livekit-ffi/src/server/video_stream.rs index 4512e4778..5b2d930b2 100644 --- a/livekit-ffi/src/server/video_stream.rs +++ b/livekit-ffi/src/server/video_stream.rs @@ -13,10 +13,14 @@ // limitations under the License. use futures_util::StreamExt; -use livekit::webrtc::{prelude::*, video_stream::native::NativeVideoStream}; -use tokio::sync::oneshot; +use livekit::{ + prelude::Track, + webrtc::{prelude::*, video_stream::native::NativeVideoStream}, +}; +use tokio::sync::{broadcast, mpsc, oneshot}; use super::{colorcvt, room::FfiTrack, FfiHandle}; +use crate::server::utils; use crate::{proto, server, FfiError, FfiHandleId, FfiResult}; pub struct FfiVideoStream { @@ -38,7 +42,7 @@ impl FfiVideoStream { /// /// It is possible that the client receives a VideoFrame after the task is closed. The client /// musts ignore it. - pub fn setup( + pub fn from_track( server: &'static server::FfiServer, new_stream: proto::NewVideoStreamRequest, ) -> FfiResult { @@ -64,6 +68,7 @@ impl FfiVideoStream { NativeVideoStream::new(rtc_track), self_dropped_rx, server.watch_handle_dropped(new_stream.track_handle), + true, )); server.watch_panic(handle); Ok::(video_stream) @@ -81,6 +86,39 @@ impl FfiVideoStream { }) } + pub fn from_participant( + server: &'static server::FfiServer, + request: proto::VideoStreamFromParticipantRequest, + ) -> FfiResult { + let (self_dropped_tx, self_dropped_rx) = oneshot::channel(); + let stream_type = request.r#type(); + let handle_id = server.next_id(); + let dst_type = request.format.and_then(|_| Some(request.format())); + let stream = match stream_type { + #[cfg(not(target_arch = "wasm32"))] + proto::VideoStreamType::VideoStreamNative => { + let video_stream = Self { handle_id, self_dropped_tx, stream_type }; + let handle = server.async_runtime.spawn(Self::participant_video_stream_task( + server, + request, + handle_id, + dst_type, + self_dropped_rx, + )); + server.watch_panic(handle); + Ok::(video_stream) + } + _ => return Err(FfiError::InvalidRequest("unsupported video stream type".into())), + }?; + let info = proto::VideoStreamInfo::from(&stream); + server.store_handle(stream.handle_id, stream); + + Ok(proto::OwnedVideoStream { + handle: Some(proto::FfiOwnedHandle { id: handle_id }), + info: Some(info), + }) + } + async fn native_video_stream_task( server: &'static server::FfiServer, stream_handle: FfiHandleId, @@ -89,6 +127,7 @@ impl FfiVideoStream { mut native_stream: NativeVideoStream, mut self_dropped_rx: oneshot::Receiver<()>, mut handle_dropped_rx: oneshot::Receiver<()>, + send_eos: bool, ) { loop { tokio::select! { @@ -136,6 +175,102 @@ impl FfiVideoStream { } } + if send_eos { + if let Err(err) = server.send_event(proto::ffi_event::Message::VideoStreamEvent( + proto::VideoStreamEvent { + stream_handle, + message: Some(proto::video_stream_event::Message::Eos( + proto::VideoStreamEos {}, + )), + }, + )) { + log::warn!("failed to send video EOS: {}", err); + } + } + } + + async fn participant_video_stream_task( + server: &'static server::FfiServer, + request: proto::VideoStreamFromParticipantRequest, + stream_handle: FfiHandleId, + dst_type: Option, + mut close_rx: oneshot::Receiver<()>, + ) { + let ffi_participant = + utils::ffi_participant_from_handle(server, request.participant_handle); + let ffi_participant = match ffi_participant { + Ok(ffi_participant) => ffi_participant, + Err(err) => { + log::error!("failed to get participant: {}", err); + return; + } + }; + + let track_source = request.track_source(); + let (track_tx, mut track_rx) = mpsc::channel::(1); + let (track_finished_tx, track_finished_rx) = broadcast::channel::(1); + server.async_runtime.spawn(utils::track_changed_trigger( + ffi_participant, + track_source.into(), + track_tx, + track_finished_tx.clone(), + )); + // track_tx is no longer held, so the track_rx will be closed when track_changed_trigger is done + + loop { + let track = track_rx.recv().await; + if let Some(track) = track { + let rtc_track = track.rtc_track(); + let MediaStreamTrack::Video(rtc_track) = rtc_track else { + continue; + }; + let (c_tx, c_rx) = oneshot::channel::<()>(); + let (handle_dropped_tx, handle_dropped_rx) = oneshot::channel::<()>(); + let (done_tx, mut done_rx) = oneshot::channel::<()>(); + + let mut track_finished_rx = track_finished_tx.subscribe(); + server.async_runtime.spawn(async move { + tokio::select! { + t = track_finished_rx.recv() => { + let Ok(t) = t else { + return + }; + if t.sid() == track.sid() { + handle_dropped_tx.send(()).ok(); + return + } + } + } + }); + + server.async_runtime.spawn(async move { + Self::native_video_stream_task( + server, + stream_handle, + dst_type, + request.normalize_stride, + NativeVideoStream::new(rtc_track), + c_rx, + handle_dropped_rx, + false, + ) + .await; + let _ = done_tx.send(()); + }); + tokio::select! { + _ = &mut close_rx => { + let _ = c_tx.send(()); + return + } + _ = &mut done_rx => { + continue + } + } + } else { + // when tracks are done (i.e. the participant leaves the room), we are done + break; + } + } if let Err(err) = server.send_event(proto::ffi_event::Message::VideoStreamEvent( proto::VideoStreamEvent { stream_handle, From 170bb3a2a9cd8b09e3f81b835fdc2a966e9b9b07 Mon Sep 17 00:00:00 2001 From: Neil Dwyer Date: Thu, 5 Sep 2024 15:58:03 -0700 Subject: [PATCH 029/274] New releases (#426) --- Cargo.lock | 10 +++++----- livekit-ffi/Cargo.toml | 4 ++-- livekit/Cargo.toml | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 907ca7ae0..164e723bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1490,7 +1490,7 @@ dependencies = [ [[package]] name = "libwebrtc" -version = "0.3.6" +version = "0.3.7" dependencies = [ "cxx", "env_logger", @@ -1546,7 +1546,7 @@ checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "livekit" -version = "0.5.2" +version = "0.6.0" dependencies = [ "futures-util", "lazy_static", @@ -1591,7 +1591,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.8.2" +version = "0.9.0" dependencies = [ "console-subscriber", "dashmap", @@ -3234,7 +3234,7 @@ checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" [[package]] name = "webrtc-sys" -version = "0.3.4" +version = "0.3.5" dependencies = [ "cc", "cxx", @@ -3247,7 +3247,7 @@ dependencies = [ [[package]] name = "webrtc-sys-build" -version = "0.3.4" +version = "0.3.5" dependencies = [ "fs2", "regex", diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 5d8080836..b9cf4babd 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.8.3" +version = "0.9.0" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" @@ -18,7 +18,7 @@ __rustls-tls = ["livekit/__rustls-tls"] tracing = ["tokio/tracing", "console-subscriber"] [dependencies] -livekit = { path = "../livekit", version = "0.5.0" } +livekit = { path = "../livekit", version = "0.6.0" } livekit-protocol = { path = "../livekit-protocol", version = "0.3.5" } tokio = { version = "1", features = ["full", "parking_lot"] } futures-util = { version = "0.3", default-features = false, features = ["sink"] } diff --git a/livekit/Cargo.toml b/livekit/Cargo.toml index e57548b5a..be31f592d 100644 --- a/livekit/Cargo.toml +++ b/livekit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit" -version = "0.5.3" +version = "0.6.0" edition = "2021" license = "Apache-2.0" description = "Rust Client SDK for LiveKit" From 97880545689c014574741d92ac416fcbb702ee69 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Thu, 5 Sep 2024 23:54:28 -0700 Subject: [PATCH 030/274] Increase default audio bitrates (#427) Users prefer FullBand audio by default. In most cases this gives a clear boost in both voice and audio quality. --- livekit/src/room/options.rs | 10 +++++----- livekit/src/room/participant/local_participant.rs | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/livekit/src/room/options.rs b/livekit/src/room/options.rs index 3d808d29d..76577e14c 100644 --- a/livekit/src/room/options.rs +++ b/livekit/src/room/options.rs @@ -307,11 +307,11 @@ pub mod audio { use super::AudioPreset; pub const TELEPHONE: AudioPreset = AudioPreset::new(12_000); - pub const SPEECH: AudioPreset = AudioPreset::new(20_000); - pub const MUSIC: AudioPreset = AudioPreset::new(32_000); - pub const MUSIC_STEREO: AudioPreset = AudioPreset::new(48_000); - pub const MUSIC_HIGH_QUALITY: AudioPreset = AudioPreset::new(64_000); - pub const MUSIC_HIGH_QUALITY_STEREO: AudioPreset = AudioPreset::new(96_000); + pub const SPEECH: AudioPreset = AudioPreset::new(24_000); + pub const MUSIC: AudioPreset = AudioPreset::new(48_000); + pub const MUSIC_STEREO: AudioPreset = AudioPreset::new(64_000); + pub const MUSIC_HIGH_QUALITY: AudioPreset = AudioPreset::new(96_000); + pub const MUSIC_HIGH_QUALITY_STEREO: AudioPreset = AudioPreset::new(128_000); pub const PRESETS: &[AudioPreset] = &[TELEPHONE, SPEECH, MUSIC, MUSIC_STEREO, MUSIC_HIGH_QUALITY, MUSIC_HIGH_QUALITY_STEREO]; diff --git a/livekit/src/room/participant/local_participant.rs b/livekit/src/room/participant/local_participant.rs index 2706d48df..b9dde45d1 100644 --- a/livekit/src/room/participant/local_participant.rs +++ b/livekit/src/room/participant/local_participant.rs @@ -210,7 +210,7 @@ impl LocalParticipant { LocalTrack::Audio(_audio_track) => { // Setup audio encoding let audio_encoding = - options.audio_encoding.as_ref().unwrap_or(&options::audio::SPEECH.encoding); + options.audio_encoding.as_ref().unwrap_or(&options::audio::MUSIC.encoding); encodings.push(RtpEncodingParameters { max_bitrate: Some(audio_encoding.max_bitrate), From 5acfc303dd7078efb1f8ffa7860f6c15f51518d0 Mon Sep 17 00:00:00 2001 From: Neil Dwyer Date: Fri, 6 Sep 2024 10:46:44 -0700 Subject: [PATCH 031/274] Introduce participant kind (#428) --- livekit-ffi/protocol/participant.proto | 9 ++++++ livekit-ffi/src/conversion/participant.rs | 14 +++++++++ livekit-ffi/src/livekit.proto.rs | 37 +++++++++++++++++++++++ livekit/src/room/mod.rs | 2 +- livekit/src/room/participant/mod.rs | 1 + 5 files changed, 62 insertions(+), 1 deletion(-) diff --git a/livekit-ffi/protocol/participant.proto b/livekit-ffi/protocol/participant.proto index 2b8be7a86..a089d86e8 100644 --- a/livekit-ffi/protocol/participant.proto +++ b/livekit-ffi/protocol/participant.proto @@ -25,9 +25,18 @@ message ParticipantInfo { string identity = 3; string metadata = 4; map attributes = 5; + ParticipantKind kind = 6; } message OwnedParticipant { FfiOwnedHandle handle = 1; ParticipantInfo info = 2; +} + +enum ParticipantKind { + PARTICIPANT_KIND_STANDARD = 0; + PARTICIPANT_KIND_INGRESS = 1; + PARTICIPANT_KIND_EGRESS = 2; + PARTICIPANT_KIND_SIP = 3; + PARTICIPANT_KIND_AGENT = 4; } \ No newline at end of file diff --git a/livekit-ffi/src/conversion/participant.rs b/livekit-ffi/src/conversion/participant.rs index 4b1c04b86..f8041f8d7 100644 --- a/livekit-ffi/src/conversion/participant.rs +++ b/livekit-ffi/src/conversion/participant.rs @@ -13,6 +13,7 @@ // limitations under the License. use crate::{proto, server::room::FfiParticipant}; +use livekit::ParticipantKind; impl From<&FfiParticipant> for proto::ParticipantInfo { fn from(value: &FfiParticipant) -> Self { @@ -23,6 +24,19 @@ impl From<&FfiParticipant> for proto::ParticipantInfo { identity: participant.identity().into(), metadata: participant.metadata(), attributes: participant.attributes(), + kind: proto::ParticipantKind::from(participant.kind()).into(), + } + } +} + +impl From for proto::ParticipantKind { + fn from(kind: ParticipantKind) -> Self { + match kind { + ParticipantKind::Standard => proto::ParticipantKind::Standard, + ParticipantKind::Sip => proto::ParticipantKind::Sip, + ParticipantKind::Ingress => proto::ParticipantKind::Ingress, + ParticipantKind::Egress => proto::ParticipantKind::Egress, + ParticipantKind::Agent => proto::ParticipantKind::Agent, } } } diff --git a/livekit-ffi/src/livekit.proto.rs b/livekit-ffi/src/livekit.proto.rs index 38351d72a..18d3eb385 100644 --- a/livekit-ffi/src/livekit.proto.rs +++ b/livekit-ffi/src/livekit.proto.rs @@ -1542,6 +1542,8 @@ pub struct ParticipantInfo { pub metadata: ::prost::alloc::string::String, #[prost(map="string, string", tag="5")] pub attributes: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + #[prost(enumeration="ParticipantKind", tag="6")] + pub kind: i32, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -1551,6 +1553,41 @@ pub struct OwnedParticipant { #[prost(message, optional, tag="2")] pub info: ::core::option::Option, } +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum ParticipantKind { + Standard = 0, + Ingress = 1, + Egress = 2, + Sip = 3, + Agent = 4, +} +impl ParticipantKind { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + ParticipantKind::Standard => "PARTICIPANT_KIND_STANDARD", + ParticipantKind::Ingress => "PARTICIPANT_KIND_INGRESS", + ParticipantKind::Egress => "PARTICIPANT_KIND_EGRESS", + ParticipantKind::Sip => "PARTICIPANT_KIND_SIP", + ParticipantKind::Agent => "PARTICIPANT_KIND_AGENT", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "PARTICIPANT_KIND_STANDARD" => Some(Self::Standard), + "PARTICIPANT_KIND_INGRESS" => Some(Self::Ingress), + "PARTICIPANT_KIND_EGRESS" => Some(Self::Egress), + "PARTICIPANT_KIND_SIP" => Some(Self::Sip), + "PARTICIPANT_KIND_AGENT" => Some(Self::Agent), + _ => None, + } + } +} /// Create a new VideoStream /// VideoStream is used to receive video frames from a track #[allow(clippy::derive_partial_eq_without_eq)] diff --git a/livekit/src/room/mod.rs b/livekit/src/room/mod.rs index a9bcb24d0..9b3e1bdc5 100644 --- a/livekit/src/room/mod.rs +++ b/livekit/src/room/mod.rs @@ -33,7 +33,7 @@ use proto::{promise::Promise, SignalTarget}; use thiserror::Error; use tokio::sync::{mpsc, oneshot, Mutex as AsyncMutex}; -use self::{ +pub use self::{ e2ee::{manager::E2eeManager, E2eeOptions}, participant::ParticipantKind, }; diff --git a/livekit/src/room/participant/mod.rs b/livekit/src/room/participant/mod.rs index d423ea295..c535111cf 100644 --- a/livekit/src/room/participant/mod.rs +++ b/livekit/src/room/participant/mod.rs @@ -61,6 +61,7 @@ impl Participant { pub fn is_speaking(self: &Self) -> bool; pub fn audio_level(self: &Self) -> f32; pub fn connection_quality(self: &Self) -> ConnectionQuality; + pub fn kind(self: &Self) -> ParticipantKind; pub(crate) fn update_info(self: &Self, info: proto::ParticipantInfo) -> (); From 346cc22618d30296192e936f26c2c968aab2ec80 Mon Sep 17 00:00:00 2001 From: Neil Dwyer Date: Fri, 6 Sep 2024 10:49:40 -0700 Subject: [PATCH 032/274] Bump ffi version (#429) --- Cargo.lock | 2 +- livekit-ffi/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 164e723bd..f81128d0c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1591,7 +1591,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.9.0" +version = "0.9.1" dependencies = [ "console-subscriber", "dashmap", diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index b9cf4babd..f090a9163 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.9.0" +version = "0.9.1" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" From 09f7da15bab0eaf54fdc426646c8ada7471f35b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Thu, 12 Sep 2024 12:13:47 -0700 Subject: [PATCH 033/274] optimize AudioSource captures (#425) --- examples/Cargo.lock | 14 +- examples/wgpu_room/src/sine_track.rs | 32 +--- libwebrtc/src/audio_source.rs | 12 +- libwebrtc/src/native/audio_source.rs | 159 ++++++------------ livekit-ffi/protocol/audio_frame.proto | 7 +- livekit-ffi/protocol/ffi.proto | 18 +- livekit-ffi/src/livekit.proto.rs | 35 ++-- livekit-ffi/src/server/audio_source.rs | 10 +- livekit-ffi/src/server/requests.rs | 13 ++ webrtc-sys/build.rs | 1 + webrtc-sys/include/livekit/audio_track.h | 69 ++++++-- .../include/livekit/global_task_queue.h | 9 + .../include/livekit/peer_connection_factory.h | 2 + webrtc-sys/src/audio_track.cpp | 153 ++++++++++++++--- webrtc-sys/src/audio_track.rs | 32 +++- webrtc-sys/src/global_task_queue.cpp | 14 ++ webrtc-sys/src/peer_connection_factory.cpp | 5 + webrtc-sys/src/peer_connection_factory.rs | 1 + 18 files changed, 370 insertions(+), 216 deletions(-) create mode 100644 webrtc-sys/include/livekit/global_task_queue.h create mode 100644 webrtc-sys/src/global_task_queue.cpp diff --git a/examples/Cargo.lock b/examples/Cargo.lock index 21ec559f1..94d6b303f 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -2108,7 +2108,7 @@ dependencies = [ [[package]] name = "libwebrtc" -version = "0.3.2" +version = "0.3.7" dependencies = [ "cxx", "jni", @@ -2122,7 +2122,6 @@ dependencies = [ "serde_json", "thiserror", "tokio", - "tokio-stream", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -2152,7 +2151,7 @@ checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "livekit" -version = "0.3.2" +version = "0.6.0" dependencies = [ "futures-util", "lazy_static", @@ -2171,7 +2170,7 @@ dependencies = [ [[package]] name = "livekit-api" -version = "0.3.2" +version = "0.4.0" dependencies = [ "async-tungstenite", "base64", @@ -2196,7 +2195,7 @@ dependencies = [ [[package]] name = "livekit-protocol" -version = "0.3.2" +version = "0.3.5" dependencies = [ "futures-util", "livekit-runtime", @@ -2215,6 +2214,7 @@ name = "livekit-runtime" version = "0.3.0" dependencies = [ "tokio", + "tokio-stream", ] [[package]] @@ -4455,7 +4455,7 @@ checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" [[package]] name = "webrtc-sys" -version = "0.3.2" +version = "0.3.5" dependencies = [ "cc", "cxx", @@ -4467,7 +4467,7 @@ dependencies = [ [[package]] name = "webrtc-sys-build" -version = "0.3.2" +version = "0.3.5" dependencies = [ "fs2", "regex", diff --git a/examples/wgpu_room/src/sine_track.rs b/examples/wgpu_room/src/sine_track.rs index becaf8217..1825bb8a7 100644 --- a/examples/wgpu_room/src/sine_track.rs +++ b/examples/wgpu_room/src/sine_track.rs @@ -17,12 +17,7 @@ pub struct SineParameters { impl Default for SineParameters { fn default() -> Self { - Self { - sample_rate: 48000, - freq: 440.0, - amplitude: 1.0, - num_channels: 2, - } + Self { sample_rate: 48000, freq: 440.0, amplitude: 1.0, num_channels: 2 } } } @@ -46,7 +41,7 @@ impl SineTrack { AudioSourceOptions::default(), params.sample_rate, params.num_channels, - None, + 1000, ), params, room, @@ -65,28 +60,18 @@ impl SineTrack { RtcAudioSource::Native(self.rtc_source.clone()), ); - let task = tokio::spawn(Self::track_task( - close_rx, - self.rtc_source.clone(), - self.params.clone(), - )); + let task = + tokio::spawn(Self::track_task(close_rx, self.rtc_source.clone(), self.params.clone())); self.room .local_participant() .publish_track( LocalTrack::Audio(track.clone()), - TrackPublishOptions { - source: TrackSource::Microphone, - ..Default::default() - }, + TrackPublishOptions { source: TrackSource::Microphone, ..Default::default() }, ) .await?; - let handle = TrackHandle { - close_tx, - track, - task, - }; + let handle = TrackHandle { close_tx, track, task }; self.handle = Some(handle); Ok(()) @@ -96,10 +81,7 @@ impl SineTrack { if let Some(handle) = self.handle.take() { handle.close_tx.send(()).ok(); handle.task.await.ok(); - self.room - .local_participant() - .unpublish_track(&handle.track.sid()) - .await?; + self.room.local_participant().unpublish_track(&handle.track.sid()).await?; } Ok(()) diff --git a/libwebrtc/src/audio_source.rs b/libwebrtc/src/audio_source.rs index b430f4fb0..1df5c4878 100644 --- a/libwebrtc/src/audio_source.rs +++ b/libwebrtc/src/audio_source.rs @@ -63,18 +63,22 @@ pub mod native { options: AudioSourceOptions, sample_rate: u32, num_channels: u32, - enable_queue: Option, + queue_size_ms: u32, ) -> NativeAudioSource { Self { handle: imp_as::NativeAudioSource::new( options, sample_rate, num_channels, - enable_queue, + queue_size_ms, ), } } + pub fn clear_buffer(&self) { + self.handle.clear_buffer() + } + pub async fn capture_frame(&self, frame: &AudioFrame<'_>) -> Result<(), RtcError> { self.handle.capture_frame(frame).await } @@ -94,9 +98,5 @@ pub mod native { pub fn num_channels(&self) -> u32 { self.handle.num_channels() } - - pub fn enable_queue(&self) -> bool { - self.handle.enable_queue() - } } } diff --git a/libwebrtc/src/native/audio_source.rs b/libwebrtc/src/native/audio_source.rs index 273853b10..c7652c676 100644 --- a/libwebrtc/src/native/audio_source.rs +++ b/libwebrtc/src/native/audio_source.rs @@ -12,39 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::{sync::Arc, time::Duration}; - use cxx::SharedPtr; -use livekit_runtime::interval; -use tokio::sync::{ - mpsc::{self, error::TryRecvError}, - Mutex as AsyncMutex, -}; +use tokio::sync::oneshot; use webrtc_sys::audio_track as sys_at; use crate::{audio_frame::AudioFrame, audio_source::AudioSourceOptions, RtcError, RtcErrorType}; -const BUFFER_SIZE_MS: usize = 50; - #[derive(Clone)] pub struct NativeAudioSource { sys_handle: SharedPtr, - inner: Arc>, sample_rate: u32, num_channels: u32, - samples_10ms: usize, - // whether to queue audio frames or send them immediately - // defaults to true - enable_queue: bool, - po_tx: mpsc::Sender>, -} - -struct AudioSourceInner { - buf: Box<[i16]>, - - // Amount of data from the previous frame that hasn't been sent to the libwebrtc source - // (because it requires 10ms of data) - len: usize, + queue_size_samples: u32, } impl NativeAudioSource { @@ -52,66 +31,24 @@ impl NativeAudioSource { options: AudioSourceOptions, sample_rate: u32, num_channels: u32, - enable_queue: Option, + queue_size_ms: u32, ) -> NativeAudioSource { - let samples_10ms = (sample_rate / 100 * num_channels) as usize; - let (po_tx, mut po_rx) = mpsc::channel(BUFFER_SIZE_MS / 10); - - let source = Self { - sys_handle: sys_at::ffi::new_audio_track_source(options.into()), - inner: Arc::new(AsyncMutex::new(AudioSourceInner { - buf: vec![0; samples_10ms].into_boxed_slice(), - len: 0, - })), - sample_rate, - num_channels, - samples_10ms, - enable_queue: enable_queue.unwrap_or(true), - po_tx, - }; - - livekit_runtime::spawn({ - let source = source.clone(); - async move { - let mut interval = interval(Duration::from_millis(10)); - interval.set_missed_tick_behavior(livekit_runtime::MissedTickBehavior::Delay); - let blank_data = vec![0; samples_10ms]; - let enable_queue = source.enable_queue; - - loop { - if enable_queue { - interval.tick().await; - } - - let frame = po_rx.try_recv(); - if let Err(TryRecvError::Disconnected) = frame { - break; - } - - if let Err(TryRecvError::Empty) = frame { - if enable_queue { - source.sys_handle.on_captured_frame( - &blank_data, - sample_rate, - num_channels, - blank_data.len() / num_channels as usize, - ); - } - continue; - } - - let frame = frame.unwrap(); - source.sys_handle.on_captured_frame( - &frame, - sample_rate, - num_channels, - frame.len() / num_channels as usize, - ); - } - } - }); - - source + assert!(queue_size_ms % 10 == 0, "queue_size_ms must be a multiple of 10"); + + print!( + "new audio source {} {} {} {}", + sample_rate, num_channels, queue_size_ms, options.echo_cancellation + ); + + let sys_handle = sys_at::ffi::new_audio_track_source( + options.into(), + sample_rate.try_into().unwrap(), + num_channels.try_into().unwrap(), + queue_size_ms.try_into().unwrap(), + ); + + let queue_size_samples = (queue_size_ms * sample_rate / 1000) * num_channels; + Self { sys_handle, sample_rate, num_channels, queue_size_samples } } pub fn sys_handle(&self) -> SharedPtr { @@ -134,8 +71,8 @@ impl NativeAudioSource { self.num_channels } - pub fn enable_queue(&self) -> bool { - self.enable_queue + pub fn clear_buffer(&self) { + self.sys_handle.clear_buffer(); } pub async fn capture_frame(&self, frame: &AudioFrame<'_>) -> Result<(), RtcError> { @@ -146,38 +83,36 @@ impl NativeAudioSource { }); } - let mut inner = self.inner.lock().await; - let mut samples = 0; - // split frames into 10ms chunks - loop { - let remaining_samples = frame.data.len() - samples; - if remaining_samples == 0 { - break; - } + extern "C" fn lk_audio_source_complete(userdata: *const sys_at::SourceContext) { + let tx = unsafe { Box::from_raw(userdata as *mut oneshot::Sender<()>) }; + let _ = tx.send(()); + } - if (inner.len != 0 && remaining_samples > 0) || remaining_samples < self.samples_10ms { - let missing_len = self.samples_10ms - inner.len; - let to_add = missing_len.min(remaining_samples); - let start = inner.len; - inner.buf[start..start + to_add] - .copy_from_slice(&frame.data[samples..samples + to_add]); - inner.len += to_add; - samples += to_add; - - if inner.len == self.samples_10ms { - let data = inner.buf.clone().to_vec(); - let _ = self.po_tx.send(data).await; - inner.len = 0; + // iterate over chunks of self._queue_size_samples + for chunk in frame.data.chunks(self.queue_size_samples as usize) { + let nb_frames = chunk.len() / self.num_channels as usize; + let (tx, rx) = oneshot::channel::<()>(); + let ctx = Box::new(tx); + let ctx_ptr = Box::into_raw(ctx) as *const sys_at::SourceContext; + + unsafe { + if !self.sys_handle.capture_frame( + chunk, + self.sample_rate, + self.num_channels, + nb_frames, + ctx_ptr, + sys_at::CompleteCallback(lk_audio_source_complete), + ) { + return Err(RtcError { + error_type: RtcErrorType::InvalidState, + message: "failed to capture frame".to_owned(), + }); } - continue; } - if remaining_samples >= self.samples_10ms { - // TODO(theomonnom): avoid copying - let data = frame.data[samples..samples + self.samples_10ms].to_vec(); - let _ = self.po_tx.send(data).await; - samples += self.samples_10ms; - } + let _ = rx.await; + println!("captured frame"); } Ok(()) diff --git a/livekit-ffi/protocol/audio_frame.proto b/livekit-ffi/protocol/audio_frame.proto index abe304ff5..8307d265d 100644 --- a/livekit-ffi/protocol/audio_frame.proto +++ b/livekit-ffi/protocol/audio_frame.proto @@ -46,7 +46,7 @@ message NewAudioSourceRequest { optional AudioSourceOptions options = 2; uint32 sample_rate = 3; uint32 num_channels = 4; - optional bool enable_queue = 5; + uint32 queue_size_ms = 5; } message NewAudioSourceResponse { OwnedAudioSource source = 1; } @@ -64,6 +64,11 @@ message CaptureAudioFrameCallback { optional string error = 2; } +message ClearAudioBufferRequest { + uint64 source_handle = 1; +} +message ClearAudioBufferResponse {} + // Create a new AudioResampler message NewAudioResamplerRequest {} message NewAudioResamplerResponse { diff --git a/livekit-ffi/protocol/ffi.proto b/livekit-ffi/protocol/ffi.proto index ba87d8417..173d36286 100644 --- a/livekit-ffi/protocol/ffi.proto +++ b/livekit-ffi/protocol/ffi.proto @@ -88,10 +88,11 @@ message FfiRequest { NewAudioStreamRequest new_audio_stream = 25; NewAudioSourceRequest new_audio_source = 26; CaptureAudioFrameRequest capture_audio_frame = 27; - NewAudioResamplerRequest new_audio_resampler = 28; - RemixAndResampleRequest remix_and_resample = 29; - E2eeRequest e2ee = 30; - AudioStreamFromParticipantRequest audio_stream_from_participant = 31; + ClearAudioBufferRequest clear_audio_buffer = 28; + NewAudioResamplerRequest new_audio_resampler = 29; + RemixAndResampleRequest remix_and_resample = 30; + E2eeRequest e2ee = 31; + AudioStreamFromParticipantRequest audio_stream_from_participant = 32; } } @@ -132,10 +133,11 @@ message FfiResponse { NewAudioStreamResponse new_audio_stream = 25; NewAudioSourceResponse new_audio_source = 26; CaptureAudioFrameResponse capture_audio_frame = 27; - NewAudioResamplerResponse new_audio_resampler = 28; - RemixAndResampleResponse remix_and_resample = 29; - AudioStreamFromParticipantResponse audio_stream_from_participant = 30; - E2eeResponse e2ee = 31; + ClearAudioBufferResponse clear_audio_buffer = 28; + NewAudioResamplerResponse new_audio_resampler = 29; + RemixAndResampleResponse remix_and_resample = 30; + AudioStreamFromParticipantResponse audio_stream_from_participant = 31; + E2eeResponse e2ee = 32; } } diff --git a/livekit-ffi/src/livekit.proto.rs b/livekit-ffi/src/livekit.proto.rs index 18d3eb385..1cfce9572 100644 --- a/livekit-ffi/src/livekit.proto.rs +++ b/livekit-ffi/src/livekit.proto.rs @@ -1,5 +1,4 @@ // @generated -// This file is @generated by prost-build. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FrameCryptor { @@ -2982,8 +2981,8 @@ pub struct NewAudioSourceRequest { pub sample_rate: u32, #[prost(uint32, tag="4")] pub num_channels: u32, - #[prost(bool, optional, tag="5")] - pub enable_queue: ::core::option::Option, + #[prost(uint32, tag="5")] + pub queue_size_ms: u32, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -3015,6 +3014,16 @@ pub struct CaptureAudioFrameCallback { #[prost(string, optional, tag="2")] pub error: ::core::option::Option<::prost::alloc::string::String>, } +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ClearAudioBufferRequest { + #[prost(uint64, tag="1")] + pub source_handle: u64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ClearAudioBufferResponse { +} /// Create a new AudioResampler #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -3241,7 +3250,7 @@ impl AudioSourceType { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiRequest { - #[prost(oneof="ffi_request::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31")] + #[prost(oneof="ffi_request::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32")] pub message: ::core::option::Option, } /// Nested message and enum types in `FfiRequest`. @@ -3306,12 +3315,14 @@ pub mod ffi_request { #[prost(message, tag="27")] CaptureAudioFrame(super::CaptureAudioFrameRequest), #[prost(message, tag="28")] - NewAudioResampler(super::NewAudioResamplerRequest), + ClearAudioBuffer(super::ClearAudioBufferRequest), #[prost(message, tag="29")] - RemixAndResample(super::RemixAndResampleRequest), + NewAudioResampler(super::NewAudioResamplerRequest), #[prost(message, tag="30")] - E2ee(super::E2eeRequest), + RemixAndResample(super::RemixAndResampleRequest), #[prost(message, tag="31")] + E2ee(super::E2eeRequest), + #[prost(message, tag="32")] AudioStreamFromParticipant(super::AudioStreamFromParticipantRequest), } } @@ -3319,7 +3330,7 @@ pub mod ffi_request { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiResponse { - #[prost(oneof="ffi_response::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31")] + #[prost(oneof="ffi_response::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32")] pub message: ::core::option::Option, } /// Nested message and enum types in `FfiResponse`. @@ -3384,12 +3395,14 @@ pub mod ffi_response { #[prost(message, tag="27")] CaptureAudioFrame(super::CaptureAudioFrameResponse), #[prost(message, tag="28")] - NewAudioResampler(super::NewAudioResamplerResponse), + ClearAudioBuffer(super::ClearAudioBufferResponse), #[prost(message, tag="29")] - RemixAndResample(super::RemixAndResampleResponse), + NewAudioResampler(super::NewAudioResamplerResponse), #[prost(message, tag="30")] - AudioStreamFromParticipant(super::AudioStreamFromParticipantResponse), + RemixAndResample(super::RemixAndResampleResponse), #[prost(message, tag="31")] + AudioStreamFromParticipant(super::AudioStreamFromParticipantResponse), + #[prost(message, tag="32")] E2ee(super::E2eeResponse), } } diff --git a/livekit-ffi/src/server/audio_source.rs b/livekit-ffi/src/server/audio_source.rs index ba0f21f7a..e07acee8e 100644 --- a/livekit-ffi/src/server/audio_source.rs +++ b/livekit-ffi/src/server/audio_source.rs @@ -43,7 +43,7 @@ impl FfiAudioSource { new_source.options.map(Into::into).unwrap_or_default(), new_source.sample_rate, new_source.num_channels, - new_source.enable_queue, + new_source.queue_size_ms, ); RtcAudioSource::Native(audio_source) } @@ -62,6 +62,14 @@ impl FfiAudioSource { }) } + pub fn clear_buffer(&self) { + match self.source { + #[cfg(not(target_arch = "wasm32"))] + RtcAudioSource::Native(ref source) => source.clear_buffer(), + _ => {} + } + } + pub fn capture_frame( &self, server: &'static server::FfiServer, diff --git a/livekit-ffi/src/server/requests.rs b/livekit-ffi/src/server/requests.rs index 705e86a1d..89e395769 100644 --- a/livekit-ffi/src/server/requests.rs +++ b/livekit-ffi/src/server/requests.rs @@ -426,6 +426,16 @@ fn on_capture_audio_frame( source.capture_frame(server, push) } +// Clear the internal audio buffer (cancel all pending frames from being played) +fn on_clear_audio_buffer( + server: &'static FfiServer, + clear: proto::ClearAudioBufferRequest, +) -> FfiResult { + let source = server.retrieve_handle::(clear.source_handle)?; + source.clear_buffer(); + Ok(proto::ClearAudioBufferResponse {}) +} + /// Create a new audio resampler fn new_audio_resampler( server: &'static FfiServer, @@ -744,6 +754,9 @@ pub fn handle_request( proto::ffi_request::Message::CaptureAudioFrame(push) => { proto::ffi_response::Message::CaptureAudioFrame(on_capture_audio_frame(server, push)?) } + proto::ffi_request::Message::ClearAudioBuffer(clear) => { + proto::ffi_response::Message::ClearAudioBuffer(on_clear_audio_buffer(server, clear)?) + } proto::ffi_request::Message::NewAudioResampler(new_res) => { proto::ffi_response::Message::NewAudioResampler(new_audio_resampler(server, new_res)?) } diff --git a/webrtc-sys/build.rs b/webrtc-sys/build.rs index beabe57c7..5e87b0c9c 100644 --- a/webrtc-sys/build.rs +++ b/webrtc-sys/build.rs @@ -72,6 +72,7 @@ fn main() { "src/audio_device.cpp", "src/audio_resampler.cpp", "src/frame_cryptor.cpp", + "src/global_task_queue.cpp", ]); let webrtc_dir = webrtc_sys_build::webrtc_dir(); diff --git a/webrtc-sys/include/livekit/audio_track.h b/webrtc-sys/include/livekit/audio_track.h index 3e074785b..029158fd5 100644 --- a/webrtc-sys/include/livekit/audio_track.h +++ b/webrtc-sys/include/livekit/audio_track.h @@ -20,18 +20,25 @@ #include "api/audio/audio_frame.h" #include "api/audio_options.h" +#include "api/task_queue/task_queue_factory.h" #include "common_audio/resampler/include/push_resampler.h" #include "livekit/helper.h" #include "livekit/media_stream_track.h" #include "livekit/webrtc.h" #include "pc/local_audio_source.h" #include "rtc_base/synchronization/mutex.h" +#include "rtc_base/task_queue.h" +#include "rtc_base/task_utils/repeating_task.h" +#include "rtc_base/thread_annotations.h" #include "rust/cxx.h" namespace livekit { class AudioTrack; class NativeAudioSink; class AudioTrackSource; +class SourceContext; + +using CompleteCallback = void (*)(const livekit::SourceContext*); } // namespace livekit #include "webrtc-sys/src/audio_track.rs.h" @@ -91,7 +98,11 @@ std::shared_ptr new_native_audio_sink( class AudioTrackSource { class InternalSource : public webrtc::LocalAudioSource { public: - InternalSource(const cricket::AudioOptions& options); + InternalSource(const cricket::AudioOptions& options, + int sample_rate, + int num_channels, + int buffer_size_ms, + webrtc::TaskQueueFactory* task_queue_factory); SourceState state() const override; bool remote() const override; @@ -103,30 +114,53 @@ class AudioTrackSource { void set_options(const cricket::AudioOptions& options); - // AudioFrame should always contain 10 ms worth of data (see index.md of - // acm) - void on_captured_frame(rust::Slice audio_data, - uint32_t sample_rate, - uint32_t number_of_channels, - size_t number_of_frames); + bool capture_frame(rust::Slice audio_data, + uint32_t sample_rate, + uint32_t number_of_channels, + size_t number_of_frames, + const SourceContext* ctx, + void (*on_complete)(const SourceContext*)); + + void clear_buffer(); private: mutable webrtc::Mutex mutex_; - std::vector sinks_; + std::unique_ptr audio_queue_; + webrtc::RepeatingTaskHandle audio_task_; + + std::vector sinks_ RTC_GUARDED_BY(mutex_); + std::vector buffer_ RTC_GUARDED_BY(mutex_); + + const SourceContext* capture_userdata_ RTC_GUARDED_BY(mutex_); + void (*on_complete_)(const SourceContext*) RTC_GUARDED_BY(mutex_); + + int sample_rate_; + int num_channels_; + int queue_size_samples_; + int notify_threshold_samples_; + cricket::AudioOptions options_{}; }; public: - AudioTrackSource(AudioSourceOptions options); + AudioTrackSource(AudioSourceOptions options, + int sample_rate, + int num_channels, + int queue_size_ms, + webrtc::TaskQueueFactory* task_queue_factory); AudioSourceOptions audio_options() const; void set_audio_options(const AudioSourceOptions& options) const; - void on_captured_frame(rust::Slice audio_data, - uint32_t sample_rate, - uint32_t number_of_channels, - size_t number_of_frames) const; + bool capture_frame(rust::Slice audio_data, + uint32_t sample_rate, + uint32_t number_of_channels, + size_t number_of_frames, + const SourceContext* ctx, + CompleteCallback on_complete) const; + + void clear_buffer() const; rtc::scoped_refptr get() const; @@ -135,7 +169,10 @@ class AudioTrackSource { }; std::shared_ptr new_audio_track_source( - AudioSourceOptions options); + AudioSourceOptions options, + int sample_rate, + int num_channels, + int queue_size_ms); static std::shared_ptr audio_to_media( std::shared_ptr track) { @@ -151,4 +188,8 @@ static std::shared_ptr _shared_audio_track() { return nullptr; // Ignore } +static std::shared_ptr _shared_audio_track_source() { + return nullptr; // Ignore +} + } // namespace livekit diff --git a/webrtc-sys/include/livekit/global_task_queue.h b/webrtc-sys/include/livekit/global_task_queue.h new file mode 100644 index 000000000..1cf9a7ac1 --- /dev/null +++ b/webrtc-sys/include/livekit/global_task_queue.h @@ -0,0 +1,9 @@ +#pragma once + +#include "api/task_queue/task_queue_factory.h" + +namespace livekit { + +webrtc::TaskQueueFactory* GetGlobalTaskQueueFactory(); + +} // namespace livekit diff --git a/webrtc-sys/include/livekit/peer_connection_factory.h b/webrtc-sys/include/livekit/peer_connection_factory.h index 7d22c4e81..ae49842ba 100644 --- a/webrtc-sys/include/livekit/peer_connection_factory.h +++ b/webrtc-sys/include/livekit/peer_connection_factory.h @@ -18,6 +18,7 @@ #include "api/peer_connection_interface.h" #include "api/scoped_refptr.h" +#include "api/task_queue/task_queue_factory.h" #include "livekit/audio_device.h" #include "media_stream.h" #include "rtp_parameters.h" @@ -65,6 +66,7 @@ class PeerConnectionFactory { std::shared_ptr rtc_runtime_; rtc::scoped_refptr audio_device_; rtc::scoped_refptr peer_factory_; + webrtc::TaskQueueFactory* task_queue_factory_; }; std::shared_ptr create_peer_connection_factory(); diff --git a/webrtc-sys/src/audio_track.cpp b/webrtc-sys/src/audio_track.cpp index 165f8e9c7..980c22efb 100644 --- a/webrtc-sys/src/audio_track.cpp +++ b/webrtc-sys/src/audio_track.cpp @@ -18,12 +18,16 @@ #include #include +#include #include #include "api/audio_options.h" #include "api/media_stream_interface.h" +#include "api/task_queue/task_queue_base.h" #include "audio/remix_resample.h" #include "common_audio/include/audio_util.h" +#include "livekit/global_task_queue.h" +#include "rtc_base/checks.h" #include "rtc_base/logging.h" #include "rtc_base/ref_counted_object.h" #include "rtc_base/synchronization/mutex.h" @@ -123,7 +127,93 @@ std::shared_ptr new_native_audio_sink( } AudioTrackSource::InternalSource::InternalSource( - const cricket::AudioOptions& options) {} + const cricket::AudioOptions& options, + int sample_rate, + int num_channels, + int queue_size_ms, // must be a multiple of 10ms + webrtc::TaskQueueFactory* task_queue_factory) + : sample_rate_(sample_rate), + num_channels_(num_channels), + capture_userdata_(nullptr), + on_complete_(nullptr) { + if (!queue_size_ms) + return; // no audio queue + + int samples10ms = sample_rate / 100 * num_channels; + queue_size_samples_ = queue_size_ms / 10 * samples10ms; + notify_threshold_samples_ = queue_size_samples_; // TODO: this is currently + // using x2 the queue size + buffer_.resize(queue_size_samples_ + notify_threshold_samples_); + + audio_queue_ = + std::make_unique(task_queue_factory->CreateTaskQueue( + "AudioSourceCapture", webrtc::TaskQueueFactory::Priority::NORMAL)); + + audio_task_ = webrtc::RepeatingTaskHandle::Start( + audio_queue_->Get(), + [this, samples10ms]() { + webrtc::MutexLock lock(&mutex_); + + if (buffer_.size() >= samples10ms) { + for (auto sink : sinks_) + sink->OnData(buffer_.data(), sizeof(int16_t), sample_rate_, + num_channels_, samples10ms / num_channels_); + + buffer_.erase(buffer_.begin(), buffer_.begin() + samples10ms); + } + + if (on_complete_ && buffer_.size() <= notify_threshold_samples_) { + on_complete_(capture_userdata_); + on_complete_ = nullptr; + capture_userdata_ = nullptr; + } + + return webrtc::TimeDelta::Millis(10); + }, + webrtc::TaskQueueBase::DelayPrecision::kHigh); +} + +bool AudioTrackSource::InternalSource::capture_frame( + rust::Slice data, + uint32_t sample_rate, + uint32_t number_of_channels, + size_t number_of_frames, + const SourceContext* ctx, + void (*on_complete)(const SourceContext*)) { + webrtc::MutexLock lock(&mutex_); + + if (queue_size_samples_) { + int available = + (queue_size_samples_ + notify_threshold_samples_) - buffer_.size(); + if (available < data.size()) + return false; + + if (on_complete_ || capture_userdata_) + return false; + + buffer_.insert(buffer_.end(), data.begin(), data.end()); + + if (buffer_.size() <= notify_threshold_samples_) { + on_complete(ctx); // complete directly + } else { + on_complete_ = on_complete; + capture_userdata_ = ctx; + } + + } else { + // capture directly when the queue buffer is 0 (frame size must be 10ms) + for (auto sink : sinks_) + sink->OnData(data.data(), sizeof(int16_t), sample_rate, + number_of_channels, number_of_frames); + } + + return true; +} + +void AudioTrackSource::InternalSource::clear_buffer() { + webrtc::MutexLock lock(&mutex_); + buffer_.clear(); +} webrtc::MediaSourceInterface::SourceState AudioTrackSource::InternalSource::state() const { @@ -157,22 +247,17 @@ void AudioTrackSource::InternalSource::RemoveSink( sinks_.erase(std::remove(sinks_.begin(), sinks_.end(), sink), sinks_.end()); } -void AudioTrackSource::InternalSource::on_captured_frame( - rust::Slice data, - uint32_t sample_rate, - uint32_t number_of_channels, - size_t number_of_frames) { - webrtc::MutexLock lock(&mutex_); - for (auto sink : sinks_) { - sink->OnData(data.data(), 16, sample_rate, number_of_channels, - number_of_frames); - } -} - -AudioTrackSource::AudioTrackSource(AudioSourceOptions options) { - source_ = - rtc::make_ref_counted(to_native_audio_options(options)); -} +AudioTrackSource::AudioTrackSource(AudioSourceOptions options, + int sample_rate, + int num_channels, + int queue_size_ms, + webrtc::TaskQueueFactory* task_queue_factory) + : source_(rtc::make_ref_counted( + to_native_audio_options(options), + sample_rate, + num_channels, + queue_size_ms, + task_queue_factory)) {} AudioSourceOptions AudioTrackSource::audio_options() const { return to_rust_audio_options(source_->options()); @@ -183,22 +268,34 @@ void AudioTrackSource::set_audio_options( source_->set_options(to_native_audio_options(options)); } -void AudioTrackSource::on_captured_frame(rust::Slice audio_data, - uint32_t sample_rate, - uint32_t number_of_channels, - size_t number_of_frames) const { - source_->on_captured_frame(audio_data, sample_rate, number_of_channels, - number_of_frames); +bool AudioTrackSource::capture_frame( + rust::Slice audio_data, + uint32_t sample_rate, + uint32_t number_of_channels, + size_t number_of_frames, + const SourceContext* ctx, + void (*on_complete)(const SourceContext*)) const { + return source_->capture_frame(audio_data, sample_rate, number_of_channels, + number_of_frames, ctx, on_complete); } -rtc::scoped_refptr AudioTrackSource::get() - const { - return source_; +void AudioTrackSource::clear_buffer() const { + source_->clear_buffer(); } std::shared_ptr new_audio_track_source( - AudioSourceOptions options) { - return std::make_shared(options); + AudioSourceOptions options, + int sample_rate, + int num_channels, + int queue_size_ms) { + return std::make_shared(options, sample_rate, num_channels, + queue_size_ms, + GetGlobalTaskQueueFactory()); +} + +rtc::scoped_refptr AudioTrackSource::get() + const { + return source_; } } // namespace livekit diff --git a/webrtc-sys/src/audio_track.rs b/webrtc-sys/src/audio_track.rs index 26b9f8308..d24f87520 100644 --- a/webrtc-sys/src/audio_track.rs +++ b/webrtc-sys/src/audio_track.rs @@ -12,6 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +use cxx::type_id; +use cxx::ExternType; +use std::any::Any; use std::sync::Arc; use crate::impl_thread_safety; @@ -29,6 +32,7 @@ pub mod ffi { include!("livekit/media_stream_track.h"); type MediaStreamTrack = crate::media_stream_track::ffi::MediaStreamTrack; + type CompleteCallback = crate::audio_track::CompleteCallback; } unsafe extern "C++" { @@ -46,24 +50,35 @@ pub mod ffi { num_channels: i32, ) -> SharedPtr; - fn on_captured_frame( + unsafe fn capture_frame( self: &AudioTrackSource, data: &[i16], sample_rate: u32, nb_channels: u32, nb_frames: usize, - ); + userdata: *const SourceContext, + on_complete: CompleteCallback, + ) -> bool; + fn clear_buffer(self: &AudioTrackSource); fn audio_options(self: &AudioTrackSource) -> AudioSourceOptions; fn set_audio_options(self: &AudioTrackSource, options: &AudioSourceOptions); - fn new_audio_track_source(options: AudioSourceOptions) -> SharedPtr; + + fn new_audio_track_source( + options: AudioSourceOptions, + sample_rate: i32, + num_channels: i32, + queue_size_ms: i32, + ) -> SharedPtr; fn audio_to_media(track: SharedPtr) -> SharedPtr; unsafe fn media_to_audio(track: SharedPtr) -> SharedPtr; fn _shared_audio_track() -> SharedPtr; + fn _shared_audio_track_source() -> SharedPtr; } extern "Rust" { type AudioSinkWrapper; + type SourceContext; fn on_data( self: &AudioSinkWrapper, @@ -79,6 +94,17 @@ impl_thread_safety!(ffi::AudioTrack, Send + Sync); impl_thread_safety!(ffi::NativeAudioSink, Send + Sync); impl_thread_safety!(ffi::AudioTrackSource, Send + Sync); +#[repr(transparent)] +pub struct SourceContext(pub Box); + +#[repr(transparent)] +pub struct CompleteCallback(pub extern "C" fn(ctx: *const SourceContext)); + +unsafe impl ExternType for CompleteCallback { + type Id = type_id!("livekit::CompleteCallback"); + type Kind = cxx::kind::Trivial; +} + pub trait AudioSink: Send { fn on_data(&self, data: &[i16], sample_rate: i32, nb_channels: usize, nb_frames: usize); } diff --git a/webrtc-sys/src/global_task_queue.cpp b/webrtc-sys/src/global_task_queue.cpp new file mode 100644 index 000000000..0f0cfc2b3 --- /dev/null +++ b/webrtc-sys/src/global_task_queue.cpp @@ -0,0 +1,14 @@ +#include "livekit/global_task_queue.h" + +#include "api/task_queue/default_task_queue_factory.h" +#include "api/task_queue/task_queue_factory.h" + +namespace livekit { + +webrtc::TaskQueueFactory* GetGlobalTaskQueueFactory() { + static std::unique_ptr global_task_queue_factory = + webrtc::CreateDefaultTaskQueueFactory(); + return global_task_queue_factory.get(); +} + +} // namespace livekit diff --git a/webrtc-sys/src/peer_connection_factory.cpp b/webrtc-sys/src/peer_connection_factory.cpp index 28e400f63..bbc73bfdc 100644 --- a/webrtc-sys/src/peer_connection_factory.cpp +++ b/webrtc-sys/src/peer_connection_factory.cpp @@ -28,6 +28,7 @@ #include "api/video_codecs/builtin_video_decoder_factory.h" #include "api/video_codecs/builtin_video_encoder_factory.h" #include "livekit/audio_device.h" +#include "livekit/audio_track.h" #include "livekit/peer_connection.h" #include "livekit/rtc_error.h" #include "livekit/rtp_parameters.h" @@ -83,6 +84,9 @@ PeerConnectionFactory::PeerConnectionFactory( peer_factory_ = webrtc::CreateModularPeerConnectionFactory(std::move(dependencies)); + + task_queue_factory_ = dependencies.task_queue_factory.get(); + if (peer_factory_.get() == nullptr) { RTC_LOG_ERR(LS_ERROR) << "Failed to create PeerConnectionFactory"; return; @@ -126,6 +130,7 @@ std::shared_ptr PeerConnectionFactory::create_audio_track( rtc_runtime_->get_or_create_media_stream_track( peer_factory_->CreateAudioTrack(label.c_str(), source->get().get()))); } + RtpCapabilities PeerConnectionFactory::rtp_sender_capabilities( MediaType type) const { return to_rust_rtp_capabilities(peer_factory_->GetRtpSenderCapabilities( diff --git a/webrtc-sys/src/peer_connection_factory.rs b/webrtc-sys/src/peer_connection_factory.rs index 51efc2f46..1ae912848 100644 --- a/webrtc-sys/src/peer_connection_factory.rs +++ b/webrtc-sys/src/peer_connection_factory.rs @@ -49,6 +49,7 @@ pub mod ffi { include!("livekit/jsep.h"); include!("livekit/webrtc.h"); include!("livekit/peer_connection.h"); + include!("livekit/audio_track.h"); type RtcConfiguration = crate::peer_connection::ffi::RtcConfiguration; type PeerConnectionState = crate::peer_connection::ffi::PeerConnectionState; From 6b1c5eafe880df843d210bad6634ed481ac76ea5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Thu, 12 Sep 2024 12:15:05 -0700 Subject: [PATCH 034/274] ffi-v0.10.0 (#432) --- livekit-ffi/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index f090a9163..9416414c6 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.9.1" +version = "0.10.0" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" From 40a1f113298742913e2780eee51e16f80eafd6f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?The=CC=81o=20Monnom?= Date: Thu, 12 Sep 2024 13:08:53 -0700 Subject: [PATCH 035/274] remove debug logs --- Cargo.lock | 2 +- libwebrtc/src/native/audio_source.rs | 6 ------ livekit-ffi/Cargo.toml | 2 +- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f81128d0c..91bcfced1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1591,7 +1591,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.9.1" +version = "0.10.1" dependencies = [ "console-subscriber", "dashmap", diff --git a/libwebrtc/src/native/audio_source.rs b/libwebrtc/src/native/audio_source.rs index c7652c676..7956508f5 100644 --- a/libwebrtc/src/native/audio_source.rs +++ b/libwebrtc/src/native/audio_source.rs @@ -35,11 +35,6 @@ impl NativeAudioSource { ) -> NativeAudioSource { assert!(queue_size_ms % 10 == 0, "queue_size_ms must be a multiple of 10"); - print!( - "new audio source {} {} {} {}", - sample_rate, num_channels, queue_size_ms, options.echo_cancellation - ); - let sys_handle = sys_at::ffi::new_audio_track_source( options.into(), sample_rate.try_into().unwrap(), @@ -112,7 +107,6 @@ impl NativeAudioSource { } let _ = rx.await; - println!("captured frame"); } Ok(()) diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 9416414c6..19ee08437 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.10.0" +version = "0.10.1" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" From c84f3c239f44b3438c080b3e984b17caeb297eb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Fri, 13 Sep 2024 09:59:18 -0700 Subject: [PATCH 036/274] send silence frames on the AudioSource when the queue is empty (#433) * send silence frames * Update audio_track.cpp * Update audio_track.cpp * directly send silence --- webrtc-sys/include/livekit/audio_track.h | 5 +++++ webrtc-sys/src/audio_track.cpp | 20 +++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/webrtc-sys/include/livekit/audio_track.h b/webrtc-sys/include/livekit/audio_track.h index 029158fd5..e7f3e7520 100644 --- a/webrtc-sys/include/livekit/audio_track.h +++ b/webrtc-sys/include/livekit/audio_track.h @@ -104,6 +104,8 @@ class AudioTrackSource { int buffer_size_ms, webrtc::TaskQueueFactory* task_queue_factory); + ~InternalSource() override; + SourceState state() const override; bool remote() const override; @@ -134,6 +136,9 @@ class AudioTrackSource { const SourceContext* capture_userdata_ RTC_GUARDED_BY(mutex_); void (*on_complete_)(const SourceContext*) RTC_GUARDED_BY(mutex_); + int missed_frames_ RTC_GUARDED_BY(mutex_) = 0; + int16_t* silence_buffer_ = nullptr; + int sample_rate_; int num_channels_; int queue_size_samples_; diff --git a/webrtc-sys/src/audio_track.cpp b/webrtc-sys/src/audio_track.cpp index 980c22efb..1d42f14e9 100644 --- a/webrtc-sys/src/audio_track.cpp +++ b/webrtc-sys/src/audio_track.cpp @@ -139,11 +139,18 @@ AudioTrackSource::InternalSource::InternalSource( if (!queue_size_ms) return; // no audio queue + // start sending silence when there is nothing on the queue for 10 frames + // (100ms) + const int silence_frames_threshold = 10; + missed_frames_ = silence_frames_threshold; + int samples10ms = sample_rate / 100 * num_channels; + + silence_buffer_ = new int16_t[samples10ms](); queue_size_samples_ = queue_size_ms / 10 * samples10ms; notify_threshold_samples_ = queue_size_samples_; // TODO: this is currently // using x2 the queue size - buffer_.resize(queue_size_samples_ + notify_threshold_samples_); + buffer_.reserve(queue_size_samples_ + notify_threshold_samples_); audio_queue_ = std::make_unique(task_queue_factory->CreateTaskQueue( @@ -160,6 +167,13 @@ AudioTrackSource::InternalSource::InternalSource( num_channels_, samples10ms / num_channels_); buffer_.erase(buffer_.begin(), buffer_.begin() + samples10ms); + } else { + missed_frames_++; + if (missed_frames_ >= silence_frames_threshold) { + for (auto sink : sinks_) + sink->OnData(silence_buffer_, sizeof(int16_t), sample_rate_, + num_channels_, samples10ms / num_channels_); + } } if (on_complete_ && buffer_.size() <= notify_threshold_samples_) { @@ -173,6 +187,10 @@ AudioTrackSource::InternalSource::InternalSource( webrtc::TaskQueueBase::DelayPrecision::kHigh); } +AudioTrackSource::InternalSource::~InternalSource() { + delete[] silence_buffer_; +} + bool AudioTrackSource::InternalSource::capture_frame( rust::Slice data, uint32_t sample_rate, From 1b87c1aff6fb84284ab15242843ff7222ed7dbf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?The=CC=81o=20Monnom?= Date: Fri, 13 Sep 2024 14:10:30 -0700 Subject: [PATCH 037/274] ffi-v0.10.2 --- livekit-ffi/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 19ee08437..b9deb78b0 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.10.1" +version = "0.10.2" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" From e2ad85d24f349aa1617a75204137665b59ddab71 Mon Sep 17 00:00:00 2001 From: aoife cassidy Date: Sat, 14 Sep 2024 15:19:29 -0700 Subject: [PATCH 038/274] add nanpa version management support (#434) --- .github/workflows/publish.yml | 35 ++- .nanparc | 1 + libwebrtc/.nanparc | 2 + livekit-api/.nanparc | 2 + livekit-ffi/.nanparc | 2 + livekit-protocol/.nanparc | 2 + livekit-protocol/src/livekit.rs | 309 +++++--------------------- livekit-protocol/src/livekit.serde.rs | 71 +++++- livekit-runtime/.nanparc | 2 + livekit/.nanparc | 2 + webrtc-sys/.nanparc | 2 + webrtc-sys/build/.nanparc | 2 + 12 files changed, 160 insertions(+), 272 deletions(-) create mode 100644 .nanparc create mode 100644 libwebrtc/.nanparc create mode 100644 livekit-api/.nanparc create mode 100644 livekit-ffi/.nanparc create mode 100644 livekit-protocol/.nanparc create mode 100644 livekit-runtime/.nanparc create mode 100644 livekit/.nanparc create mode 100644 webrtc-sys/.nanparc create mode 100644 webrtc-sys/build/.nanparc diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 63c225528..de50fd655 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -12,30 +12,41 @@ # See the License for the specific language governing permissions and # limitations under the License. -name: Publish crates +name: Bump and publish crates on: - push: - tags: - - v* + workflow_dispatch: + inputs: + packages: + description: "packages to bump" + type: string + required: true env: CARGO_TERM_COLOR: always CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_TOKEN }} jobs: + bump: + permissions: + contents: write + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: nbsp/ilo@v1 + with: + packages: ${{ github.event.inputs.packages }} publish: - runs-on: windows-latest + runs-on: ubuntu-latest + needs: bump steps: - uses: actions/checkout@v3 with: submodules: true - name: Publish crates run: | - cd livekit-protocol && cargo publish --no-verify - cd ../webrtc-sys/build && cargo publish --no-verify - cd ../../webrtc-sys && cargo publish --no-verify - cd ../libwebrtc && cargo publish --no-verify - cd ../livekit-api && cargo publish --no-verify - cd ../livekit && cp ../README.md README.md && cargo publish --allow-dirty --no-verify - cd ../livekit-ffi && cargo publish --no-verify + git tag --points-at HEAD | + sed 's|^[^/]*@|@|' | + sed 's|^[^/]*/||' | + sed 's|@.*||' | + xargs -I _ sh -c 'cd ./_ && cargo publish --no-verify' diff --git a/.nanparc b/.nanparc new file mode 100644 index 000000000..d7523fefa --- /dev/null +++ b/.nanparc @@ -0,0 +1 @@ +packages livekit livekit-ffi livekit-protocol livekit-runtime livekit-api libwebrtc webrtc-sys webrtc-sys/build diff --git a/libwebrtc/.nanparc b/libwebrtc/.nanparc new file mode 100644 index 000000000..5bf971c37 --- /dev/null +++ b/libwebrtc/.nanparc @@ -0,0 +1,2 @@ +version 0.3.7 +language rust diff --git a/livekit-api/.nanparc b/livekit-api/.nanparc new file mode 100644 index 000000000..f3e92f54e --- /dev/null +++ b/livekit-api/.nanparc @@ -0,0 +1,2 @@ +version 0.4.0 +language rust diff --git a/livekit-ffi/.nanparc b/livekit-ffi/.nanparc new file mode 100644 index 000000000..18c96a7ba --- /dev/null +++ b/livekit-ffi/.nanparc @@ -0,0 +1,2 @@ +version 0.10.2 +language rust diff --git a/livekit-protocol/.nanparc b/livekit-protocol/.nanparc new file mode 100644 index 000000000..d431df850 --- /dev/null +++ b/livekit-protocol/.nanparc @@ -0,0 +1,2 @@ +version 0.3.5 +language rust diff --git a/livekit-protocol/src/livekit.rs b/livekit-protocol/src/livekit.rs index 39426bdef..e7cc3d0cf 100644 --- a/livekit-protocol/src/livekit.rs +++ b/livekit-protocol/src/livekit.rs @@ -1,6 +1,5 @@ // @generated // This file is @generated by prost-build. -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct Room { #[prost(string, tag="1")] @@ -30,7 +29,6 @@ pub struct Room { #[prost(message, optional, tag="13")] pub version: ::core::option::Option, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct Codec { #[prost(string, tag="1")] @@ -38,8 +36,7 @@ pub struct Codec { #[prost(string, tag="2")] pub fmtp_line: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct PlayoutDelay { #[prost(bool, tag="1")] pub enabled: bool, @@ -48,7 +45,6 @@ pub struct PlayoutDelay { #[prost(uint32, tag="3")] pub max: u32, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ParticipantPermission { /// allow participant to subscribe to other tracks in the room @@ -80,7 +76,6 @@ pub struct ParticipantPermission { #[prost(bool, tag="11")] pub agent: bool, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ParticipantInfo { #[prost(string, tag="1")] @@ -194,8 +189,7 @@ pub mod participant_info { } } } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct Encryption { } /// Nested message and enum types in `Encryption`. @@ -230,7 +224,6 @@ pub mod encryption { } } } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SimulcastCodecInfo { #[prost(string, tag="1")] @@ -242,7 +235,6 @@ pub struct SimulcastCodecInfo { #[prost(message, repeated, tag="4")] pub layers: ::prost::alloc::vec::Vec, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackInfo { #[prost(string, tag="1")] @@ -293,8 +285,7 @@ pub struct TrackInfo { pub audio_features: ::prost::alloc::vec::Vec, } /// provide information about available spatial layers -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct VideoLayer { /// for tracks with a single layer, this should be HIGH #[prost(enumeration="VideoQuality", tag="1")] @@ -310,7 +301,6 @@ pub struct VideoLayer { pub ssrc: u32, } /// new DataPacket API -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct DataPacket { #[deprecated] @@ -353,8 +343,7 @@ pub mod data_packet { } } } - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Oneof)] + #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Value { #[prost(message, tag="2")] User(super::UserPacket), @@ -366,13 +355,11 @@ pub mod data_packet { Transcription(super::Transcription), } } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ActiveSpeakerUpdate { #[prost(message, repeated, tag="1")] pub speakers: ::prost::alloc::vec::Vec, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SpeakerInfo { #[prost(string, tag="1")] @@ -384,7 +371,6 @@ pub struct SpeakerInfo { #[prost(bool, tag="3")] pub active: bool, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct UserPacket { /// participant ID of user that sent the message @@ -417,7 +403,6 @@ pub struct UserPacket { #[prost(uint64, optional, tag="10")] pub end_time: ::core::option::Option, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SipDtmf { #[prost(uint32, tag="3")] @@ -425,7 +410,6 @@ pub struct SipDtmf { #[prost(string, tag="4")] pub digit: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct Transcription { /// Participant that got its speech transcribed @@ -436,7 +420,6 @@ pub struct Transcription { #[prost(message, repeated, tag="4")] pub segments: ::prost::alloc::vec::Vec, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TranscriptionSegment { #[prost(string, tag="1")] @@ -452,7 +435,6 @@ pub struct TranscriptionSegment { #[prost(string, tag="6")] pub language: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ParticipantTracks { /// participant ID of participant to whom the tracks belong @@ -462,7 +444,6 @@ pub struct ParticipantTracks { pub track_sids: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, } /// details about the server -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ServerInfo { #[prost(enumeration="server_info::Edition", tag="1")] @@ -511,7 +492,6 @@ pub mod server_info { } } /// details about the client -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ClientInfo { #[prost(enumeration="client_info::Sdk", tag="1")] @@ -593,7 +573,6 @@ pub mod client_info { } } /// server provided client configuration -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ClientConfiguration { #[prost(message, optional, tag="1")] @@ -607,13 +586,11 @@ pub struct ClientConfiguration { #[prost(enumeration="ClientConfigSetting", tag="5")] pub force_relay: i32, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct VideoConfiguration { #[prost(enumeration="ClientConfigSetting", tag="1")] pub hardware_encoder: i32, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct DisabledCodecs { /// disabled for both publish and subscribe @@ -623,8 +600,7 @@ pub struct DisabledCodecs { #[prost(message, repeated, tag="2")] pub publish: ::prost::alloc::vec::Vec, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct RtpDrift { #[prost(message, optional, tag="1")] pub start_time: ::core::option::Option<::pbjson_types::Timestamp>, @@ -645,7 +621,6 @@ pub struct RtpDrift { #[prost(double, tag="9")] pub clock_rate: f64, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RtpStats { #[prost(message, optional, tag="1")] @@ -738,8 +713,7 @@ pub struct RtpStats { #[prost(message, optional, tag="46")] pub rebased_report_drift: ::core::option::Option, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct RtpForwarderState { #[prost(bool, tag="1")] pub started: bool, @@ -758,15 +732,13 @@ pub struct RtpForwarderState { } /// Nested message and enum types in `RTPForwarderState`. pub mod rtp_forwarder_state { - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Oneof)] + #[derive(Clone, Copy, PartialEq, ::prost::Oneof)] pub enum CodecMunger { #[prost(message, tag="7")] Vp8Munger(super::Vp8MungerState), } } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct RtpMungerState { #[prost(uint64, tag="1")] pub ext_last_sequence_number: u64, @@ -781,8 +753,7 @@ pub struct RtpMungerState { #[prost(bool, tag="6")] pub second_last_marker: bool, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct Vp8MungerState { #[prost(int32, tag="1")] pub ext_last_picture_id: i32, @@ -799,8 +770,7 @@ pub struct Vp8MungerState { #[prost(bool, tag="7")] pub key_idx_used: bool, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct TimedVersion { #[prost(int64, tag="1")] pub unix_micro: i64, @@ -1220,7 +1190,6 @@ impl AudioTrackFeature { } } /// composite using a web browser -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RoomCompositeEgressRequest { /// required @@ -1255,8 +1224,7 @@ pub struct RoomCompositeEgressRequest { /// Nested message and enum types in `RoomCompositeEgressRequest`. pub mod room_composite_egress_request { /// deprecated (use _output fields) - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Oneof)] + #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Output { #[prost(message, tag="6")] File(super::EncodedFileOutput), @@ -1265,8 +1233,7 @@ pub mod room_composite_egress_request { #[prost(message, tag="10")] Segments(super::SegmentedFileOutput), } - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Oneof)] + #[derive(Clone, Copy, PartialEq, ::prost::Oneof)] pub enum Options { /// (default H264_720P_30) #[prost(enumeration="super::EncodingOptionsPreset", tag="8")] @@ -1277,7 +1244,6 @@ pub mod room_composite_egress_request { } } /// record any website -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct WebEgressRequest { #[prost(string, tag="1")] @@ -1305,8 +1271,7 @@ pub struct WebEgressRequest { /// Nested message and enum types in `WebEgressRequest`. pub mod web_egress_request { /// deprecated (use _output fields) - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Oneof)] + #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Output { #[prost(message, tag="4")] File(super::EncodedFileOutput), @@ -1315,8 +1280,7 @@ pub mod web_egress_request { #[prost(message, tag="6")] Segments(super::SegmentedFileOutput), } - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Oneof)] + #[derive(Clone, Copy, PartialEq, ::prost::Oneof)] pub enum Options { #[prost(enumeration="super::EncodingOptionsPreset", tag="7")] Preset(i32), @@ -1325,7 +1289,6 @@ pub mod web_egress_request { } } /// record audio and video from a single participant -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ParticipantEgressRequest { /// required @@ -1350,8 +1313,7 @@ pub struct ParticipantEgressRequest { } /// Nested message and enum types in `ParticipantEgressRequest`. pub mod participant_egress_request { - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Oneof)] + #[derive(Clone, Copy, PartialEq, ::prost::Oneof)] pub enum Options { /// (default H264_720P_30) #[prost(enumeration="super::EncodingOptionsPreset", tag="4")] @@ -1362,7 +1324,6 @@ pub mod participant_egress_request { } } /// containerize up to one audio and one video track -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackCompositeEgressRequest { /// required @@ -1391,8 +1352,7 @@ pub struct TrackCompositeEgressRequest { /// Nested message and enum types in `TrackCompositeEgressRequest`. pub mod track_composite_egress_request { /// deprecated (use _output fields) - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Oneof)] + #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Output { #[prost(message, tag="4")] File(super::EncodedFileOutput), @@ -1401,8 +1361,7 @@ pub mod track_composite_egress_request { #[prost(message, tag="8")] Segments(super::SegmentedFileOutput), } - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Oneof)] + #[derive(Clone, Copy, PartialEq, ::prost::Oneof)] pub enum Options { /// (default H264_720P_30) #[prost(enumeration="super::EncodingOptionsPreset", tag="6")] @@ -1413,7 +1372,6 @@ pub mod track_composite_egress_request { } } /// record tracks individually, without transcoding -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackEgressRequest { /// required @@ -1429,8 +1387,7 @@ pub struct TrackEgressRequest { /// Nested message and enum types in `TrackEgressRequest`. pub mod track_egress_request { /// required - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Oneof)] + #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Output { #[prost(message, tag="3")] File(super::DirectFileOutput), @@ -1438,7 +1395,6 @@ pub mod track_egress_request { WebsocketUrl(::prost::alloc::string::String), } } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct EncodedFileOutput { /// (optional) @@ -1455,8 +1411,7 @@ pub struct EncodedFileOutput { } /// Nested message and enum types in `EncodedFileOutput`. pub mod encoded_file_output { - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Oneof)] + #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Output { #[prost(message, tag="3")] S3(super::S3Upload), @@ -1469,7 +1424,6 @@ pub mod encoded_file_output { } } /// Used to generate HLS segments or other kind of segmented output -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SegmentedFileOutput { /// (optional) @@ -1500,8 +1454,7 @@ pub struct SegmentedFileOutput { /// Nested message and enum types in `SegmentedFileOutput`. pub mod segmented_file_output { /// required - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Oneof)] + #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Output { #[prost(message, tag="5")] S3(super::S3Upload), @@ -1513,7 +1466,6 @@ pub mod segmented_file_output { AliOss(super::AliOssUpload), } } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct DirectFileOutput { /// see egress docs for templating (default {track_id}-{time}) @@ -1527,8 +1479,7 @@ pub struct DirectFileOutput { } /// Nested message and enum types in `DirectFileOutput`. pub mod direct_file_output { - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Oneof)] + #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Output { #[prost(message, tag="2")] S3(super::S3Upload), @@ -1540,7 +1491,6 @@ pub mod direct_file_output { AliOss(super::AliOssUpload), } } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ImageOutput { /// in seconds (required) @@ -1571,8 +1521,7 @@ pub struct ImageOutput { /// Nested message and enum types in `ImageOutput`. pub mod image_output { /// required - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Oneof)] + #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Output { #[prost(message, tag="8")] S3(super::S3Upload), @@ -1584,7 +1533,6 @@ pub mod image_output { AliOss(super::AliOssUpload), } } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct S3Upload { #[prost(string, tag="1")] @@ -1611,7 +1559,6 @@ pub struct S3Upload { #[prost(message, optional, tag="10")] pub proxy: ::core::option::Option, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GcpUpload { /// service account credentials serialized in JSON "credentials.json" @@ -1622,7 +1569,6 @@ pub struct GcpUpload { #[prost(message, optional, tag="3")] pub proxy: ::core::option::Option, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct AzureBlobUpload { #[prost(string, tag="1")] @@ -1632,7 +1578,6 @@ pub struct AzureBlobUpload { #[prost(string, tag="3")] pub container_name: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct AliOssUpload { #[prost(string, tag="1")] @@ -1646,7 +1591,6 @@ pub struct AliOssUpload { #[prost(string, tag="5")] pub bucket: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ProxyConfig { #[prost(string, tag="1")] @@ -1656,7 +1600,6 @@ pub struct ProxyConfig { #[prost(string, tag="3")] pub password: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct StreamOutput { /// required @@ -1666,8 +1609,7 @@ pub struct StreamOutput { #[prost(string, repeated, tag="2")] pub urls: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct EncodingOptions { /// (default 1920) #[prost(int32, tag="1")] @@ -1706,7 +1648,6 @@ pub struct EncodingOptions { #[prost(double, tag="10")] pub key_frame_interval: f64, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct UpdateLayoutRequest { #[prost(string, tag="1")] @@ -1714,7 +1655,6 @@ pub struct UpdateLayoutRequest { #[prost(string, tag="2")] pub layout: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct UpdateStreamRequest { #[prost(string, tag="1")] @@ -1724,7 +1664,6 @@ pub struct UpdateStreamRequest { #[prost(string, repeated, tag="3")] pub remove_output_urls: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ListEgressRequest { /// (optional, filter by room name) @@ -1737,19 +1676,16 @@ pub struct ListEgressRequest { #[prost(bool, tag="3")] pub active: bool, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ListEgressResponse { #[prost(message, repeated, tag="1")] pub items: ::prost::alloc::vec::Vec, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct StopEgressRequest { #[prost(string, tag="1")] pub egress_id: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct EgressInfo { #[prost(string, tag="1")] @@ -1788,8 +1724,7 @@ pub struct EgressInfo { } /// Nested message and enum types in `EgressInfo`. pub mod egress_info { - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Oneof)] + #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Request { #[prost(message, tag="4")] RoomComposite(super::RoomCompositeEgressRequest), @@ -1803,8 +1738,7 @@ pub mod egress_info { Track(super::TrackEgressRequest), } /// deprecated (use _result fields) - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Oneof)] + #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Result { #[prost(message, tag="7")] Stream(super::StreamInfoList), @@ -1814,13 +1748,11 @@ pub mod egress_info { Segments(super::SegmentsInfo), } } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct StreamInfoList { #[prost(message, repeated, tag="1")] pub info: ::prost::alloc::vec::Vec, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct StreamInfo { #[prost(string, tag="1")] @@ -1868,7 +1800,6 @@ pub mod stream_info { } } } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FileInfo { #[prost(string, tag="1")] @@ -1884,7 +1815,6 @@ pub struct FileInfo { #[prost(string, tag="5")] pub location: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SegmentsInfo { #[prost(string, tag="1")] @@ -1906,7 +1836,6 @@ pub struct SegmentsInfo { #[prost(int64, tag="7")] pub ended_at: i64, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ImagesInfo { #[prost(string, tag="4")] @@ -1918,7 +1847,6 @@ pub struct ImagesInfo { #[prost(int64, tag="3")] pub ended_at: i64, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct AutoParticipantEgress { #[prost(message, repeated, tag="3")] @@ -1930,8 +1858,7 @@ pub struct AutoParticipantEgress { } /// Nested message and enum types in `AutoParticipantEgress`. pub mod auto_participant_egress { - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Oneof)] + #[derive(Clone, Copy, PartialEq, ::prost::Oneof)] pub enum Options { /// (default H264_720P_30) #[prost(enumeration="super::EncodingOptionsPreset", tag="1")] @@ -1941,7 +1868,6 @@ pub mod auto_participant_egress { Advanced(super::EncodingOptions), } } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct AutoTrackEgress { /// see docs for templating (default {track_id}-{time}) @@ -1955,8 +1881,7 @@ pub struct AutoTrackEgress { } /// Nested message and enum types in `AutoTrackEgress`. pub mod auto_track_egress { - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Oneof)] + #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Output { #[prost(message, tag="2")] S3(super::S3Upload), @@ -2199,7 +2124,6 @@ impl EgressStatus { } } } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SignalRequest { #[prost(oneof="signal_request::Message", tags="1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17, 18")] @@ -2207,8 +2131,7 @@ pub struct SignalRequest { } /// Nested message and enum types in `SignalRequest`. pub mod signal_request { - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Oneof)] + #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Message { /// initial join exchange, for publisher #[prost(message, tag="1")] @@ -2263,7 +2186,6 @@ pub mod signal_request { UpdateVideoTrack(super::UpdateLocalVideoTrack), } } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SignalResponse { #[prost(oneof="signal_response::Message", tags="1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23")] @@ -2271,8 +2193,7 @@ pub struct SignalResponse { } /// Nested message and enum types in `SignalResponse`. pub mod signal_response { - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Oneof)] + #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Message { /// sent when join is accepted #[prost(message, tag="1")] @@ -2345,7 +2266,6 @@ pub mod signal_response { TrackSubscribed(super::TrackSubscribed), } } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SimulcastCodec { #[prost(string, tag="1")] @@ -2353,7 +2273,6 @@ pub struct SimulcastCodec { #[prost(string, tag="2")] pub cid: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct AddTrackRequest { /// client ID of track, to match it when RTC track is received @@ -2395,7 +2314,6 @@ pub struct AddTrackRequest { #[prost(string, tag="15")] pub stream: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrickleRequest { #[prost(string, tag="1")] @@ -2405,7 +2323,6 @@ pub struct TrickleRequest { #[prost(bool, tag="3")] pub r#final: bool, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct MuteTrackRequest { #[prost(string, tag="1")] @@ -2413,7 +2330,6 @@ pub struct MuteTrackRequest { #[prost(bool, tag="2")] pub muted: bool, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct JoinResponse { #[prost(message, optional, tag="1")] @@ -2449,7 +2365,6 @@ pub struct JoinResponse { #[prost(bytes="vec", tag="13")] pub sif_trailer: ::prost::alloc::vec::Vec, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ReconnectResponse { #[prost(message, repeated, tag="1")] @@ -2457,7 +2372,6 @@ pub struct ReconnectResponse { #[prost(message, optional, tag="2")] pub client_configuration: ::core::option::Option, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackPublishedResponse { #[prost(string, tag="1")] @@ -2465,13 +2379,11 @@ pub struct TrackPublishedResponse { #[prost(message, optional, tag="2")] pub track: ::core::option::Option, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackUnpublishedResponse { #[prost(string, tag="1")] pub track_sid: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SessionDescription { /// "answer" | "offer" | "pranswer" | "rollback" @@ -2480,13 +2392,11 @@ pub struct SessionDescription { #[prost(string, tag="2")] pub sdp: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ParticipantUpdate { #[prost(message, repeated, tag="1")] pub participants: ::prost::alloc::vec::Vec, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct UpdateSubscription { #[prost(string, repeated, tag="1")] @@ -2496,7 +2406,6 @@ pub struct UpdateSubscription { #[prost(message, repeated, tag="3")] pub participant_tracks: ::prost::alloc::vec::Vec, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct UpdateTrackSettings { #[prost(string, repeated, tag="1")] @@ -2525,7 +2434,6 @@ pub struct UpdateTrackSettings { #[prost(uint32, tag="8")] pub priority: u32, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct UpdateLocalAudioTrack { #[prost(string, tag="1")] @@ -2533,7 +2441,6 @@ pub struct UpdateLocalAudioTrack { #[prost(enumeration="AudioTrackFeature", repeated, tag="2")] pub features: ::prost::alloc::vec::Vec, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct UpdateLocalVideoTrack { #[prost(string, tag="1")] @@ -2543,7 +2450,6 @@ pub struct UpdateLocalVideoTrack { #[prost(uint32, tag="3")] pub height: u32, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct LeaveRequest { /// sent when server initiates the disconnect due to server-restart @@ -2595,7 +2501,6 @@ pub mod leave_request { } } /// message to indicate published video track dimensions are changing -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct UpdateVideoLayers { #[prost(string, tag="1")] @@ -2603,7 +2508,6 @@ pub struct UpdateVideoLayers { #[prost(message, repeated, tag="2")] pub layers: ::prost::alloc::vec::Vec, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct UpdateParticipantMetadata { #[prost(string, tag="1")] @@ -2617,7 +2521,6 @@ pub struct UpdateParticipantMetadata { #[prost(uint32, tag="4")] pub request_id: u32, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct IceServer { #[prost(string, repeated, tag="1")] @@ -2627,19 +2530,16 @@ pub struct IceServer { #[prost(string, tag="3")] pub credential: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SpeakersChanged { #[prost(message, repeated, tag="1")] pub speakers: ::prost::alloc::vec::Vec, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RoomUpdate { #[prost(message, optional, tag="1")] pub room: ::core::option::Option, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ConnectionQualityInfo { #[prost(string, tag="1")] @@ -2649,13 +2549,11 @@ pub struct ConnectionQualityInfo { #[prost(float, tag="3")] pub score: f32, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ConnectionQualityUpdate { #[prost(message, repeated, tag="1")] pub updates: ::prost::alloc::vec::Vec, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct StreamStateInfo { #[prost(string, tag="1")] @@ -2665,21 +2563,18 @@ pub struct StreamStateInfo { #[prost(enumeration="StreamState", tag="3")] pub state: i32, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct StreamStateUpdate { #[prost(message, repeated, tag="1")] pub stream_states: ::prost::alloc::vec::Vec, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct SubscribedQuality { #[prost(enumeration="VideoQuality", tag="1")] pub quality: i32, #[prost(bool, tag="2")] pub enabled: bool, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SubscribedCodec { #[prost(string, tag="1")] @@ -2687,7 +2582,6 @@ pub struct SubscribedCodec { #[prost(message, repeated, tag="2")] pub qualities: ::prost::alloc::vec::Vec, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SubscribedQualityUpdate { #[prost(string, tag="1")] @@ -2697,7 +2591,6 @@ pub struct SubscribedQualityUpdate { #[prost(message, repeated, tag="3")] pub subscribed_codecs: ::prost::alloc::vec::Vec, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackPermission { /// permission could be granted either by participant sid or identity @@ -2710,7 +2603,6 @@ pub struct TrackPermission { #[prost(string, tag="4")] pub participant_identity: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SubscriptionPermission { #[prost(bool, tag="1")] @@ -2718,7 +2610,6 @@ pub struct SubscriptionPermission { #[prost(message, repeated, tag="2")] pub track_permissions: ::prost::alloc::vec::Vec, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SubscriptionPermissionUpdate { #[prost(string, tag="1")] @@ -2728,7 +2619,6 @@ pub struct SubscriptionPermissionUpdate { #[prost(bool, tag="3")] pub allowed: bool, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SyncState { /// last subscribe answer before reconnecting @@ -2746,7 +2636,6 @@ pub struct SyncState { #[prost(string, repeated, tag="6")] pub track_sids_disabled: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct DataChannelInfo { #[prost(string, tag="1")] @@ -2756,16 +2645,14 @@ pub struct DataChannelInfo { #[prost(enumeration="SignalTarget", tag="3")] pub target: i32, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct SimulateScenario { #[prost(oneof="simulate_scenario::Scenario", tags="1, 2, 3, 4, 5, 6, 7, 8, 9")] pub scenario: ::core::option::Option, } /// Nested message and enum types in `SimulateScenario`. pub mod simulate_scenario { - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Oneof)] + #[derive(Clone, Copy, PartialEq, ::prost::Oneof)] pub enum Scenario { /// simulate N seconds of speaker activity #[prost(int32, tag="1")] @@ -2797,8 +2684,7 @@ pub mod simulate_scenario { LeaveRequestFullReconnect(bool), } } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct Ping { #[prost(int64, tag="1")] pub timestamp: i64, @@ -2806,8 +2692,7 @@ pub struct Ping { #[prost(int64, tag="2")] pub rtt: i64, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct Pong { /// timestamp field of last received ping request #[prost(int64, tag="1")] @@ -2815,13 +2700,11 @@ pub struct Pong { #[prost(int64, tag="2")] pub timestamp: i64, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RegionSettings { #[prost(message, repeated, tag="1")] pub regions: ::prost::alloc::vec::Vec, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RegionInfo { #[prost(string, tag="1")] @@ -2831,7 +2714,6 @@ pub struct RegionInfo { #[prost(int64, tag="3")] pub distance: i64, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SubscriptionResponse { #[prost(string, tag="1")] @@ -2839,7 +2721,6 @@ pub struct SubscriptionResponse { #[prost(enumeration="SubscriptionError", tag="2")] pub err: i32, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RequestResponse { #[prost(uint32, tag="1")] @@ -2884,7 +2765,6 @@ pub mod request_response { } } } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackSubscribed { #[prost(string, tag="1")] @@ -2971,7 +2851,6 @@ impl CandidateProtocol { } } } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct Job { #[prost(string, tag="1")] @@ -2994,7 +2873,6 @@ pub struct Job { #[prost(message, optional, tag="8")] pub state: ::core::option::Option, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct JobState { #[prost(enumeration="JobStatus", tag="1")] @@ -3011,7 +2889,6 @@ pub struct JobState { pub participant_identity: ::prost::alloc::string::String, } /// from Worker to Server -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct WorkerMessage { #[prost(oneof="worker_message::Message", tags="1, 2, 3, 4, 5, 6, 7")] @@ -3019,8 +2896,7 @@ pub struct WorkerMessage { } /// Nested message and enum types in `WorkerMessage`. pub mod worker_message { - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Oneof)] + #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Message { /// agent workers need to register themselves with the server first #[prost(message, tag="1")] @@ -3043,7 +2919,6 @@ pub mod worker_message { } } /// from Server to Worker -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ServerMessage { #[prost(oneof="server_message::Message", tags="1, 2, 3, 5, 4")] @@ -3051,8 +2926,7 @@ pub struct ServerMessage { } /// Nested message and enum types in `ServerMessage`. pub mod server_message { - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Oneof)] + #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Message { /// server confirms the registration, from this moment on, the worker is considered active #[prost(message, tag="1")] @@ -3068,7 +2942,6 @@ pub mod server_message { Pong(super::WorkerPong), } } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SimulateJobRequest { #[prost(enumeration="JobType", tag="1")] @@ -3078,21 +2951,18 @@ pub struct SimulateJobRequest { #[prost(message, optional, tag="3")] pub participant: ::core::option::Option, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct WorkerPing { #[prost(int64, tag="1")] pub timestamp: i64, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct WorkerPong { #[prost(int64, tag="1")] pub last_timestamp: i64, #[prost(int64, tag="2")] pub timestamp: i64, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RegisterWorkerRequest { #[prost(enumeration="JobType", tag="1")] @@ -3110,7 +2980,6 @@ pub struct RegisterWorkerRequest { #[prost(message, optional, tag="7")] pub allowed_permissions: ::core::option::Option, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RegisterWorkerResponse { #[prost(string, tag="1")] @@ -3118,14 +2987,12 @@ pub struct RegisterWorkerResponse { #[prost(message, optional, tag="3")] pub server_info: ::core::option::Option, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct MigrateJobRequest { /// string job_id = 1 \[deprecated = true\]; #[prost(string, repeated, tag="2")] pub job_ids: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct AvailabilityRequest { #[prost(message, optional, tag="1")] @@ -3135,7 +3002,6 @@ pub struct AvailabilityRequest { #[prost(bool, tag="2")] pub resuming: bool, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct AvailabilityResponse { #[prost(string, tag="1")] @@ -3153,7 +3019,6 @@ pub struct AvailabilityResponse { #[prost(map="string, string", tag="7")] pub participant_attributes: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct UpdateJobStatus { #[prost(string, tag="1")] @@ -3165,8 +3030,7 @@ pub struct UpdateJobStatus { #[prost(string, tag="3")] pub error: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct UpdateWorkerStatus { #[prost(enumeration="WorkerStatus", optional, tag="1")] pub status: ::core::option::Option, @@ -3176,7 +3040,6 @@ pub struct UpdateWorkerStatus { #[prost(int32, tag="4")] pub job_count: i32, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct JobAssignment { #[prost(message, optional, tag="1")] @@ -3186,7 +3049,6 @@ pub struct JobAssignment { #[prost(string, tag="3")] pub token: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct JobTermination { #[prost(string, tag="1")] @@ -3276,7 +3138,6 @@ impl JobStatus { } } } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CreateAgentDispatchRequest { #[prost(string, tag="1")] @@ -3286,7 +3147,6 @@ pub struct CreateAgentDispatchRequest { #[prost(string, tag="3")] pub metadata: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RoomAgentDispatch { #[prost(string, tag="1")] @@ -3294,7 +3154,6 @@ pub struct RoomAgentDispatch { #[prost(string, tag="2")] pub metadata: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct DeleteAgentDispatchRequest { #[prost(string, tag="1")] @@ -3302,7 +3161,6 @@ pub struct DeleteAgentDispatchRequest { #[prost(string, tag="2")] pub room: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ListAgentDispatchRequest { /// if set, only the dispatch whose id is given will be returned @@ -3312,13 +3170,11 @@ pub struct ListAgentDispatchRequest { #[prost(string, tag="2")] pub room: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ListAgentDispatchResponse { #[prost(message, repeated, tag="1")] pub agent_dispatches: ::prost::alloc::vec::Vec, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct AgentDispatch { #[prost(string, tag="1")] @@ -3332,7 +3188,6 @@ pub struct AgentDispatch { #[prost(message, optional, tag="5")] pub state: ::core::option::Option, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct AgentDispatchState { /// For dispatches of tyoe JT_ROOM, there will be at most 1 job. @@ -3344,7 +3199,6 @@ pub struct AgentDispatchState { #[prost(int64, tag="3")] pub deleted_at: i64, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CreateRoomRequest { /// name of the room @@ -3389,7 +3243,6 @@ pub struct CreateRoomRequest { #[prost(bool, tag="13")] pub replay_enabled: bool, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RoomEgress { #[prost(message, optional, tag="1")] @@ -3399,50 +3252,42 @@ pub struct RoomEgress { #[prost(message, optional, tag="2")] pub tracks: ::core::option::Option, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RoomAgent { #[prost(message, repeated, tag="1")] pub dispatches: ::prost::alloc::vec::Vec, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ListRoomsRequest { /// when set, will only return rooms with name match #[prost(string, repeated, tag="1")] pub names: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ListRoomsResponse { #[prost(message, repeated, tag="1")] pub rooms: ::prost::alloc::vec::Vec, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct DeleteRoomRequest { /// name of the room #[prost(string, tag="1")] pub room: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct DeleteRoomResponse { } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ListParticipantsRequest { /// name of the room #[prost(string, tag="1")] pub room: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ListParticipantsResponse { #[prost(message, repeated, tag="1")] pub participants: ::prost::alloc::vec::Vec, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RoomParticipantIdentity { /// name of the room @@ -3452,11 +3297,9 @@ pub struct RoomParticipantIdentity { #[prost(string, tag="2")] pub identity: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct RemoveParticipantResponse { } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct MuteRoomTrackRequest { /// name of the room @@ -3471,13 +3314,11 @@ pub struct MuteRoomTrackRequest { #[prost(bool, tag="4")] pub muted: bool, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct MuteRoomTrackResponse { #[prost(message, optional, tag="1")] pub track: ::core::option::Option, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct UpdateParticipantRequest { #[prost(string, tag="1")] @@ -3498,7 +3339,6 @@ pub struct UpdateParticipantRequest { #[prost(map="string, string", tag="6")] pub attributes: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct UpdateSubscriptionsRequest { #[prost(string, tag="1")] @@ -3516,11 +3356,9 @@ pub struct UpdateSubscriptionsRequest { pub participant_tracks: ::prost::alloc::vec::Vec, } /// empty for now -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct UpdateSubscriptionsResponse { } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SendDataRequest { #[prost(string, tag="1")] @@ -3540,11 +3378,9 @@ pub struct SendDataRequest { pub topic: ::core::option::Option<::prost::alloc::string::String>, } /// -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct SendDataResponse { } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct UpdateRoomMetadataRequest { #[prost(string, tag="1")] @@ -3553,7 +3389,6 @@ pub struct UpdateRoomMetadataRequest { #[prost(string, tag="2")] pub metadata: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RoomConfiguration { /// Used as ID, must be unique @@ -3584,7 +3419,6 @@ pub struct RoomConfiguration { #[prost(bool, tag="9")] pub sync_streams: bool, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CreateIngressRequest { #[prost(enumeration="IngressInput", tag="1")] @@ -3619,7 +3453,6 @@ pub struct CreateIngressRequest { #[prost(message, optional, tag="7")] pub video: ::core::option::Option, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct IngressAudioOptions { #[prost(string, tag="1")] @@ -3631,8 +3464,7 @@ pub struct IngressAudioOptions { } /// Nested message and enum types in `IngressAudioOptions`. pub mod ingress_audio_options { - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Oneof)] + #[derive(Clone, Copy, PartialEq, ::prost::Oneof)] pub enum EncodingOptions { #[prost(enumeration="super::IngressAudioEncodingPreset", tag="3")] Preset(i32), @@ -3640,7 +3472,6 @@ pub mod ingress_audio_options { Options(super::IngressAudioEncodingOptions), } } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct IngressVideoOptions { #[prost(string, tag="1")] @@ -3652,8 +3483,7 @@ pub struct IngressVideoOptions { } /// Nested message and enum types in `IngressVideoOptions`. pub mod ingress_video_options { - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Oneof)] + #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum EncodingOptions { #[prost(enumeration="super::IngressVideoEncodingPreset", tag="3")] Preset(i32), @@ -3661,8 +3491,7 @@ pub mod ingress_video_options { Options(super::IngressVideoEncodingOptions), } } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct IngressAudioEncodingOptions { /// desired audio codec to publish to room #[prost(enumeration="AudioCodec", tag="1")] @@ -3674,7 +3503,6 @@ pub struct IngressAudioEncodingOptions { #[prost(uint32, tag="4")] pub channels: u32, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct IngressVideoEncodingOptions { /// desired codec to publish to room @@ -3686,7 +3514,6 @@ pub struct IngressVideoEncodingOptions { #[prost(message, repeated, tag="3")] pub layers: ::prost::alloc::vec::Vec, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct IngressInfo { #[prost(string, tag="1")] @@ -3726,7 +3553,6 @@ pub struct IngressInfo { #[prost(message, optional, tag="12")] pub state: ::core::option::Option, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct IngressState { #[prost(enumeration="ingress_state::Status", tag="1")] @@ -3790,7 +3616,6 @@ pub mod ingress_state { } } } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct InputVideoState { #[prost(string, tag="1")] @@ -3804,7 +3629,6 @@ pub struct InputVideoState { #[prost(double, tag="5")] pub framerate: f64, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct InputAudioState { #[prost(string, tag="1")] @@ -3816,7 +3640,6 @@ pub struct InputAudioState { #[prost(uint32, tag="4")] pub sample_rate: u32, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct UpdateIngressRequest { #[prost(string, tag="1")] @@ -3841,7 +3664,6 @@ pub struct UpdateIngressRequest { #[prost(message, optional, tag="7")] pub video: ::core::option::Option, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ListIngressRequest { /// when blank, lists all ingress endpoints @@ -3853,13 +3675,11 @@ pub struct ListIngressRequest { #[prost(string, tag="2")] pub ingress_id: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ListIngressResponse { #[prost(message, repeated, tag="1")] pub items: ::prost::alloc::vec::Vec, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct DeleteIngressRequest { #[prost(string, tag="1")] @@ -3983,7 +3803,6 @@ impl IngressVideoEncodingPreset { } } } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct WebhookEvent { /// one of room_started, room_finished, participant_joined, participant_left, @@ -4014,7 +3833,6 @@ pub struct WebhookEvent { #[prost(int32, tag="11")] pub num_dropped: i32, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CreateSipTrunkRequest { /// CIDR or IPs that traffic is accepted from @@ -4052,7 +3870,6 @@ pub struct CreateSipTrunkRequest { #[prost(string, tag="11")] pub metadata: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SipTrunkInfo { #[prost(string, tag="1")] @@ -4129,14 +3946,12 @@ pub mod sip_trunk_info { } } } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CreateSipInboundTrunkRequest { /// Trunk ID is ignored #[prost(message, optional, tag="1")] pub trunk: ::core::option::Option, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SipInboundTrunkInfo { #[prost(string, tag="1")] @@ -4166,14 +3981,12 @@ pub struct SipInboundTrunkInfo { #[prost(string, tag="8")] pub auth_password: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CreateSipOutboundTrunkRequest { /// Trunk ID is ignored #[prost(message, optional, tag="1")] pub trunk: ::core::option::Option, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SipOutboundTrunkInfo { #[prost(string, tag="1")] @@ -4201,43 +4014,35 @@ pub struct SipOutboundTrunkInfo { #[prost(string, tag="8")] pub auth_password: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct ListSipTrunkRequest { } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ListSipTrunkResponse { #[prost(message, repeated, tag="1")] pub items: ::prost::alloc::vec::Vec, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct ListSipInboundTrunkRequest { } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ListSipInboundTrunkResponse { #[prost(message, repeated, tag="1")] pub items: ::prost::alloc::vec::Vec, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct ListSipOutboundTrunkRequest { } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ListSipOutboundTrunkResponse { #[prost(message, repeated, tag="1")] pub items: ::prost::alloc::vec::Vec, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct DeleteSipTrunkRequest { #[prost(string, tag="1")] pub sip_trunk_id: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SipDispatchRuleDirect { /// What room should call be directed into @@ -4247,7 +4052,6 @@ pub struct SipDispatchRuleDirect { #[prost(string, tag="2")] pub pin: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SipDispatchRuleIndividual { /// Prefix used on new room name @@ -4257,7 +4061,6 @@ pub struct SipDispatchRuleIndividual { #[prost(string, tag="2")] pub pin: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SipDispatchRule { #[prost(oneof="sip_dispatch_rule::Rule", tags="1, 2")] @@ -4265,8 +4068,7 @@ pub struct SipDispatchRule { } /// Nested message and enum types in `SIPDispatchRule`. pub mod sip_dispatch_rule { - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Oneof)] + #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Rule { /// SIPDispatchRuleDirect is a `SIP Dispatch Rule` that puts a user directly into a room /// This places users into an existing room. Optionally you can require a pin before a user can @@ -4278,7 +4080,6 @@ pub mod sip_dispatch_rule { DispatchRuleIndividual(super::SipDispatchRuleIndividual), } } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CreateSipDispatchRuleRequest { #[prost(message, optional, tag="1")] @@ -4306,7 +4107,6 @@ pub struct CreateSipDispatchRuleRequest { #[prost(map="string, string", tag="7")] pub attributes: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SipDispatchRuleInfo { #[prost(string, tag="1")] @@ -4332,17 +4132,14 @@ pub struct SipDispatchRuleInfo { #[prost(map="string, string", tag="8")] pub attributes: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct ListSipDispatchRuleRequest { } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ListSipDispatchRuleResponse { #[prost(message, repeated, tag="1")] pub items: ::prost::alloc::vec::Vec, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct DeleteSipDispatchRuleRequest { #[prost(string, tag="1")] @@ -4350,7 +4147,6 @@ pub struct DeleteSipDispatchRuleRequest { } /// A SIP Participant is a singular SIP session connected to a LiveKit room via /// a SIP Trunk into a SIP DispatchRule -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CreateSipParticipantRequest { /// What SIP Trunk should be used to dial the user @@ -4386,7 +4182,6 @@ pub struct CreateSipParticipantRequest { #[prost(bool, tag="10")] pub hide_phone_number: bool, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SipParticipantInfo { #[prost(string, tag="1")] diff --git a/livekit-protocol/src/livekit.serde.rs b/livekit-protocol/src/livekit.serde.rs index c89e30ca6..62e7c6904 100644 --- a/livekit-protocol/src/livekit.serde.rs +++ b/livekit-protocol/src/livekit.serde.rs @@ -627,10 +627,12 @@ impl serde::Serialize for AgentDispatchState { } if self.created_at != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("createdAt", ToString::to_string(&self.created_at).as_str())?; } if self.deleted_at != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("deletedAt", ToString::to_string(&self.deleted_at).as_str())?; } struct_ser.end() @@ -5996,14 +5998,17 @@ impl serde::Serialize for EgressInfo { } if self.started_at != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("startedAt", ToString::to_string(&self.started_at).as_str())?; } if self.ended_at != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("endedAt", ToString::to_string(&self.ended_at).as_str())?; } if self.updated_at != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("updatedAt", ToString::to_string(&self.updated_at).as_str())?; } if !self.details.is_empty() { @@ -7322,18 +7327,22 @@ impl serde::Serialize for FileInfo { } if self.started_at != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("startedAt", ToString::to_string(&self.started_at).as_str())?; } if self.ended_at != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("endedAt", ToString::to_string(&self.ended_at).as_str())?; } if self.duration != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("duration", ToString::to_string(&self.duration).as_str())?; } if self.size != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("size", ToString::to_string(&self.size).as_str())?; } if !self.location.is_empty() { @@ -8183,14 +8192,17 @@ impl serde::Serialize for ImagesInfo { } if self.image_count != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("imageCount", ToString::to_string(&self.image_count).as_str())?; } if self.started_at != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("startedAt", ToString::to_string(&self.started_at).as_str())?; } if self.ended_at != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("endedAt", ToString::to_string(&self.ended_at).as_str())?; } struct_ser.end() @@ -9167,14 +9179,17 @@ impl serde::Serialize for IngressState { } if self.started_at != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("startedAt", ToString::to_string(&self.started_at).as_str())?; } if self.ended_at != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("endedAt", ToString::to_string(&self.ended_at).as_str())?; } if self.updated_at != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("updatedAt", ToString::to_string(&self.updated_at).as_str())?; } if !self.resource_id.is_empty() { @@ -10562,14 +10577,17 @@ impl serde::Serialize for JobState { } if self.started_at != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("startedAt", ToString::to_string(&self.started_at).as_str())?; } if self.ended_at != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("endedAt", ToString::to_string(&self.ended_at).as_str())?; } if self.updated_at != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("updatedAt", ToString::to_string(&self.updated_at).as_str())?; } if !self.participant_identity.is_empty() { @@ -11050,6 +11068,7 @@ impl serde::Serialize for JoinResponse { } if !self.sif_trailer.is_empty() { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("sifTrailer", pbjson::private::base64::encode(&self.sif_trailer).as_str())?; } struct_ser.end() @@ -13957,6 +13976,7 @@ impl serde::Serialize for ParticipantInfo { } if self.joined_at != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("joinedAt", ToString::to_string(&self.joined_at).as_str())?; } if !self.name.is_empty() { @@ -14422,7 +14442,7 @@ impl serde::Serialize for ParticipantPermission { let v = self.can_publish_sources.iter().cloned().map(|v| { TrackSource::try_from(v) .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", v))) - }).collect::, _>>()?; + }).collect::, _>>()?; struct_ser.serialize_field("canPublishSources", &v)?; } if self.hidden { @@ -14825,10 +14845,12 @@ impl serde::Serialize for Ping { let mut struct_ser = serializer.serialize_struct("livekit.Ping", len)?; if self.timestamp != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("timestamp", ToString::to_string(&self.timestamp).as_str())?; } if self.rtt != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("rtt", ToString::to_string(&self.rtt).as_str())?; } struct_ser.end() @@ -15076,10 +15098,12 @@ impl serde::Serialize for Pong { let mut struct_ser = serializer.serialize_struct("livekit.Pong", len)?; if self.last_ping_timestamp != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("lastPingTimestamp", ToString::to_string(&self.last_ping_timestamp).as_str())?; } if self.timestamp != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("timestamp", ToString::to_string(&self.timestamp).as_str())?; } struct_ser.end() @@ -15354,18 +15378,22 @@ impl serde::Serialize for RtpDrift { } if self.start_timestamp != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("startTimestamp", ToString::to_string(&self.start_timestamp).as_str())?; } if self.end_timestamp != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("endTimestamp", ToString::to_string(&self.end_timestamp).as_str())?; } if self.rtp_clock_ticks != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("rtpClockTicks", ToString::to_string(&self.rtp_clock_ticks).as_str())?; } if self.drift_samples != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("driftSamples", ToString::to_string(&self.drift_samples).as_str())?; } if self.drift_ms != 0. { @@ -15602,14 +15630,17 @@ impl serde::Serialize for RtpForwarderState { } if self.pre_start_time != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("preStartTime", ToString::to_string(&self.pre_start_time).as_str())?; } if self.ext_first_timestamp != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("extFirstTimestamp", ToString::to_string(&self.ext_first_timestamp).as_str())?; } if self.dummy_start_timestamp_offset != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("dummyStartTimestampOffset", ToString::to_string(&self.dummy_start_timestamp_offset).as_str())?; } if let Some(v) = self.rtp_munger.as_ref() { @@ -15812,18 +15843,22 @@ impl serde::Serialize for RtpMungerState { let mut struct_ser = serializer.serialize_struct("livekit.RTPMungerState", len)?; if self.ext_last_sequence_number != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("extLastSequenceNumber", ToString::to_string(&self.ext_last_sequence_number).as_str())?; } if self.ext_second_last_sequence_number != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("extSecondLastSequenceNumber", ToString::to_string(&self.ext_second_last_sequence_number).as_str())?; } if self.ext_last_timestamp != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("extLastTimestamp", ToString::to_string(&self.ext_last_timestamp).as_str())?; } if self.ext_second_last_timestamp != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("extSecondLastTimestamp", ToString::to_string(&self.ext_second_last_timestamp).as_str())?; } if self.last_marker { @@ -16139,10 +16174,12 @@ impl serde::Serialize for RtpStats { } if self.bytes != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("bytes", ToString::to_string(&self.bytes).as_str())?; } if self.header_bytes != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("headerBytes", ToString::to_string(&self.header_bytes).as_str())?; } if self.bitrate != 0. { @@ -16165,10 +16202,12 @@ impl serde::Serialize for RtpStats { } if self.bytes_duplicate != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("bytesDuplicate", ToString::to_string(&self.bytes_duplicate).as_str())?; } if self.header_bytes_duplicate != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("headerBytesDuplicate", ToString::to_string(&self.header_bytes_duplicate).as_str())?; } if self.bitrate_duplicate != 0. { @@ -16182,10 +16221,12 @@ impl serde::Serialize for RtpStats { } if self.bytes_padding != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("bytesPadding", ToString::to_string(&self.bytes_padding).as_str())?; } if self.header_bytes_padding != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("headerBytesPadding", ToString::to_string(&self.header_bytes_padding).as_str())?; } if self.bitrate_padding != 0. { @@ -17140,6 +17181,7 @@ impl serde::Serialize for RegionInfo { } if self.distance != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("distance", ToString::to_string(&self.distance).as_str())?; } struct_ser.end() @@ -17990,6 +18032,7 @@ impl serde::Serialize for Room { } if self.creation_time != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("creationTime", ToString::to_string(&self.creation_time).as_str())?; } if !self.turn_password.is_empty() { @@ -21717,10 +21760,12 @@ impl serde::Serialize for SegmentsInfo { } if self.duration != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("duration", ToString::to_string(&self.duration).as_str())?; } if self.size != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("size", ToString::to_string(&self.size).as_str())?; } if !self.playlist_location.is_empty() { @@ -21731,14 +21776,17 @@ impl serde::Serialize for SegmentsInfo { } if self.segment_count != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("segmentCount", ToString::to_string(&self.segment_count).as_str())?; } if self.started_at != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("startedAt", ToString::to_string(&self.started_at).as_str())?; } if self.ended_at != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("endedAt", ToString::to_string(&self.ended_at).as_str())?; } struct_ser.end() @@ -21958,6 +22006,7 @@ impl serde::Serialize for SendDataRequest { } if !self.data.is_empty() { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("data", pbjson::private::base64::encode(&self.data).as_str())?; } if self.kind != 0 { @@ -22778,6 +22827,7 @@ impl serde::Serialize for SignalRequest { } signal_request::Message::Ping(v) => { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("ping", ToString::to_string(&v).as_str())?; } signal_request::Message::UpdateMetadata(v) => { @@ -23107,6 +23157,7 @@ impl serde::Serialize for SignalResponse { } signal_response::Message::Pong(v) => { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("pong", ToString::to_string(&v).as_str())?; } signal_response::Message::Reconnect(v) => { @@ -23663,6 +23714,7 @@ impl serde::Serialize for SimulateScenario { } simulate_scenario::Scenario::SubscriberBandwidth(v) => { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("subscriberBandwidth", ToString::to_string(&v).as_str())?; } simulate_scenario::Scenario::DisconnectSignalOnResume(v) => { @@ -24563,14 +24615,17 @@ impl serde::Serialize for StreamInfo { } if self.started_at != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("startedAt", ToString::to_string(&self.started_at).as_str())?; } if self.ended_at != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("endedAt", ToString::to_string(&self.ended_at).as_str())?; } if self.duration != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("duration", ToString::to_string(&self.duration).as_str())?; } if self.status != 0 { @@ -26373,6 +26428,7 @@ impl serde::Serialize for TimedVersion { let mut struct_ser = serializer.serialize_struct("livekit.TimedVersion", len)?; if self.unix_micro != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("unixMicro", ToString::to_string(&self.unix_micro).as_str())?; } if self.ticks != 0 { @@ -27041,7 +27097,7 @@ impl serde::Serialize for TrackInfo { let v = self.audio_features.iter().cloned().map(|v| { AudioTrackFeature::try_from(v) .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", v))) - }).collect::, _>>()?; + }).collect::, _>>()?; struct_ser.serialize_field("audioFeatures", &v)?; } struct_ser.end() @@ -28104,10 +28160,12 @@ impl serde::Serialize for TranscriptionSegment { } if self.start_time != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("startTime", ToString::to_string(&self.start_time).as_str())?; } if self.end_time != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("endTime", ToString::to_string(&self.end_time).as_str())?; } if self.r#final { @@ -28910,7 +28968,7 @@ impl serde::Serialize for UpdateLocalAudioTrack { let v = self.features.iter().cloned().map(|v| { AudioTrackFeature::try_from(v) .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", v))) - }).collect::, _>>()?; + }).collect::, _>>()?; struct_ser.serialize_field("features", &v)?; } struct_ser.end() @@ -30585,6 +30643,7 @@ impl serde::Serialize for UserPacket { } if !self.payload.is_empty() { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("payload", pbjson::private::base64::encode(&self.payload).as_str())?; } if !self.destination_sids.is_empty() { @@ -30601,10 +30660,12 @@ impl serde::Serialize for UserPacket { } if let Some(v) = self.start_time.as_ref() { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("startTime", ToString::to_string(&v).as_str())?; } if let Some(v) = self.end_time.as_ref() { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("endTime", ToString::to_string(&v).as_str())?; } struct_ser.end() @@ -31789,6 +31850,7 @@ impl serde::Serialize for WebhookEvent { } if self.created_at != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("createdAt", ToString::to_string(&self.created_at).as_str())?; } if self.num_dropped != 0 { @@ -32166,6 +32228,7 @@ impl serde::Serialize for WorkerPing { let mut struct_ser = serializer.serialize_struct("livekit.WorkerPing", len)?; if self.timestamp != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("timestamp", ToString::to_string(&self.timestamp).as_str())?; } struct_ser.end() @@ -32267,10 +32330,12 @@ impl serde::Serialize for WorkerPong { let mut struct_ser = serializer.serialize_struct("livekit.WorkerPong", len)?; if self.last_timestamp != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("lastTimestamp", ToString::to_string(&self.last_timestamp).as_str())?; } if self.timestamp != 0 { #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("timestamp", ToString::to_string(&self.timestamp).as_str())?; } struct_ser.end() diff --git a/livekit-runtime/.nanparc b/livekit-runtime/.nanparc new file mode 100644 index 000000000..80b849a2a --- /dev/null +++ b/livekit-runtime/.nanparc @@ -0,0 +1,2 @@ +version 0.3.0 +language rust diff --git a/livekit/.nanparc b/livekit/.nanparc new file mode 100644 index 000000000..1cc55e1ba --- /dev/null +++ b/livekit/.nanparc @@ -0,0 +1,2 @@ +version 0.6.0 +language rust diff --git a/webrtc-sys/.nanparc b/webrtc-sys/.nanparc new file mode 100644 index 000000000..d431df850 --- /dev/null +++ b/webrtc-sys/.nanparc @@ -0,0 +1,2 @@ +version 0.3.5 +language rust diff --git a/webrtc-sys/build/.nanparc b/webrtc-sys/build/.nanparc new file mode 100644 index 000000000..d431df850 --- /dev/null +++ b/webrtc-sys/build/.nanparc @@ -0,0 +1,2 @@ +version 0.3.5 +language rust From 2e4d38570af13958fd0f4e18a51617f87c8a47da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Sat, 21 Sep 2024 21:50:46 -0700 Subject: [PATCH 039/274] soxr-sys (#438) --- .nanparc | 2 +- Cargo.lock | 2 +- soxr-sys/.nanparc | 2 + soxr-sys/Cargo.lock | 32 + soxr-sys/Cargo.toml | 14 + soxr-sys/build.rs | 40 + soxr-sys/generate_bindings.sh | 1 + soxr-sys/src/LICENCE | 23 + soxr-sys/src/aliases.h | 39 + soxr-sys/src/avfft32.c | 33 + soxr-sys/src/avfft32s.c | 32 + soxr-sys/src/ccrw2.h | 75 ++ soxr-sys/src/cr-core.c | 314 ++++++ soxr-sys/src/cr.c | 588 ++++++++++ soxr-sys/src/cr.h | 178 +++ soxr-sys/src/cr32.c | 8 + soxr-sys/src/cr32s.c | 8 + soxr-sys/src/cr64.c | 8 + soxr-sys/src/cr64s.c | 8 + soxr-sys/src/data-io.c | 223 ++++ soxr-sys/src/data-io.h | 39 + soxr-sys/src/dbesi0.c | 149 +++ soxr-sys/src/dev32s.h | 54 + soxr-sys/src/dev64s.h | 42 + soxr-sys/src/fft4g.c | 1346 +++++++++++++++++++++++ soxr-sys/src/fft4g.h | 23 + soxr-sys/src/fft4g32.c | 36 + soxr-sys/src/fft4g32s.c | 31 + soxr-sys/src/fft4g64.c | 35 + soxr-sys/src/fft4g_cache.h | 92 ++ soxr-sys/src/fifo.h | 125 +++ soxr-sys/src/filter.c | 277 +++++ soxr-sys/src/filter.h | 44 + soxr-sys/src/half-coefs.h | 75 ++ soxr-sys/src/half-fir.h | 61 ++ soxr-sys/src/internal.h | 84 ++ soxr-sys/src/lib.rs | 152 +++ soxr-sys/src/math-wrap.h | 31 + soxr-sys/src/pffft-avx.h | 40 + soxr-sys/src/pffft-wrap.c | 110 ++ soxr-sys/src/pffft.c | 1946 +++++++++++++++++++++++++++++++++ soxr-sys/src/pffft.h | 197 ++++ soxr-sys/src/pffft32.c | 39 + soxr-sys/src/pffft32s.c | 34 + soxr-sys/src/pffft64s.c | 34 + soxr-sys/src/poly-fir.h | 150 +++ soxr-sys/src/poly-fir0.h | 56 + soxr-sys/src/rdft.h | 31 + soxr-sys/src/rdft_t.h | 24 + soxr-sys/src/rint-clip.h | 158 +++ soxr-sys/src/rint.h | 102 ++ soxr-sys/src/samplerate.h | 1 + soxr-sys/src/soxr-config.h | 28 + soxr-sys/src/soxr-lsr.c | 198 ++++ soxr-sys/src/soxr-lsr.h | 78 ++ soxr-sys/src/soxr.c | 842 ++++++++++++++ soxr-sys/src/soxr.h | 344 ++++++ soxr-sys/src/soxr.rs | 403 +++++++ soxr-sys/src/std-types.h | 48 + soxr-sys/src/util-simd.c | 89 ++ soxr-sys/src/util32s.c | 8 + soxr-sys/src/util32s.h | 23 + soxr-sys/src/util64s.c | 8 + soxr-sys/src/util64s.h | 23 + soxr-sys/src/vr-coefs.c | 115 ++ soxr-sys/src/vr-coefs.h | 94 ++ soxr-sys/src/vr32.c | 651 +++++++++++ 67 files changed, 10198 insertions(+), 2 deletions(-) create mode 100644 soxr-sys/.nanparc create mode 100644 soxr-sys/Cargo.lock create mode 100644 soxr-sys/Cargo.toml create mode 100644 soxr-sys/build.rs create mode 100755 soxr-sys/generate_bindings.sh create mode 100644 soxr-sys/src/LICENCE create mode 100644 soxr-sys/src/aliases.h create mode 100644 soxr-sys/src/avfft32.c create mode 100644 soxr-sys/src/avfft32s.c create mode 100644 soxr-sys/src/ccrw2.h create mode 100644 soxr-sys/src/cr-core.c create mode 100644 soxr-sys/src/cr.c create mode 100644 soxr-sys/src/cr.h create mode 100644 soxr-sys/src/cr32.c create mode 100644 soxr-sys/src/cr32s.c create mode 100644 soxr-sys/src/cr64.c create mode 100644 soxr-sys/src/cr64s.c create mode 100644 soxr-sys/src/data-io.c create mode 100644 soxr-sys/src/data-io.h create mode 100644 soxr-sys/src/dbesi0.c create mode 100644 soxr-sys/src/dev32s.h create mode 100644 soxr-sys/src/dev64s.h create mode 100644 soxr-sys/src/fft4g.c create mode 100644 soxr-sys/src/fft4g.h create mode 100644 soxr-sys/src/fft4g32.c create mode 100644 soxr-sys/src/fft4g32s.c create mode 100644 soxr-sys/src/fft4g64.c create mode 100644 soxr-sys/src/fft4g_cache.h create mode 100644 soxr-sys/src/fifo.h create mode 100644 soxr-sys/src/filter.c create mode 100644 soxr-sys/src/filter.h create mode 100644 soxr-sys/src/half-coefs.h create mode 100644 soxr-sys/src/half-fir.h create mode 100644 soxr-sys/src/internal.h create mode 100644 soxr-sys/src/lib.rs create mode 100644 soxr-sys/src/math-wrap.h create mode 100644 soxr-sys/src/pffft-avx.h create mode 100644 soxr-sys/src/pffft-wrap.c create mode 100644 soxr-sys/src/pffft.c create mode 100644 soxr-sys/src/pffft.h create mode 100644 soxr-sys/src/pffft32.c create mode 100644 soxr-sys/src/pffft32s.c create mode 100644 soxr-sys/src/pffft64s.c create mode 100644 soxr-sys/src/poly-fir.h create mode 100644 soxr-sys/src/poly-fir0.h create mode 100644 soxr-sys/src/rdft.h create mode 100644 soxr-sys/src/rdft_t.h create mode 100644 soxr-sys/src/rint-clip.h create mode 100644 soxr-sys/src/rint.h create mode 100644 soxr-sys/src/samplerate.h create mode 100644 soxr-sys/src/soxr-config.h create mode 100644 soxr-sys/src/soxr-lsr.c create mode 100644 soxr-sys/src/soxr-lsr.h create mode 100644 soxr-sys/src/soxr.c create mode 100644 soxr-sys/src/soxr.h create mode 100644 soxr-sys/src/soxr.rs create mode 100644 soxr-sys/src/std-types.h create mode 100644 soxr-sys/src/util-simd.c create mode 100644 soxr-sys/src/util32s.c create mode 100644 soxr-sys/src/util32s.h create mode 100644 soxr-sys/src/util64s.c create mode 100644 soxr-sys/src/util64s.h create mode 100644 soxr-sys/src/vr-coefs.c create mode 100644 soxr-sys/src/vr-coefs.h create mode 100644 soxr-sys/src/vr32.c diff --git a/.nanparc b/.nanparc index d7523fefa..748a8a360 100644 --- a/.nanparc +++ b/.nanparc @@ -1 +1 @@ -packages livekit livekit-ffi livekit-protocol livekit-runtime livekit-api libwebrtc webrtc-sys webrtc-sys/build +packages livekit livekit-ffi livekit-protocol livekit-runtime livekit-api libwebrtc webrtc-sys webrtc-sys/build soxr-sys diff --git a/Cargo.lock b/Cargo.lock index 91bcfced1..4cce07499 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1591,7 +1591,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.10.1" +version = "0.10.2" dependencies = [ "console-subscriber", "dashmap", diff --git a/soxr-sys/.nanparc b/soxr-sys/.nanparc new file mode 100644 index 000000000..2124a88b3 --- /dev/null +++ b/soxr-sys/.nanparc @@ -0,0 +1,2 @@ +version 0.1.0 +language rust diff --git a/soxr-sys/Cargo.lock b/soxr-sys/Cargo.lock new file mode 100644 index 000000000..63cf08bf5 --- /dev/null +++ b/soxr-sys/Cargo.lock @@ -0,0 +1,32 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "cc" +version = "1.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07b1695e2c7e8fc85310cde85aeaab7e3097f593c91d209d3f9df76c928100f0" +dependencies = [ + "shlex", +] + +[[package]] +name = "hound" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "soxr-rs" +version = "0.1.0" +dependencies = [ + "cc", + "hound", +] diff --git a/soxr-sys/Cargo.toml b/soxr-sys/Cargo.toml new file mode 100644 index 000000000..6661f1e24 --- /dev/null +++ b/soxr-sys/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "soxr-sys" +version = "0.1.0" +authors = ["Theo Monnom . + + +Notes + +1. Re software in the `examples' directory: works that are not resampling +examples but are based on the given examples -- for example, applications using +the library -- shall not be considered to be derivative works of the examples. + +2. If building with pffft.c, see the licence embedded in that file. diff --git a/soxr-sys/src/aliases.h b/soxr-sys/src/aliases.h new file mode 100644 index 000000000..d1a392f6e --- /dev/null +++ b/soxr-sys/src/aliases.h @@ -0,0 +1,39 @@ +/* SoX Resampler Library Copyright (c) 2007-16 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +#if defined SOXR_LIB + +#define lsx_bessel_I_0 _soxr_bessel_I_0 +#define lsx_cdft_f _soxr_cdft_f +#define lsx_cdft _soxr_cdft +#define lsx_clear_fft_cache_f _soxr_clear_fft_cache_f +#define lsx_clear_fft_cache _soxr_clear_fft_cache +#define lsx_ddct_f _soxr_ddct_f +#define lsx_ddct _soxr_ddct +#define lsx_ddst_f _soxr_ddst_f +#define lsx_ddst _soxr_ddst +#define lsx_design_lpf _soxr_design_lpf +#define lsx_dfct_f _soxr_dfct_f +#define lsx_dfct _soxr_dfct +#define lsx_dfst_f _soxr_dfst_f +#define lsx_dfst _soxr_dfst +#define lsx_fir_to_phase _soxr_fir_to_phase +#define lsx_f_resp _soxr_f_resp +#define lsx_init_fft_cache_f _soxr_init_fft_cache_f +#define lsx_init_fft_cache _soxr_init_fft_cache +#define lsx_inv_f_resp _soxr_inv_f_resp +#define lsx_kaiser_beta _soxr_kaiser_beta +#define lsx_kaiser_params _soxr_kaiser_params +#define lsx_make_lpf _soxr_make_lpf +#define lsx_ordered_convolve_f _soxr_ordered_convolve_f +#define lsx_ordered_convolve _soxr_ordered_convolve +#define lsx_ordered_partial_convolve_f _soxr_ordered_partial_convolve_f +#define lsx_ordered_partial_convolve _soxr_ordered_partial_convolve +#define lsx_rdft_f _soxr_rdft_f +#define lsx_rdft _soxr_rdft +#define lsx_safe_cdft_f _soxr_safe_cdft_f +#define lsx_safe_cdft _soxr_safe_cdft +#define lsx_safe_rdft_f _soxr_safe_rdft_f +#define lsx_safe_rdft _soxr_safe_rdft + +#endif diff --git a/soxr-sys/src/avfft32.c b/soxr-sys/src/avfft32.c new file mode 100644 index 000000000..fe651f5db --- /dev/null +++ b/soxr-sys/src/avfft32.c @@ -0,0 +1,33 @@ +/* SoX Resampler Library Copyright (c) 2007-13 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +#include +#include +#include +#include "filter.h" +#include "rdft_t.h" + +static void * forward_setup(int len) {return av_rdft_init((int)(log(len)/log(2)+.5),DFT_R2C);} +static void * backward_setup(int len) {return av_rdft_init((int)(log(len)/log(2)+.5),IDFT_C2R);} +static void rdft(int length, void * setup, float * h) {av_rdft_calc(setup, h); (void)length;} +static int multiplier(void) {return 2;} +static void nothing(void) {} +static int flags(void) {return 0;} + +fn_t _soxr_rdft32_cb[] = { + (fn_t)forward_setup, + (fn_t)backward_setup, + (fn_t)av_rdft_end, + (fn_t)rdft, + (fn_t)rdft, + (fn_t)rdft, + (fn_t)rdft, + (fn_t)_soxr_ordered_convolve_f, + (fn_t)_soxr_ordered_partial_convolve_f, + (fn_t)multiplier, + (fn_t)nothing, + (fn_t)malloc, + (fn_t)calloc, + (fn_t)free, + (fn_t)flags, +}; diff --git a/soxr-sys/src/avfft32s.c b/soxr-sys/src/avfft32s.c new file mode 100644 index 000000000..5a7e62db2 --- /dev/null +++ b/soxr-sys/src/avfft32s.c @@ -0,0 +1,32 @@ +/* SoX Resampler Library Copyright (c) 2007-13 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +#include +#include +#include "util32s.h" +#include "rdft_t.h" + +static void * forward_setup(int len) {return av_rdft_init((int)(log(len)/log(2)+.5),DFT_R2C);} +static void * backward_setup(int len) {return av_rdft_init((int)(log(len)/log(2)+.5),IDFT_C2R);} +static void rdft(int length, void * setup, float * h) {av_rdft_calc(setup, h); (void)length;} +static int multiplier(void) {return 2;} +static void nothing(void) {} +static int flags(void) {return RDFT_IS_SIMD;} + +fn_t _soxr_rdft32s_cb[] = { + (fn_t)forward_setup, + (fn_t)backward_setup, + (fn_t)av_rdft_end, + (fn_t)rdft, + (fn_t)rdft, + (fn_t)rdft, + (fn_t)rdft, + (fn_t)ORDERED_CONVOLVE_SIMD, + (fn_t)ORDERED_PARTIAL_CONVOLVE_SIMD, + (fn_t)multiplier, + (fn_t)nothing, + (fn_t)SIMD_ALIGNED_MALLOC, + (fn_t)SIMD_ALIGNED_CALLOC, + (fn_t)SIMD_ALIGNED_FREE, + (fn_t)flags, +}; diff --git a/soxr-sys/src/ccrw2.h b/soxr-sys/src/ccrw2.h new file mode 100644 index 000000000..09331a4b1 --- /dev/null +++ b/soxr-sys/src/ccrw2.h @@ -0,0 +1,75 @@ +/* SoX Resampler Library Copyright (c) 2007-13 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +/* Concurrent Control with "Readers" and "Writers", P.J. Courtois et al, 1971 */ + +#if !defined soxr_ccrw2_included +#define soxr_ccrw2_included + +#if defined SOXR_LIB +#include "internal.h" +#endif + +#if defined _OPENMP + +#include + +typedef struct { + int readcount, writecount; /* initial value = 0 */ + omp_lock_t mutex_1, mutex_2, mutex_3, w, r; /* initial value = 1 */ +} ccrw2_t; /* Problem #2: `writers-preference' */ + +#define ccrw2_become_reader(p) do {\ + omp_set_lock(&p.mutex_3);\ + omp_set_lock(&p.r);\ + omp_set_lock(&p.mutex_1);\ + if (++p.readcount == 1) omp_set_lock(&p.w);\ + omp_unset_lock(&p.mutex_1);\ + omp_unset_lock(&p.r);\ + omp_unset_lock(&p.mutex_3);\ +} while (0) +#define ccrw2_cease_reading(p) do {\ + omp_set_lock(&p.mutex_1);\ + if (!--p.readcount) omp_unset_lock(&p.w);\ + omp_unset_lock(&p.mutex_1);\ +} while (0) +#define ccrw2_become_writer(p) do {\ + omp_set_lock(&p.mutex_2);\ + if (++p.writecount == 1) omp_set_lock(&p.r);\ + omp_unset_lock(&p.mutex_2);\ + omp_set_lock(&p.w);\ +} while (0) +#define ccrw2_cease_writing(p) do {\ + omp_unset_lock(&p.w);\ + omp_set_lock(&p.mutex_2);\ + if (!--p.writecount) omp_unset_lock(&p.r);\ + omp_unset_lock(&p.mutex_2);\ +} while (0) +#define ccrw2_init(p) do {\ + omp_init_lock(&p.mutex_1);\ + omp_init_lock(&p.mutex_2);\ + omp_init_lock(&p.mutex_3);\ + omp_init_lock(&p.w);\ + omp_init_lock(&p.r);\ +} while (0) +#define ccrw2_clear(p) do {\ + omp_destroy_lock(&p.r);\ + omp_destroy_lock(&p.w);\ + omp_destroy_lock(&p.mutex_3);\ + omp_destroy_lock(&p.mutex_2);\ + omp_destroy_lock(&p.mutex_1);\ +} while (0) + +#else + +typedef int ccrw2_t; +#define ccrw2_become_reader(x) (void)(x) +#define ccrw2_cease_reading(x) (void)(x) +#define ccrw2_become_writer(x) (void)(x) +#define ccrw2_cease_writing(x) (void)(x) +#define ccrw2_init(x) (void)(x) +#define ccrw2_clear(x) (void)(x) + +#endif /* _OPENMP */ + +#endif diff --git a/soxr-sys/src/cr-core.c b/soxr-sys/src/cr-core.c new file mode 100644 index 000000000..159a5d976 --- /dev/null +++ b/soxr-sys/src/cr-core.c @@ -0,0 +1,314 @@ +/* SoX Resampler Library Copyright (c) 2007-18 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. + * + * Constant-rate resampling engine-specific code. */ + +#include +#include +#include +#include + +#include "filter.h" + +#if defined SOXR_LIB + #include "internal.h" + #include "cr.h" + #if CORE_TYPE & CORE_DBL + typedef double sample_t; + #if CORE_TYPE & CORE_SIMD_DFT + #define RDFT_CB _soxr_rdft64s_cb + #else + #define RDFT_CB _soxr_rdft64_cb + #endif + #else + typedef float sample_t; + #if CORE_TYPE & CORE_SIMD_DFT + #define RDFT_CB _soxr_rdft32s_cb + #else + #define RDFT_CB _soxr_rdft32_cb + #endif + #endif + + #if CORE_TYPE & (CORE_SIMD_POLY|CORE_SIMD_HALF|CORE_SIMD_DFT) + #if CORE_TYPE & CORE_DBL + #include "util64s.h" + #include "dev64s.h" + #else + #include "util32s.h" + #include "dev32s.h" + #endif + #endif + + extern fn_t RDFT_CB[]; +#else + #define RDFT_CB 0 +#endif + + + +static void cubic_stage_fn(stage_t * p, fifo_t * output_fifo) +{ + sample_t const * input = stage_read_p(p); + int num_in = min(stage_occupancy(p), p->input_size); + int i, max_num_out = 1 + (int)(num_in * p->out_in_ratio); + sample_t * output = fifo_reserve(output_fifo, max_num_out); + + for (i = 0; p->at.integer < num_in; ++i, p->at.whole += p->step.whole) { + sample_t const * s = input + p->at.integer; + double x = p->at.fraction * (1 / MULT32); + double b = .5*(s[1]+s[-1])-*s, a = (1/6.)*(s[2]-s[1]+s[-1]-*s-4*b); + double c = s[1]-*s-a-b; + output[i] = (sample_t)(p->mult * (((a*x + b)*x + c)*x + *s)); + } + assert(max_num_out - i >= 0); + fifo_trim_by(output_fifo, max_num_out - i); + fifo_read(&p->fifo, p->at.integer, NULL); + p->at.integer = 0; +} + + + +#if defined __AVX__ + #define DEFINED_AVX 1 +#else + #define DEFINED_AVX 0 +#endif + +#if defined __x86_64__ || defined _M_X64 || defined i386 || defined _M_IX86 + #define DEFINED_X86 1 +#else + #define DEFINED_X86 0 +#endif + +#if defined __arm__ + #define DEFINED_ARM 1 +#else + #define DEFINED_ARM 0 +#endif + + + +#if CORE_TYPE & CORE_DBL + #define SIMD_AVX ((CORE_TYPE & CORE_SIMD_HALF) && DEFINED_AVX) + #define SIMD_SSE 0 +#else + #define SIMD_SSE ((CORE_TYPE & CORE_SIMD_HALF) && DEFINED_X86) + #define SIMD_AVX 0 +#endif + +#define SIMD_NEON ((CORE_TYPE & CORE_SIMD_HALF) && DEFINED_ARM) + + + +#include "half-coefs.h" + +#if !(CORE_TYPE & CORE_SIMD_HALF) +#define FUNCTION_H h7 +#define CONVOLVE ____ __ _ +#include "half-fir.h" +#endif + +#define FUNCTION_H h8 +#define CONVOLVE ____ ____ +#include "half-fir.h" + +#define FUNCTION_H h9 +#define CONVOLVE ____ ____ _ +#include "half-fir.h" + +#if CORE_TYPE & CORE_DBL + #define FUNCTION_H h10 + #define CONVOLVE ____ ____ __ + #include "half-fir.h" + + #define FUNCTION_H h11 + #define CONVOLVE ____ ____ __ _ + #include "half-fir.h" + + #define FUNCTION_H h12 + #define CONVOLVE ____ ____ ____ + #include "half-fir.h" + + #define FUNCTION_H h13 + #define CONVOLVE ____ ____ ____ _ + #include "half-fir.h" +#endif + +static half_fir_info_t const half_firs[] = { +#if !(CORE_TYPE & CORE_SIMD_HALF) + { 7, half_fir_coefs_7 , h7 , 0 , 120.65f}, +#endif + { 8, half_fir_coefs_8 , h8 , 0 , 136.51f}, + { 9, half_fir_coefs_9 , h9 , 0 , 152.32f}, +#if CORE_TYPE & CORE_DBL + {10, half_fir_coefs_10, h10, 0 , 168.08f}, + {11, half_fir_coefs_11, h11, 0 , 183.79f}, + {12, half_fir_coefs_12, h12, 0 , 199.46f}, + {13, half_fir_coefs_13, h13, 0 , 215.12f}, +#endif +}; + +#undef SIMD_AVX +#undef SIMD_NEON +#undef SIMD_SSE + + + +#if CORE_TYPE & CORE_DBL + #define SIMD_AVX ((CORE_TYPE & CORE_SIMD_POLY) && DEFINED_AVX) + #define SIMD_SSE 0 +#else + #define SIMD_SSE ((CORE_TYPE & CORE_SIMD_POLY) && DEFINED_X86) + #define SIMD_AVX 0 +#endif + +#define SIMD_NEON ((CORE_TYPE & CORE_SIMD_POLY) && DEFINED_ARM) + + + +#define COEFS (sample_t * __restrict)p->shared->poly_fir_coefs +#define VAR_LENGTH p->n +#define VAR_CONVOLVE(n) while (j < (n)) _ +#define VAR_POLY_PHASE_BITS p->phase_bits + + + +#define FUNCTION vpoly0 +#define FIR_LENGTH VAR_LENGTH +#define CONVOLVE(n) VAR_CONVOLVE(n) +#include "poly-fir0.h" + +#define FUNCTION vpoly1 +#define COEF_INTERP 1 +#define PHASE_BITS VAR_POLY_PHASE_BITS +#define FIR_LENGTH VAR_LENGTH +#define CONVOLVE(n) VAR_CONVOLVE(n) +#include "poly-fir.h" + +#define FUNCTION vpoly2 +#define COEF_INTERP 2 +#define PHASE_BITS VAR_POLY_PHASE_BITS +#define FIR_LENGTH VAR_LENGTH +#define CONVOLVE(n) VAR_CONVOLVE(n) +#include "poly-fir.h" + +#define FUNCTION vpoly3 +#define COEF_INTERP 3 +#define PHASE_BITS VAR_POLY_PHASE_BITS +#define FIR_LENGTH VAR_LENGTH +#define CONVOLVE(n) VAR_CONVOLVE(n) +#include "poly-fir.h" + + + +#if !(CORE_TYPE & CORE_SIMD_POLY) + +#define poly_fir_convolve_U100 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ +#define FUNCTION U100_0 +#define FIR_LENGTH U100_l +#define CONVOLVE(n) poly_fir_convolve_U100 +#include "poly-fir0.h" + +#define u100_l 11 +#define poly_fir_convolve_u100 _ _ _ _ _ _ _ _ _ _ _ +#define FUNCTION u100_0 +#define FIR_LENGTH u100_l +#define CONVOLVE(n) poly_fir_convolve_u100 +#include "poly-fir0.h" + +#define FUNCTION u100_1 +#define COEF_INTERP 1 +#define PHASE_BITS 8 +#define FIR_LENGTH u100_l +#define CONVOLVE(n) poly_fir_convolve_u100 +#include "poly-fir.h" + +#define FUNCTION u100_2 +#define COEF_INTERP 2 +#define PHASE_BITS 6 +#define FIR_LENGTH u100_l +#define CONVOLVE(n) poly_fir_convolve_u100 +#include "poly-fir.h" + +#endif + +#define u100_1_b 8 +#define u100_2_b 6 + + + +static poly_fir_t const poly_firs[] = { + {-1, {{0, vpoly0}, { 7.2f, vpoly1}, {5.0f, vpoly2}}}, + {-1, {{0, vpoly0}, { 9.4f, vpoly1}, {6.7f, vpoly2}}}, + {-1, {{0, vpoly0}, {12.4f, vpoly1}, {7.8f, vpoly2}}}, + {-1, {{0, vpoly0}, {13.6f, vpoly1}, {9.3f, vpoly2}}}, + {-1, {{0, vpoly0}, {10.5f, vpoly2}, {8.4f, vpoly3}}}, + {-1, {{0, vpoly0}, {11.85f,vpoly2}, {9.0f, vpoly3}}}, + + {-1, {{0, vpoly0}, { 8.0f, vpoly1}, {5.3f, vpoly2}}}, + {-1, {{0, vpoly0}, { 8.6f, vpoly1}, {5.7f, vpoly2}}}, + {-1, {{0, vpoly0}, {10.6f, vpoly1}, {6.75f,vpoly2}}}, + {-1, {{0, vpoly0}, {12.6f, vpoly1}, {8.6f, vpoly2}}}, + {-1, {{0, vpoly0}, { 9.6f, vpoly2}, {7.6f, vpoly3}}}, + {-1, {{0, vpoly0}, {11.4f, vpoly2}, {8.65f,vpoly3}}}, + +#if CORE_TYPE & CORE_SIMD_POLY + {10.62f, {{0, vpoly0}, {0, 0}, {0, 0}}}, + {-1, {{0, vpoly0}, {u100_1_b, vpoly1}, {u100_2_b, vpoly2}}}, +#else + {10.62f, {{U100_l, U100_0}, {0, 0}, {0, 0}}}, + {11.28f, {{u100_l, u100_0}, {u100_1_b, u100_1}, {u100_2_b, u100_2}}}, +#endif + {-1, {{0, vpoly0}, { 9, vpoly1}, { 6, vpoly2}}}, + {-1, {{0, vpoly0}, { 11, vpoly1}, { 7, vpoly2}}}, + {-1, {{0, vpoly0}, { 13, vpoly1}, { 8, vpoly2}}}, + {-1, {{0, vpoly0}, { 10, vpoly2}, { 8, vpoly3}}}, + {-1, {{0, vpoly0}, { 12, vpoly2}, { 9, vpoly3}}}, +}; + + + +static cr_core_t const cr_core = { + +#if CORE_TYPE & CORE_SIMD_POLY + {SIMD_ALIGNED_MALLOC, SIMD_ALIGNED_CALLOC, SIMD_ALIGNED_FREE}, +#else + {malloc, calloc, free}, +#endif + half_firs, array_length(half_firs), + 0, 0, + cubic_stage_fn, + poly_firs, RDFT_CB +}; + + + +#if defined SOXR_LIB + +#include "soxr.h" + +static char const * rate_create(void * channel, void * shared, double io_ratio, + soxr_quality_spec_t * q_spec, soxr_runtime_spec_t * r_spec, double scale) +{ + return _soxr_init(channel, shared, io_ratio, q_spec, r_spec, scale, + &cr_core, CORE_TYPE); +} + + + +static char const * id(void) {return CORE_STR;} + +fn_t RATE_CB[] = { + (fn_t)_soxr_input, + (fn_t)_soxr_process, + (fn_t)_soxr_output, + (fn_t)_soxr_flush, + (fn_t)_soxr_close, + (fn_t)_soxr_delay, + (fn_t)_soxr_sizes, + (fn_t)rate_create, + (fn_t)0, + (fn_t)id, +}; + +#endif diff --git a/soxr-sys/src/cr.c b/soxr-sys/src/cr.c new file mode 100644 index 000000000..4122db3ce --- /dev/null +++ b/soxr-sys/src/cr.c @@ -0,0 +1,588 @@ +/* SoX Resampler Library Copyright (c) 2007-16 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. + * + * Constant-rate resampling common code. */ + +#include +#include +#include +#include + +#include "filter.h" + +#if defined SOXR_LIB + #include "internal.h" + #define STATIC +#endif + +#include "cr.h" + +#define num_coefs4 ((core_flags&CORE_SIMD_POLY)? ((num_coefs+3)&~3) : num_coefs) + +#define coef_coef(C,T,x) \ + C((T*)result, interp_order, num_coefs4, j, x, num_coefs4 - 1 - i) + +#define STORE(C,T) { \ + if (interp_order > 2) coef_coef(C,T,3) = (T)d; \ + if (interp_order > 1) coef_coef(C,T,2) = (T)c; \ + if (interp_order > 0) coef_coef(C,T,1) = (T)b; \ + coef_coef(C,T,0) = (T)f0;} + +static real * prepare_poly_fir_coefs(double const * coefs, int num_coefs, + int num_phases, int interp_order, double multiplier, + core_flags_t core_flags, alloc_t const * mem) +{ + int i, j, length = num_coefs4 * num_phases * (interp_order + 1); + real * result = mem->calloc(1,(size_t)length << LOG2_SIZEOF_REAL(core_flags)); + double fm1 = coefs[0], f1 = 0, f2 = 0; + + for (i = num_coefs - 1; i >= 0; --i) + for (j = num_phases - 1; j >= 0; --j) { + double f0 = fm1, b = 0, c = 0, d = 0; /* = 0 to kill compiler warning */ + int pos = i * num_phases + j - 1; + fm1 = pos > 0 ? coefs[pos - 1] * multiplier : 0; + switch (interp_order) { + case 1: b = f1 - f0; break; + case 2: b = f1 - (.5 * (f2+f0) - f1) - f0; c = .5 * (f2+f0) - f1; break; + case 3: c=.5*(f1+fm1)-f0;d=(1/6.)*(f2-f1+fm1-f0-4*c);b=f1-f0-d-c; break; + default: assert(!interp_order); + } + switch (core_flags & 3) { + case 0: if (WITH_CR32 ) STORE(coef , float ); break; + case 1: if (WITH_CR64 ) STORE(coef , double); break; + case 2: if (WITH_CR32S) STORE(coef4, float ); break; + default:if (WITH_CR64S) STORE(coef4, double); break; + } + f2 = f1, f1 = f0; + } + return result; +} + +#undef STORE +#undef coef_coef + +#define IS_FLOAT32 (WITH_CR32 || WITH_CR32S) && \ + (!(WITH_CR64 || WITH_CR64S) || sizeof_real == sizeof(float)) +#define WITH_FLOAT64 WITH_CR64 || WITH_CR64S + +static void dft_stage_fn(stage_t * p, fifo_t * output_fifo) +{ + real * output, * dft_out; + int i, j, num_in = max(0, fifo_occupancy(&p->fifo)); + rate_shared_t const * s = p->shared; + dft_filter_t const * f = &s->dft_filter[p->dft_filter_num]; + int const overlap = f->num_taps - 1; + + if (p->at.integer + p->L * num_in >= f->dft_length) { + fn_t const * const RDFT_CB = p->rdft_cb; + size_t const sizeof_real = sizeof(char) << LOG2_SIZEOF_REAL(p->core_flags); + div_t divd = div(f->dft_length - overlap - p->at.integer + p->L - 1, p->L); + real const * input = fifo_read_ptr(&p->fifo); + fifo_read(&p->fifo, divd.quot, NULL); + num_in -= divd.quot; + + output = fifo_reserve(output_fifo, f->dft_length); + dft_out = (p->core_flags & CORE_SIMD_DFT)? p->dft_out : output; + + if (lsx_is_power_of_2(p->L)) { /* F-domain */ + int portion = f->dft_length / p->L; + memcpy(dft_out, input, (unsigned)portion * sizeof_real); + rdft_oforward(portion, f->dft_forward_setup, dft_out, p->dft_scratch); + if (IS_FLOAT32) { +#define dft_out ((float *)dft_out) + for (i = portion + 2; i < (portion << 1); i += 2) /* Mirror image. */ + dft_out[i] = dft_out[(portion << 1) - i], + dft_out[i+1] = -dft_out[(portion << 1) - i + 1]; + dft_out[portion] = dft_out[1]; + dft_out[portion + 1] = 0; + dft_out[1] = dft_out[0]; +#undef dft_out + } + else if (WITH_FLOAT64) { +#define dft_out ((double *)dft_out) + for (i = portion + 2; i < (portion << 1); i += 2) /* Mirror image. */ + dft_out[i] = dft_out[(portion << 1) - i], + dft_out[i+1] = -dft_out[(portion << 1) - i + 1]; + dft_out[portion] = dft_out[1]; + dft_out[portion + 1] = 0; + dft_out[1] = dft_out[0]; +#undef dft_out + } + + for (portion <<= 1; i < f->dft_length; i += portion, portion <<= 1) { + memcpy((char *)dft_out + (size_t)i * sizeof_real, dft_out, (size_t)portion * sizeof_real); + if (IS_FLOAT32) + #define dft_out ((float *)dft_out) + dft_out[i + 1] = 0; + #undef dft_out + else if (WITH_FLOAT64) + #define dft_out ((double *)dft_out) + dft_out[i + 1] = 0; + #undef dft_out + } + if (p->step.integer > 0) + rdft_reorder_back(f->dft_length, f->dft_backward_setup, dft_out, p->dft_scratch); + } else { + if (p->L == 1) + memcpy(dft_out, input, (size_t)f->dft_length * sizeof_real); + else { + memset(dft_out, 0, (size_t)f->dft_length * sizeof_real); + if (IS_FLOAT32) + for (j = 0, i = p->at.integer; i < f->dft_length; ++j, i += p->L) + ((float *)dft_out)[i] = ((float *)input)[j]; + else if (WITH_FLOAT64) + for (j = 0, i = p->at.integer; i < f->dft_length; ++j, i += p->L) + ((double *)dft_out)[i] = ((double *)input)[j]; + p->at.integer = p->L - 1 - divd.rem; + } + if (p->step.integer > 0) + rdft_forward(f->dft_length, f->dft_forward_setup, dft_out, p->dft_scratch); + else + rdft_oforward(f->dft_length, f->dft_forward_setup, dft_out, p->dft_scratch); + } + + if (p->step.integer > 0) { + rdft_convolve(f->dft_length, f->dft_backward_setup, dft_out, f->coefs); + rdft_backward(f->dft_length, f->dft_backward_setup, dft_out, p->dft_scratch); + if ((p->core_flags & CORE_SIMD_DFT) && p->step.integer == 1) + memcpy(output, dft_out, (size_t)f->dft_length * sizeof_real); + if (p->step.integer != 1) { + if (IS_FLOAT32) + for (j = 0, i = p->remM; i < f->dft_length - overlap; ++j, + i += p->step.integer) + ((float *)output)[j] = ((float *)dft_out)[i]; + else if (WITH_FLOAT64) + for (j = 0, i = p->remM; i < f->dft_length - overlap; ++j, + i += p->step.integer) + ((double *)output)[j] = ((double *)dft_out)[i]; + p->remM = i - (f->dft_length - overlap); + fifo_trim_by(output_fifo, f->dft_length - j); + } + else fifo_trim_by(output_fifo, overlap); + } + else { /* F-domain */ + int m = -p->step.integer; + rdft_convolve_portion(f->dft_length >> m, dft_out, f->coefs); + rdft_obackward(f->dft_length >> m, f->dft_backward_setup, dft_out, p->dft_scratch); + if (p->core_flags & CORE_SIMD_DFT) + memcpy(output, dft_out, (size_t)(f->dft_length >> m) * sizeof_real); + fifo_trim_by(output_fifo, (((1 << m) - 1) * f->dft_length + overlap) >>m); + } + (void)RDFT_CB; + } + p->input_size = (f->dft_length - p->at.integer + p->L - 1) / p->L; +} + +/* Set to 4 x nearest power of 2 or half of that */ +/* if danger of causing too many cache misses. */ +static int set_dft_length(int num_taps, int min, int large) +{ + double d = log((double)num_taps) / log(2.); + return 1 << range_limit((int)(d + 2.77), min, max((int)(d + 1.77), large)); +} + +static void dft_stage_init( + unsigned instance, double Fp, double Fs, double Fn, double att, + double phase_response, stage_t * p, int L, int M, double * multiplier, + unsigned min_dft_size, unsigned large_dft_size, core_flags_t core_flags, + fn_t const * RDFT_CB) +{ + dft_filter_t * f = &p->shared->dft_filter[instance]; + int num_taps = 0, dft_length = f->dft_length, i, offset; + bool f_domain_m = abs(3-M) == 1 && Fs <= 1; + size_t const sizeof_real = sizeof(char) << LOG2_SIZEOF_REAL(core_flags); + + if (!dft_length) { + int k = phase_response == 50 && lsx_is_power_of_2(L) && Fn == L? L << 1 : 4; + double m, * h = lsx_design_lpf(Fp, Fs, Fn, att, &num_taps, -k, -1.); + + if (phase_response != 50) + lsx_fir_to_phase(&h, &num_taps, &f->post_peak, phase_response); + else f->post_peak = num_taps / 2; + + dft_length = set_dft_length(num_taps, (int)min_dft_size, (int)large_dft_size); + f->coefs = rdft_calloc((size_t)dft_length, sizeof_real); + offset = dft_length - num_taps + 1; + m = (1. / dft_length) * rdft_multiplier() * L * *multiplier; + if (IS_FLOAT32) for (i = 0; i < num_taps; ++i) + ((float *)f->coefs)[(i + offset) & (dft_length - 1)] =(float)(h[i] * m); + else if (WITH_FLOAT64) for (i = 0; i < num_taps; ++i) + ((double *)f->coefs)[(i + offset) & (dft_length - 1)] = h[i] * m; + free(h); + } + + if (rdft_flags() & RDFT_IS_SIMD) + p->dft_out = rdft_malloc(sizeof_real * (size_t)dft_length); + if (rdft_flags() & RDFT_NEEDS_SCRATCH) + p->dft_scratch = rdft_malloc(2 * sizeof_real * (size_t)dft_length); + + if (!f->dft_length) { + void * coef_setup = rdft_forward_setup(dft_length); + int Lp = lsx_is_power_of_2(L)? L : 1; + int Mp = f_domain_m? M : 1; + f->dft_forward_setup = rdft_forward_setup(dft_length / Lp); + f->dft_backward_setup = rdft_backward_setup(dft_length / Mp); + if (Mp == 1) + rdft_forward(dft_length, coef_setup, f->coefs, p->dft_scratch); + else + rdft_oforward(dft_length, coef_setup, f->coefs, p->dft_scratch); + rdft_delete_setup(coef_setup); + f->num_taps = num_taps; + f->dft_length = dft_length; + lsx_debug("fir_len=%i dft_length=%i Fp=%g Fs=%g Fn=%g att=%g %i/%i", + num_taps, dft_length, Fp, Fs, Fn, att, L, M); + } + *multiplier = 1; + p->out_in_ratio = (double)L / M; + p->core_flags = core_flags; + p->rdft_cb = RDFT_CB; + p->fn = dft_stage_fn; + p->preload = f->post_peak / L; + p->at.integer = f->post_peak % L; + p->L = L; + p->step.integer = f_domain_m? -M/2 : M; + p->dft_filter_num = instance; + p->block_len = f->dft_length - (f->num_taps - 1); + p->phase0 = p->at.integer / p->L; + p->input_size = (f->dft_length - p->at.integer + p->L - 1) / p->L; +} + +static struct half_fir_info const * find_half_fir( + struct half_fir_info const * firs, size_t len, double att) +{ + size_t i; + for (i = 0; i + 1 < len && att > firs[i].att; ++i); + return &firs[i]; +} + +#define have_pre_stage (preM * preL != 1) +#define have_arb_stage (arbM * arbL != 1) +#define have_post_stage (postM * postL != 1) + +#include "soxr.h" + +STATIC char const * _soxr_init( + rate_t * const p, /* Per audio channel. */ + rate_shared_t * const shared, /* By channels undergoing same rate change. */ + double const io_ratio, /* Input rate divided by output rate. */ + soxr_quality_spec_t const * const q_spec, + soxr_runtime_spec_t const * const r_spec, + double multiplier, /* Linear gain to apply during conversion. */ + cr_core_t const * const core, + core_flags_t const core_flags) +{ + size_t const sizeof_real = sizeof(char) << LOG2_SIZEOF_REAL(core_flags); + double const tolerance = 1 + 1e-5; + + double bits = q_spec->precision; + rolloff_t const rolloff = (rolloff_t)(q_spec->flags & 3); + int interpolator = (int)(r_spec->flags & 3) - 1; + double const Fp0 = q_spec->passband_end, Fs0 = q_spec->stopband_begin; + double const phase_response = q_spec->phase_response, tbw0 = Fs0-Fp0; + + bool const maintain_3dB_pt = !!(q_spec->flags & SOXR_MAINTAIN_3DB_PT); + double tbw_tighten = 1, alpha; + #define tighten(x) (Fs0-(Fs0-(x))*tbw_tighten) + + double arbM = io_ratio, Fn1, Fp1 = Fp0, Fs1 = Fs0, bits1 = min(bits,33); + double att = (bits1 + 1) * linear_to_dB(2.), attArb = att; /* +1: pass+stop */ + int preL = 1, preM = 1, shr = 0, arbL = 1, postL = 1, postM = 1; + bool upsample=false, rational=false, iOpt=!(r_spec->flags&SOXR_NOSMALLINTOPT); + bool lq_bits= (q_spec->flags & SOXR_PROMOTE_TO_LQ)? bits <= 16 : bits == 16; + bool lq_Fp0 = (q_spec->flags & SOXR_PROMOTE_TO_LQ)? Fp0<=lq_bw0 : Fp0==lq_bw0; + int n = 0, i, mode = lq_bits && rolloff == rolloff_medium? io_ratio > 1 || + phase_response != 50 || !lq_Fp0 || Fs0 != 1 : ((int)ceil(bits1) - 6) / 4; + struct half_fir_info const * half_fir_info; + stage_t * s; + + if (io_ratio < 1 && Fs0 - 1 > 1 - Fp0 / tolerance) + return "imaging greater than rolloff"; + if (.002 / tolerance > tbw0 || tbw0 > .5 * tolerance) + return "transition bandwidth not in [0.2,50] % of nyquist"; + if (.5 / tolerance > Fp0 || Fs0 > 1.5 * tolerance) + return "transition band not within [50,150] % of nyquist"; + if (bits!=0 && (15 > bits || bits > 33)) + return "precision not in [15,33] bits"; + if (io_ratio <= 0) + return "resampling factor not positive"; + if (0 > phase_response || phase_response > 100) + return "phase response not in [0=min-phase,100=max-phase] %"; + + p->core = core; + p->io_ratio = io_ratio; + if (bits!=0) while (!n++) { /* Determine stages: */ + int try, L, M, x, maxL = interpolator > 0? 1 : mode? 2048 : + (int)ceil(r_spec->coef_size_kbytes * 1000. / (U100_l * (int)sizeof_real)); + double d, epsilon = 0, frac; + upsample = arbM < 1; + for (i = (int)(.5 * arbM), shr = 0; i >>= 1; arbM *= .5, ++shr); + preM = upsample || (arbM > 1.5 && arbM < 2); + postM = 1 + (arbM > 1 && preM), arbM /= postM; + preL = 1 + (!preM && arbM < 2) + (upsample && mode), arbM *= preL; + if ((frac = arbM - (int)arbM)!=0) + epsilon = fabs(floor(frac * MULT32 + .5) / (frac * MULT32) - 1); + for (i = 1, rational = frac==0; i <= maxL && !rational; ++i) { + d = frac * i, try = (int)(d + .5); + if ((rational = fabs(try / d - 1) <= epsilon)) { /* No long doubles! */ + if (try == i) + arbM = ceil(arbM), shr += x = arbM > 3, arbM /= 1 + x; + else arbM = i * (int)arbM + try, arbL = i; + } + } + L = preL * arbL, M = (int)(arbM * postM), x = (L|M)&1, L >>= !x, M >>= !x; + if (iOpt && postL == 1 && (d = preL * arbL / arbM) > 4 && d != 5) { + for (postL = 4, i = (int)(d / 16); (i >>= 1) && postL < 256; postL <<= 1); + arbM = arbM * postL / arbL / preL, arbL = 1, n = 0; + } else if (rational && (max(L, M) < 3 + 2 * iOpt || L * M < 6 * iOpt)) + preL = L, preM = M, arbM = arbL = postM = 1; + if (!mode && (!rational || !n)) + ++mode, n = 0; + } + + p->num_stages = shr + have_pre_stage + have_arb_stage + have_post_stage; + if (!p->num_stages && multiplier != 1) { + bits = arbL = 0; /* Use cubic_stage in this case. */ + ++p->num_stages; + } + p->stages = calloc((size_t)p->num_stages + 1, sizeof(*p->stages)); + if (!p->stages) + return "out of memory"; + for (i = 0; i < p->num_stages; ++i) { + p->stages[i].num = i; + p->stages[i].shared = shared; + p->stages[i].input_size = 8192; + } + p->stages[0].is_input = true; + + alpha = postM / (io_ratio * (postL << 0)); + + if ((n = p->num_stages) > 1) { /* Att. budget: */ + if (have_arb_stage) + att += linear_to_dB(2.), attArb = att, --n; + att += linear_to_dB((double)n); + } + + half_fir_info = find_half_fir(core->half_firs, core->half_firs_len, att); + for (i = 0, s = p->stages; i < shr; ++i, ++s) { + s->fn = half_fir_info->fn; + s->coefs = half_fir_info->coefs; + s->n = half_fir_info->num_coefs; + s->pre_post = 4 * s->n; + s->preload = s->pre = s->pre_post >> 1; + } + + if (have_pre_stage) { + if (maintain_3dB_pt && have_post_stage) { /* Trans. bands overlapping. */ + double x = tbw0 * lsx_inv_f_resp(-3., att); + x = -lsx_f_resp(x / (max(2 * alpha - Fs0, alpha) - Fp0), att); + if (x > .035) { + tbw_tighten = ((4.3074e-3 - 3.9121e-4 * x) * x - .040009) * x + 1.0014; + lsx_debug("tbw_tighten=%g (%gdB)", tbw_tighten, x); + } + } + Fn1 = preM? max(preL, preM) : arbM / arbL; + dft_stage_init(0, tighten(Fp1), Fs1, Fn1, att, phase_response, s++, preL, + max(preM, 1), &multiplier, r_spec->log2_min_dft_size, + r_spec->log2_large_dft_size, core_flags, core->rdft_cb); + Fp1 /= Fn1, Fs1 /= Fn1; + } + + if (bits==0 && have_arb_stage) { /* `Quick' cubic arb stage: */ + s->fn = core->cubic_stage_fn; + s->mult = multiplier, multiplier = 1; + s->step.whole = (int64_t)(arbM * MULT32 + .5); + s->pre_post = max(3, s->step.integer); + s->preload = s->pre = 1; + s->out_in_ratio = MULT32 / (double)s->step.whole; + } + else if (have_arb_stage) { /* Higher quality arb stage: */ + static const float rolloffs[] = {-.01f, -.3f, 0, -.103f}; + poly_fir_t const * f = &core->poly_firs[6*(upsample+!!preM)+mode-!upsample]; + int order, num_coefs = (int)f->interp[0].scalar, phase_bits, phases; + size_t coefs_size; + double at, Fp = Fp1, Fs, Fn, mult = upsample? 1 : arbM / arbL; + poly_fir1_t const * f1; + + if (!upsample && preM) + Fn = 2 * mult, Fs = 3 + fabs(Fs1 - 1); + else Fn = 1, Fs = 2 - (mode? Fp1 + (Fs1 - Fp1) * .7 : Fs1); + + if (mode) + Fp = Fs - (Fs - Fp) / (1 - lsx_inv_f_resp(rolloffs[rolloff], attArb)); + + i = (interpolator < 0? !rational : max(interpolator, !rational)) - 1; + do { + f1 = &f->interp[++i]; + assert(f1->fn); + if (i) + arbM /= arbL, arbL = 1, rational = false; + phase_bits = (int)ceil(f1->scalar - log(mult)/log(2.)); + phases = !rational? (1 << phase_bits) : arbL; + if (f->interp[0].scalar==0) { + int phases0 = max(phases, 19), n0 = 0; + lsx_design_lpf(Fp, Fs, -Fn, attArb, &n0, phases0, f->beta); + num_coefs = n0 / phases0 + 1, num_coefs += num_coefs & !preM; + } + if ((num_coefs & 1) && rational && (arbL & 1)) + phases <<= 1, arbL <<= 1, arbM *= 2; + at = arbL * (s->phase0 = .5 * (num_coefs & 1)); + order = i + (i && mode > 4); + coefs_size = (size_t)(num_coefs4 * phases * (order+1)) * sizeof_real; + } while (interpolator < 0 && i < 2 && f->interp[i+1].fn && + coefs_size / 1000 > r_spec->coef_size_kbytes); + + if (!s->shared->poly_fir_coefs) { + int num_taps = num_coefs * phases - 1; + double * coefs = lsx_design_lpf( + Fp, Fs, Fn, attArb, &num_taps, phases, f->beta); + s->shared->poly_fir_coefs = prepare_poly_fir_coefs( + coefs, num_coefs, phases, order, multiplier, core_flags, &core->mem); + lsx_debug("fir_len=%i phases=%i coef_interp=%i size=%.3gk", + num_coefs, phases, order, (double)coefs_size / 1000.); + free(coefs); + } + multiplier = 1; + s->fn = f1->fn; + s->pre_post = num_coefs4 - 1; + s->preload = ((num_coefs - 1) >> 1) + (num_coefs4 - num_coefs); + s->n = num_coefs4; + s->phase_bits = phase_bits; + s->L = arbL; + s->use_hi_prec_clock = + mode>1 && (q_spec->flags & SOXR_HI_PREC_CLOCK) && !rational; +#if WITH_FLOAT_STD_PREC_CLOCK + if (order && !s->use_hi_prec_clock) { + s->at.flt = at; + s->step.flt = arbM; + s->out_in_ratio = (double)(arbL / s->step.flt); + } else +#endif + { + s->at.whole = (int64_t)(at * MULT32 + .5); +#if WITH_HI_PREC_CLOCK + if (s->use_hi_prec_clock) { + double M = arbM * MULT32; + s->at.fix.ls.parts.ms = 0x80000000ul; + s->step.whole = (int64_t)M; + M -= (double)s->step.whole; + M *= MULT32 * MULT32; + s->step.fix.ls.all = (uint64_t)M; + } else +#endif + s->step.whole = (int64_t)(arbM * MULT32 + .5); + s->out_in_ratio = MULT32 * arbL / (double)s->step.whole; + } + ++s; + } + + if (have_post_stage) + dft_stage_init(1, tighten(Fp0 / (upsample? alpha : 1)), upsample? max(2 - + Fs0 / alpha, 1) : Fs0, (double)max(postL, postM), att, phase_response, + s++, postL, postM, &multiplier, r_spec->log2_min_dft_size, + r_spec->log2_large_dft_size, core_flags, core->rdft_cb); + + lsx_debug("%g: >>%i %i/%i %i/%g %i/%i (%x)", 1/io_ratio, + shr, preL, preM, arbL, arbM, postL, postM, core_flags); + + for (i = 0, s = p->stages; i < p->num_stages; ++i, ++s) { + fifo_create(&s->fifo, (int)sizeof_real); + memset(fifo_reserve(&s->fifo, s->preload), 0, + sizeof_real * (size_t)s->preload); + lsx_debug_more("%5i|%-5i preload=%i remL=%i", + s->pre, s->pre_post-s->pre, s->preload, s->at.integer); + } + fifo_create(&s->fifo, (int)sizeof_real); + return 0; +} + +static bool stage_process(stage_t * stage, bool flushing) +{ + fifo_t * fifo = &stage->fifo; + bool done = false; + int want; + while (!done && (want = stage->input_size - fifo_occupancy(fifo)) > 0) { + if (stage->is_input) { + if (flushing) + memset(fifo_reserve(fifo, want), 0, fifo->item_size * (size_t)want); + else done = true; + } + else done = stage_process(stage - 1, flushing); + } + stage->fn(stage, &stage[1].fifo); + return done && fifo_occupancy(fifo) < stage->input_size; +} + +STATIC void _soxr_process(rate_t * p, size_t olen) +{ + int const n = p->flushing? min(-(int)p->samples_out, (int)olen) : (int)olen; + stage_t * stage = &p->stages[p->num_stages]; + fifo_t * fifo = &stage->fifo; + bool done = false; + while (!done && fifo_occupancy(fifo) < (int)n) + done = stage->is_input || stage_process(stage - 1, p->flushing); +} + +STATIC real * _soxr_input(rate_t * p, real const * samples, size_t n) +{ + if (p->flushing) + return 0; + p->samples_in += (int64_t)n; + return fifo_write(&p->stages[0].fifo, (int)n, samples); +} + +STATIC real const * _soxr_output(rate_t * p, real * samples, size_t * n0) +{ + fifo_t * fifo = &p->stages[p->num_stages].fifo; + int n = p->flushing? min(-(int)p->samples_out, (int)*n0) : (int)*n0; + p->samples_out += n = min(n, fifo_occupancy(fifo)); + return fifo_read(fifo, (int)(*n0 = (size_t)n), samples); +} + +STATIC void _soxr_flush(rate_t * p) +{ + if (p->flushing) return; + p->samples_out -= (int64_t)((double)p->samples_in / p->io_ratio + .5); + p->samples_in = 0; + p->flushing = true; +} + +STATIC void _soxr_close(rate_t * p) +{ + if (p->stages) { + fn_t const * const RDFT_CB = p->core->rdft_cb; + rate_shared_t * shared = p->stages[0].shared; + int i; + + for (i = 0; i <= p->num_stages; ++i) { + stage_t * s = &p->stages[i]; + rdft_free(s->dft_scratch); + rdft_free(s->dft_out); + fifo_delete(&s->fifo); + } + if (shared) { + for (i = 0; i < 2; ++i) { + dft_filter_t * f= &shared->dft_filter[i]; + rdft_free(f->coefs); + rdft_delete_setup(f->dft_forward_setup); + rdft_delete_setup(f->dft_backward_setup); + } + p->core->mem.free(shared->poly_fir_coefs); + memset(shared, 0, sizeof(*shared)); + } + free(p->stages); + (void)RDFT_CB; + } +} + +#if defined SOXR_LIB +STATIC double _soxr_delay(rate_t * p) +{ + return (double)p->samples_in / p->io_ratio - (double)p->samples_out; +} + +STATIC void _soxr_sizes(size_t * shared, size_t * channel) +{ + *shared = sizeof(rate_shared_t); + *channel = sizeof(rate_t); +} +#endif diff --git a/soxr-sys/src/cr.h b/soxr-sys/src/cr.h new file mode 100644 index 000000000..d6e863799 --- /dev/null +++ b/soxr-sys/src/cr.h @@ -0,0 +1,178 @@ +/* SoX Resampler Library Copyright (c) 2007-16 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +#if !defined soxr_cr_included +#define soxr_cr_included + +#define FIFO_SIZE_T int +#include "fifo.h" + +typedef void real; /* float or double */ +struct stage; +typedef void (* stage_fn_t)(struct stage * input, fifo_t * output); +typedef struct half_fir_info { + int num_coefs; + real const * coefs; + stage_fn_t fn, dfn; + float att; +} half_fir_info_t; +typedef struct {float scalar; stage_fn_t fn;} poly_fir1_t; +typedef struct {float beta; poly_fir1_t interp[3];} poly_fir_t; + +#define U100_l 42 +#define MULT32 (65536. * 65536.) + +/* Conceptually: coef_p is &coefs[num_phases][fir_len][interp_order+1]: */ +#define coef(coef_p, interp_order, fir_len, phase_num, coef_interp_num, fir_coef_num) (coef_p)[\ + (fir_len) * ((interp_order) + 1) * (phase_num) + \ + ((interp_order) + 1) * (fir_coef_num) + \ + ((interp_order) - (coef_interp_num))] + +/* Conceptually: coef_p is &coefs[num_phases][fir_len/4][interp_order+1][4]: */ +#define coef4(coef_p, interp_order, fir_len, phase_num, coef_interp_num, fir_coef_num) (coef_p)[\ + (fir_len) * ((interp_order) + 1) * (phase_num) + \ + ((interp_order) + 1) * ((fir_coef_num) & ~3) + \ + 4 * ((interp_order) - (coef_interp_num)) + \ + ((fir_coef_num) & 3)] + +typedef union { /* Int64 in parts */ + #if HAVE_BIGENDIAN + struct {int32_t ms; uint32_t ls;} parts; + #else + struct {uint32_t ls; int32_t ms;} parts; + #endif + int64_t all; +} int64p_t; + +typedef union { /* Uint64 in parts */ + #if HAVE_BIGENDIAN + struct {uint32_t ms, ls;} parts; + #else + struct {uint32_t ls, ms;} parts; + #endif + uint64_t all; +} uint64p_t; + +typedef struct { + int dft_length, num_taps, post_peak; + void * dft_forward_setup, * dft_backward_setup; + real * coefs; +} dft_filter_t; + +typedef struct { /* So generated filter coefs may be shared between channels */ + real * poly_fir_coefs; + dft_filter_t dft_filter[2]; +} rate_shared_t; + +typedef double float_step_t; /* Or long double or __float128. */ + +typedef union { /* Fixed point arithmetic */ + struct {uint64p_t ls; int64p_t ms;} fix; /* Hi-prec has ~96 bits. */ + float_step_t flt; +} step_t; + +#define integer fix.ms.parts.ms +#define fraction fix.ms.parts.ls +#define whole fix.ms.all + +#define CORE_DBL 1 +#define CORE_SIMD_POLY 2 +#define CORE_SIMD_HALF 4 +#define CORE_SIMD_DFT 8 +#define LOG2_SIZEOF_REAL(core_flags) (2 + ((core_flags) & 1)) + +typedef int core_flags_t; + +#if defined SOXR_LIB +#include "rdft_t.h" +#else +typedef void fn_t; +#endif + +typedef struct stage { + int num; + + /* Common to all stage types: */ + core_flags_t core_flags; + stage_fn_t fn; + fifo_t fifo; + int pre; /* Number of past samples to store */ + int pre_post; /* pre + number of future samples to store */ + int preload; /* Number of zero samples to pre-load the fifo */ + double out_in_ratio; /* For buffer management. */ + int input_size; + bool is_input; + + /* For a stage with variable (run-time generated) filter coefs: */ + fn_t const * rdft_cb; + rate_shared_t * shared; + unsigned dft_filter_num; /* Which, if any, of the 2 DFT filters to use */ + real * dft_scratch; + float * dft_out; + real const * coefs; + + /* For a stage with variable L/M: */ + step_t at, step; + bool use_hi_prec_clock; + int L, remM; + int n, phase_bits, block_len; + double mult, phase0; +} stage_t; + +#define stage_occupancy(s) max(0, fifo_occupancy(&(s)->fifo) - (s)->pre_post) +#define stage_read_p(s) ((sample_t *)fifo_read_ptr(&(s)->fifo) + (s)->pre) + +#define lq_bw0 (1385/2048.) /* ~.67625, FP exact. */ + +typedef enum {rolloff_small, rolloff_medium, rolloff_none} rolloff_t; + +typedef struct { + void * (* alloc)(size_t); + void * (* calloc)(size_t, size_t); + void (* free)(void *); +} alloc_t; + +typedef struct { + alloc_t mem; + half_fir_info_t const * half_firs; + size_t half_firs_len; + half_fir_info_t const * doub_firs; + size_t doub_firs_len; + stage_fn_t cubic_stage_fn; + poly_fir_t const * poly_firs; + fn_t * rdft_cb; +} cr_core_t; + +typedef struct rate rate_t; +struct rate { + cr_core_t const * core; + double io_ratio; + int64_t samples_in, samples_out; + int num_stages, flushing; + stage_t * stages; +}; + +#if defined SOXR_LIB + +#include "soxr.h" + +char const * _soxr_init( + rate_t * const p, /* Per audio channel. */ + rate_shared_t * const shared, /* Between channels (undergoing same rate change)*/ + double const io_ratio, /* Input rate divided by output rate. */ + soxr_quality_spec_t const * const q_spec, + soxr_runtime_spec_t const * const r_spec, + double multiplier, /* Linear gain to apply during conversion. 1 */ + cr_core_t const * const core, + core_flags_t const); + +void _soxr_process(struct rate * p, size_t olen); +real * _soxr_input(struct rate * p, real const * samples, size_t n); +real const * _soxr_output(struct rate * p, real * samples, size_t * n0); +void _soxr_flush(struct rate * p); +void _soxr_close(struct rate * p); +double _soxr_delay(struct rate * p); +void _soxr_sizes(size_t * shared, size_t * channel); +#endif + +#endif diff --git a/soxr-sys/src/cr32.c b/soxr-sys/src/cr32.c new file mode 100644 index 000000000..b9eb264d0 --- /dev/null +++ b/soxr-sys/src/cr32.c @@ -0,0 +1,8 @@ +/* SoX Resampler Library Copyright (c) 2007-16 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +#define RATE_CB _soxr_rate32_cb +#define CORE_STR "cr32" + +#define CORE_TYPE 0 +#include "cr-core.c" diff --git a/soxr-sys/src/cr32s.c b/soxr-sys/src/cr32s.c new file mode 100644 index 000000000..5de2a4336 --- /dev/null +++ b/soxr-sys/src/cr32s.c @@ -0,0 +1,8 @@ +/* SoX Resampler Library Copyright (c) 2007-16 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +#define RATE_CB _soxr_rate32s_cb +#define CORE_STR "cr32s" + +#define CORE_TYPE (CORE_SIMD_POLY|CORE_SIMD_HALF|CORE_SIMD_DFT) +#include "cr-core.c" diff --git a/soxr-sys/src/cr64.c b/soxr-sys/src/cr64.c new file mode 100644 index 000000000..518cdd761 --- /dev/null +++ b/soxr-sys/src/cr64.c @@ -0,0 +1,8 @@ +/* SoX Resampler Library Copyright (c) 2007-16 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +#define RATE_CB _soxr_rate64_cb +#define CORE_STR "cr64" + +#define CORE_TYPE CORE_DBL +#include "cr-core.c" diff --git a/soxr-sys/src/cr64s.c b/soxr-sys/src/cr64s.c new file mode 100644 index 000000000..5dcd6f100 --- /dev/null +++ b/soxr-sys/src/cr64s.c @@ -0,0 +1,8 @@ +/* SoX Resampler Library Copyright (c) 2007-16 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +#define RATE_CB _soxr_rate64s_cb +#define CORE_STR "cr64s" + +#define CORE_TYPE (CORE_DBL|CORE_SIMD_POLY|CORE_SIMD_HALF|CORE_SIMD_DFT) +#include "cr-core.c" diff --git a/soxr-sys/src/data-io.c b/soxr-sys/src/data-io.c new file mode 100644 index 000000000..fb6167583 --- /dev/null +++ b/soxr-sys/src/data-io.c @@ -0,0 +1,223 @@ +/* SoX Resampler Library Copyright (c) 2007-16 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +#include +#include +#include + +#include "data-io.h" +#include "internal.h" + + + +#define DEINTERLEAVE_FROM(T,flag) do { \ + unsigned i; \ + size_t j; \ + T const * src = *src0; \ + if (ch > 1) for (j = 0; j < n; ++j) \ + for (i = 0; i < ch; ++i) dest[i][j] = (DEINTERLEAVE_TO)*src++; \ + else if (flag) memcpy(dest[0], src, n * sizeof(T)), src = &src[n]; \ + else for (j = 0; j < n; dest[0][j++] = (DEINTERLEAVE_TO)*src++); \ + *src0 = src; \ +} while (0) + + + +#if WITH_CR64 || WITH_CR64S +void _soxr_deinterleave(double * * dest, /* Round/clipping not needed here */ + soxr_datatype_t data_type, void const * * src0, size_t n, unsigned ch) +{ +#define DEINTERLEAVE_TO double + switch (data_type & 3) { + case SOXR_FLOAT32: DEINTERLEAVE_FROM(float, 0); break; + case SOXR_FLOAT64: DEINTERLEAVE_FROM(double, 1); break; + case SOXR_INT32: DEINTERLEAVE_FROM(int32_t, 0); break; + case SOXR_INT16: DEINTERLEAVE_FROM(int16_t, 0); break; + default: break; + } +} +#endif + + + +#if WITH_CR32 || WITH_CR32S || WITH_VR32 +void _soxr_deinterleave_f(float * * dest, /* Round/clipping not needed here */ + soxr_datatype_t data_type, void const * * src0, size_t n, unsigned ch) +{ +#undef DEINTERLEAVE_TO +#define DEINTERLEAVE_TO float + switch (data_type & 3) { + case SOXR_FLOAT32: DEINTERLEAVE_FROM(float, 1); break; + case SOXR_FLOAT64: DEINTERLEAVE_FROM(double, 0); break; + case SOXR_INT32: DEINTERLEAVE_FROM(int32_t, 0); break; + case SOXR_INT16: DEINTERLEAVE_FROM(int16_t, 0); break; + default: break; + } +} +#endif + + + +#include "rint.h" + + + +#if defined FE_INVALID && defined FPU_RINT32 && defined __STDC_VERSION__ + #if __STDC_VERSION__ >= 199901L + #pragma STDC FENV_ACCESS ON + #endif +#endif + +#if WITH_CR64 || WITH_CR64S +#define FLOATX double + +#define LSX_RINT_CLIP_2 lsx_rint32_clip_2 +#define LSX_RINT_CLIP lsx_rint32_clip +#define RINT_CLIP rint32_clip +#define RINT rint32D +#if defined FPU_RINT32 + #define FPU_RINT +#endif +#define RINT_T int32_t +#define RINT_MAX 2147483647L +#include "rint-clip.h" + +#define LSX_RINT_CLIP_2 lsx_rint16_clip_2 +#define LSX_RINT_CLIP lsx_rint16_clip +#define RINT_CLIP rint16_clip +#define RINT rint16D +#if defined FPU_RINT16 + #define FPU_RINT +#endif +#define RINT_T int16_t +#define RINT_MAX 32767 +#include "rint-clip.h" + +#define LSX_RINT_CLIP_2 lsx_rint16_clip_2_dither +#define LSX_RINT_CLIP lsx_rint16_clip_dither +#define RINT_CLIP rint16_clip_dither +#define RINT rint16D +#if defined FPU_RINT16 + #define FPU_RINT +#endif +#define RINT_T int16_t +#define RINT_MAX 32767 +#define DITHER +#include "rint-clip.h" + +#undef FLOATX +#endif + + + +#if WITH_CR32 || WITH_CR32S || WITH_VR32 +#define FLOATX float + +#define LSX_RINT_CLIP_2 lsx_rint32_clip_2_f +#define LSX_RINT_CLIP lsx_rint32_clip_f +#define RINT_CLIP rint32_clip_f +#define RINT rint32F +#if defined FPU_RINT32 + #define FPU_RINT +#endif +#define RINT_T int32_t +#define RINT_MAX 2147483647L +#include "rint-clip.h" + +#define LSX_RINT_CLIP_2 lsx_rint16_clip_2_f +#define LSX_RINT_CLIP lsx_rint16_clip_f +#define RINT_CLIP rint16_clip_f +#define RINT rint16F +#if defined FPU_RINT16 + #define FPU_RINT +#endif +#define RINT_T int16_t +#define RINT_MAX 32767 +#include "rint-clip.h" + +#define LSX_RINT_CLIP_2 lsx_rint16_clip_2_dither_f +#define LSX_RINT_CLIP lsx_rint16_clip_dither_f +#define RINT_CLIP rint16_clip_dither_f +#define RINT rint16D +#if defined FPU_RINT16 + #define FPU_RINT +#endif +#define RINT_T int16_t +#define RINT_MAX 32767 +#define DITHER +#include "rint-clip.h" + +#undef FLOATX +#endif + +#if defined FE_INVALID && defined FPU_RINT32 && defined __STDC_VERSION__ + #if __STDC_VERSION__ >= 199901L + #pragma STDC FENV_ACCESS OFF + #endif +#endif + + + +#define INTERLEAVE_TO(T,flag) do { \ + unsigned i; \ + size_t j; \ + T * dest = *dest0; \ + if (ch > 1) \ + for (j = 0; j < n; ++j) for (i = 0; i < ch; ++i) *dest++ = (T)src[i][j]; \ + else if (flag) memcpy(dest, src[0], n * sizeof(T)), dest = &dest[n]; \ + else for (j = 0; j < n; *dest++ = (T)src[0][j++]); \ + *dest0 = dest; \ + return 0; \ +} while (0) + +#if WITH_CR64 || WITH_CR64S +size_t /* clips */ _soxr_interleave(soxr_datatype_t data_type, void * * dest0, + double const * const * src, size_t n, unsigned ch, unsigned long * seed) +{ + switch (data_type & 3) { + case SOXR_FLOAT32: INTERLEAVE_TO(float, 0); + case SOXR_FLOAT64: INTERLEAVE_TO(double, 1); + + case SOXR_INT32: if (ch == 1) + return lsx_rint32_clip(dest0, src[0], n); + return lsx_rint32_clip_2(dest0, src, ch, n); + + case SOXR_INT16: if (seed) { + if (ch == 1) + return lsx_rint16_clip_dither(dest0, src[0], n, seed); + return lsx_rint16_clip_2_dither(dest0, src, ch, n, seed); + } + if (ch == 1) + return lsx_rint16_clip(dest0, src[0], n); + return lsx_rint16_clip_2(dest0, src, ch, n); + default: break; + } + return 0; +} +#endif + +#if WITH_CR32 || WITH_CR32S || WITH_VR32 +size_t /* clips */ _soxr_interleave_f(soxr_datatype_t data_type, void * * dest0, + float const * const * src, size_t n, unsigned ch, unsigned long * seed) +{ + switch (data_type & 3) { + case SOXR_FLOAT32: INTERLEAVE_TO(float, 1); + case SOXR_FLOAT64: INTERLEAVE_TO(double, 0); + + case SOXR_INT32: if (ch == 1) + return lsx_rint32_clip_f(dest0, src[0], n); + return lsx_rint32_clip_2_f(dest0, src, ch, n); + + case SOXR_INT16: if (seed) { + if (ch == 1) + return lsx_rint16_clip_dither_f(dest0, src[0], n, seed); + return lsx_rint16_clip_2_dither_f(dest0, src, ch, n, seed); + } + if (ch == 1) + return lsx_rint16_clip_f(dest0, src[0], n); + return lsx_rint16_clip_2_f(dest0, src, ch, n); + default: break; + } + return 0; +} +#endif diff --git a/soxr-sys/src/data-io.h b/soxr-sys/src/data-io.h new file mode 100644 index 000000000..83a0a133d --- /dev/null +++ b/soxr-sys/src/data-io.h @@ -0,0 +1,39 @@ +/* SoX Resampler Library Copyright (c) 2007-13 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +#if !defined soxr_data_io_included +#define soxr_data_io_included + +#include "soxr.h" + +void _soxr_deinterleave( + double * * dest, + soxr_datatype_t data_type, + void const * * src0, + size_t n, + unsigned ch); + +void _soxr_deinterleave_f( + float * * dest, + soxr_datatype_t data_type, + void const * * src0, + size_t n, + unsigned ch); + +size_t /* clips */ _soxr_interleave( + soxr_datatype_t data_type, + void * * dest, + double const * const * src, + size_t n, + unsigned ch, + unsigned long * seed); + +size_t /* clips */ _soxr_interleave_f( + soxr_datatype_t data_type, + void * * dest, + float const * const * src, + size_t n, + unsigned ch, + unsigned long * seed); + +#endif diff --git a/soxr-sys/src/dbesi0.c b/soxr-sys/src/dbesi0.c new file mode 100644 index 000000000..654216eb4 --- /dev/null +++ b/soxr-sys/src/dbesi0.c @@ -0,0 +1,149 @@ +/* Copyright(C) 1996 Takuya OOURA + +You may use, copy, modify this code for any purpose and +without fee. + +Package home: http://www.kurims.kyoto-u.ac.jp/~ooura/bessel.html +*/ + +#include "filter.h" +#define dbesi0 lsx_bessel_I_0 + +/* Bessel I_0(x) function in double precision */ + +#include + +double dbesi0(double x) +{ + int k; + double w, t, y; + static double a[65] = { + 8.5246820682016865877e-11, 2.5966600546497407288e-9, + 7.9689994568640180274e-8, 1.9906710409667748239e-6, + 4.0312469446528002532e-5, 6.4499871606224265421e-4, + 0.0079012345761930579108, 0.071111111109207045212, + 0.444444444444724909, 1.7777777777777532045, + 4.0000000000000011182, 3.99999999999999998, + 1.0000000000000000001, + 1.1520919130377195927e-10, 2.2287613013610985225e-9, + 8.1903951930694585113e-8, 1.9821560631611544984e-6, + 4.0335461940910133184e-5, 6.4495330974432203401e-4, + 0.0079013012611467520626, 0.071111038160875566622, + 0.44444450319062699316, 1.7777777439146450067, + 4.0000000132337935071, 3.9999999968569015366, + 1.0000000003426703174, + 1.5476870780515238488e-10, 1.2685004214732975355e-9, + 9.2776861851114223267e-8, 1.9063070109379044378e-6, + 4.0698004389917945832e-5, 6.4370447244298070713e-4, + 0.0079044749458444976958, 0.071105052411749363882, + 0.44445280640924755082, 1.7777694934432109713, + 4.0000055808824003386, 3.9999977081165740932, + 1.0000004333949319118, + 2.0675200625006793075e-10, -6.1689554705125681442e-10, + 1.2436765915401571654e-7, 1.5830429403520613423e-6, + 4.2947227560776583326e-5, 6.3249861665073441312e-4, + 0.0079454472840953930811, 0.070994327785661860575, + 0.44467219586283000332, 1.7774588182255374745, + 4.0003038986252717972, 3.9998233869142057195, + 1.0000472932961288324, + 2.7475684794982708655e-10, -3.8991472076521332023e-9, + 1.9730170483976049388e-7, 5.9651531561967674521e-7, + 5.1992971474748995357e-5, 5.7327338675433770752e-4, + 0.0082293143836530412024, 0.069990934858728039037, + 0.44726764292723985087, 1.7726685170014087784, + 4.0062907863712704432, 3.9952750700487845355, + 1.0016354346654179322 + }; + static double b[70] = { + 6.7852367144945531383e-8, 4.6266061382821826854e-7, + 6.9703135812354071774e-6, 7.6637663462953234134e-5, + 7.9113515222612691636e-4, 0.0073401204731103808981, + 0.060677114958668837046, 0.43994941411651569622, + 2.7420017097661750609, 14.289661921740860534, + 59.820609640320710779, 188.78998681199150629, + 399.8731367825601118, 427.56411572180478514, + 1.8042097874891098754e-7, 1.2277164312044637357e-6, + 1.8484393221474274861e-5, 2.0293995900091309208e-4, + 0.0020918539850246207459, 0.019375315654033949297, + 0.15985869016767185908, 1.1565260527420641724, + 7.1896341224206072113, 37.354773811947484532, + 155.80993164266268457, 489.5211371158540918, + 1030.9147225169564806, 1093.5883545113746958, + 4.8017305613187493564e-7, 3.261317843912380074e-6, + 4.9073137508166159639e-5, 5.3806506676487583755e-4, + 0.0055387918291051866561, 0.051223717488786549025, + 0.42190298621367914765, 3.0463625987357355872, + 18.895299447327733204, 97.915189029455461554, + 407.13940115493494659, 1274.3088990480582632, + 2670.9883037012547506, 2815.7166284662544712, + 1.2789926338424623394e-6, 8.6718263067604918916e-6, + 1.3041508821299929489e-4, 0.001428224737372747892, + 0.014684070635768789378, 0.13561403190404185755, + 1.1152592585977393953, 8.0387088559465389038, + 49.761318895895479206, 257.2684232313529138, + 1066.8543146269566231, 3328.3874581009636362, + 6948.8586598121634874, 7288.4893398212481055, + 3.409350368197032893e-6, 2.3079025203103376076e-5, + 3.4691373283901830239e-4, 0.003794994977222908545, + 0.038974209677945602145, 0.3594948380414878371, + 2.9522878893539528226, 21.246564609514287056, + 131.28727387146173141, 677.38107093296675421, + 2802.3724744545046518, 8718.5731420798254081, + 18141.348781638832286, 18948.925349296308859 + }; + static double c[45] = { + 2.5568678676452702768e-15, 3.0393953792305924324e-14, + 6.3343751991094840009e-13, 1.5041298011833009649e-11, + 4.4569436918556541414e-10, 1.746393051427167951e-8, + 1.0059224011079852317e-6, 1.0729838945088577089e-4, + 0.05150322693642527738, + 5.2527963991711562216e-15, 7.202118481421005641e-15, + 7.2561421229904797156e-13, 1.482312146673104251e-11, + 4.4602670450376245434e-10, 1.7463600061788679671e-8, + 1.005922609132234756e-6, 1.0729838937545111487e-4, + 0.051503226936437300716, + 1.3365917359358069908e-14, -1.2932643065888544835e-13, + 1.7450199447905602915e-12, 1.0419051209056979788e-11, + 4.58047881980598326e-10, 1.7442405450073548966e-8, + 1.0059461453281292278e-6, 1.0729837434500161228e-4, + 0.051503226940658446941, + 5.3771611477352308649e-14, -1.1396193006413731702e-12, + 1.2858641335221653409e-11, -5.9802086004570057703e-11, + 7.3666894305929510222e-10, 1.6731837150730356448e-8, + 1.0070831435812128922e-6, 1.0729733111203704813e-4, + 0.051503227360726294675, + 3.7819492084858931093e-14, -4.8600496888588034879e-13, + 1.6898350504817224909e-12, 4.5884624327524255865e-11, + 1.2521615963377513729e-10, 1.8959658437754727957e-8, + 1.0020716710561353622e-6, 1.073037119856927559e-4, + 0.05150322383300230775 + }; + + w = fabs(x); + if (w < 8.5) { + t = w * w * 0.0625; + k = 13 * ((int) t); + y = (((((((((((a[k] * t + a[k + 1]) * t + + a[k + 2]) * t + a[k + 3]) * t + a[k + 4]) * t + + a[k + 5]) * t + a[k + 6]) * t + a[k + 7]) * t + + a[k + 8]) * t + a[k + 9]) * t + a[k + 10]) * t + + a[k + 11]) * t + a[k + 12]; + } else if (w < 12.5) { + k = (int) w; + t = w - k; + k = 14 * (k - 8); + y = ((((((((((((b[k] * t + b[k + 1]) * t + + b[k + 2]) * t + b[k + 3]) * t + b[k + 4]) * t + + b[k + 5]) * t + b[k + 6]) * t + b[k + 7]) * t + + b[k + 8]) * t + b[k + 9]) * t + b[k + 10]) * t + + b[k + 11]) * t + b[k + 12]) * t + b[k + 13]; + } else { + t = 60 / w; + k = 9 * ((int) t); + y = ((((((((c[k] * t + c[k + 1]) * t + + c[k + 2]) * t + c[k + 3]) * t + c[k + 4]) * t + + c[k + 5]) * t + c[k + 6]) * t + c[k + 7]) * t + + c[k + 8]) * sqrt(t) * exp(w); + } + return y; +} diff --git a/soxr-sys/src/dev32s.h b/soxr-sys/src/dev32s.h new file mode 100644 index 000000000..7edae868d --- /dev/null +++ b/soxr-sys/src/dev32s.h @@ -0,0 +1,54 @@ +/* SoX Resampler Library Copyright (c) 2007-16 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +#if !defined soxr_dev32s_included +#define soxr_dev32s_included + +#if defined __GNUC__ + #define SIMD_INLINE(T) static __inline T __attribute__((always_inline)) + #define vAlign __attribute__((aligned (16))) +#elif defined _MSC_VER + #define SIMD_INLINE(T) static __forceinline T + #define vAlign __declspec(align(16)) +#endif + +#if defined __x86_64__ || defined _M_X64 || defined i386 || defined _M_IX86 + +#include + +#define vZero() _mm_setzero_ps() +#define vSet1(a) _mm_set_ss(a) +#define vMul(a,b) _mm_mul_ps(a,b) +#define vAdd(a,b) _mm_add_ps(a,b) +#define vMac(a,b,c) vAdd(vMul(a,b),c) +#define vLds(a) _mm_set1_ps(a) +#define vLd(a) _mm_load_ps(a) +#define vLdu(a) _mm_loadu_ps(a) + +typedef __m128 v4_t; + +SIMD_INLINE(void) vStorSum(float * a, v4_t b) { + v4_t t = vAdd(_mm_movehl_ps(b, b), b); + _mm_store_ss(a, vAdd(t, _mm_shuffle_ps(t,t,1)));} + +#elif defined __arm__ + +#include + +#define vZero() vdupq_n_f32(0) +#define vMul(a,b) vmulq_f32(a,b) +#define vAdd(a,b) vaddq_f32(a,b) +#define vMac(a,b,c) vmlaq_f32(c,a,b) +#define vLds(a) vld1q_dup_f32(&(a)) +#define vLd(a) vld1q_f32(a) +#define vLdu(a) vld1q_f32(a) + +typedef float32x4_t v4_t; + +SIMD_INLINE(void) vStorSum(float * a, v4_t b) { + float32x2_t t = vadd_f32(vget_high_f32(b), vget_low_f32(b)); + *a = vget_lane_f32(vpadd_f32(t, t), 0);} + +#endif + +#endif diff --git a/soxr-sys/src/dev64s.h b/soxr-sys/src/dev64s.h new file mode 100644 index 000000000..4672210d1 --- /dev/null +++ b/soxr-sys/src/dev64s.h @@ -0,0 +1,42 @@ +/* SoX Resampler Library Copyright (c) 2007-16 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +#if !defined soxr_dev64s_included +#define soxr_dev64s_included + +#if defined __GNUC__ + #define SIMD_INLINE(T) static __inline T __attribute__((always_inline)) + #define vAlign __attribute__((aligned (32))) +#elif defined _MSC_VER + #define SIMD_INLINE(T) static __forceinline T + #define vAlign __declspec(align(32)) +#else + #define SIMD_INLINE(T) static __inline T +#endif + +#if defined __x86_64__ || defined _M_X64 || defined i386 || defined _M_IX86 + +#include + +#if defined __AVX__ + +#define vZero() _mm256_setzero_pd() +#define vSet1(a) _mm256_set_pd(0,0,0,a) +#define vMul(a,b) _mm256_mul_pd(a,b) +#define vAdd(a,b) _mm256_add_pd(a,b) +#define vMac(a,b,c) vAdd(vMul(a,b),c) /* Note: gcc -mfma will `fuse' these */ +#define vLds(a) _mm256_set1_pd(a) +#define vLd(a) _mm256_load_pd(a) +#define vLdu(a) _mm256_loadu_pd(a) + +typedef __m256d v4_t; + +SIMD_INLINE(void) vStorSum(double * a, v4_t b) { + b = _mm256_hadd_pd(b, _mm256_permute2f128_pd(b,b,1)); + _mm_store_sd(a, _mm256_castpd256_pd128(_mm256_hadd_pd(b,b)));} + +#endif + +#endif + +#endif diff --git a/soxr-sys/src/fft4g.c b/soxr-sys/src/fft4g.c new file mode 100644 index 000000000..cf6293a04 --- /dev/null +++ b/soxr-sys/src/fft4g.c @@ -0,0 +1,1346 @@ +/* Copyright Takuya OOURA, 1996-2001. + +You may use, copy, modify and distribute this code for any +purpose (include commercial use) and without fee. Please +refer to this package when you modify this code. + +Package home: http://www.kurims.kyoto-u.ac.jp/~ooura/fft.html + +Fast Fourier/Cosine/Sine Transform + dimension :one + data length :power of 2 + decimation :frequency + radix :4, 2 + data :inplace + table :use +functions + cdft: Complex Discrete Fourier Transform + rdft: Real Discrete Fourier Transform + ddct: Discrete Cosine Transform + ddst: Discrete Sine Transform + dfct: Cosine Transform of RDFT (Real Symmetric DFT) + dfst: Sine Transform of RDFT (Real Anti-symmetric DFT) +function prototypes + void cdft(int, int, double *, int *, double *); + void rdft(int, int, double *, int *, double *); + void ddct(int, int, double *, int *, double *); + void ddst(int, int, double *, int *, double *); + void dfct(int, double *, double *, int *, double *); + void dfst(int, double *, double *, int *, double *); + + +-------- Complex DFT (Discrete Fourier Transform) -------- + [definition] + + X[k] = sum_j=0^n-1 x[j]*exp(2*pi*i*j*k/n), 0<=k + X[k] = sum_j=0^n-1 x[j]*exp(-2*pi*i*j*k/n), 0<=k + ip[0] = 0; // first time only + cdft(2*n, 1, a, ip, w); + + ip[0] = 0; // first time only + cdft(2*n, -1, a, ip, w); + [parameters] + 2*n :data length (int) + n >= 1, n = power of 2 + a[0...2*n-1] :input/output data (double *) + input data + a[2*j] = Re(x[j]), + a[2*j+1] = Im(x[j]), 0<=j= 2+sqrt(n) + strictly, + length of ip >= + 2+(1<<(int)(log(n+0.5)/log(2))/2). + ip[0],ip[1] are pointers of the cos/sin table. + w[0...n/2-1] :cos/sin table (double *) + w[],ip[] are initialized if ip[0] == 0. + [remark] + Inverse of + cdft(2*n, -1, a, ip, w); + is + cdft(2*n, 1, a, ip, w); + for (j = 0; j <= 2 * n - 1; j++) { + a[j] *= 1.0 / n; + } + . + + +-------- Real DFT / Inverse of Real DFT -------- + [definition] + RDFT + R[k] = sum_j=0^n-1 a[j]*cos(2*pi*j*k/n), 0<=k<=n/2 + I[k] = sum_j=0^n-1 a[j]*sin(2*pi*j*k/n), 0 IRDFT (excluding scale) + a[k] = (R[0] + R[n/2]*cos(pi*k))/2 + + sum_j=1^n/2-1 R[j]*cos(2*pi*j*k/n) + + sum_j=1^n/2-1 I[j]*sin(2*pi*j*k/n), 0<=k + ip[0] = 0; // first time only + rdft(n, 1, a, ip, w); + + ip[0] = 0; // first time only + rdft(n, -1, a, ip, w); + [parameters] + n :data length (int) + n >= 2, n = power of 2 + a[0...n-1] :input/output data (double *) + + output data + a[2*k] = R[k], 0<=k + input data + a[2*j] = R[j], 0<=j= 2+sqrt(n/2) + strictly, + length of ip >= + 2+(1<<(int)(log(n/2+0.5)/log(2))/2). + ip[0],ip[1] are pointers of the cos/sin table. + w[0...n/2-1] :cos/sin table (double *) + w[],ip[] are initialized if ip[0] == 0. + [remark] + Inverse of + rdft(n, 1, a, ip, w); + is + rdft(n, -1, a, ip, w); + for (j = 0; j <= n - 1; j++) { + a[j] *= 2.0 / n; + } + . + + +-------- DCT (Discrete Cosine Transform) / Inverse of DCT -------- + [definition] + IDCT (excluding scale) + C[k] = sum_j=0^n-1 a[j]*cos(pi*j*(k+1/2)/n), 0<=k DCT + C[k] = sum_j=0^n-1 a[j]*cos(pi*(j+1/2)*k/n), 0<=k + ip[0] = 0; // first time only + ddct(n, 1, a, ip, w); + + ip[0] = 0; // first time only + ddct(n, -1, a, ip, w); + [parameters] + n :data length (int) + n >= 2, n = power of 2 + a[0...n-1] :input/output data (double *) + output data + a[k] = C[k], 0<=k= 2+sqrt(n/2) + strictly, + length of ip >= + 2+(1<<(int)(log(n/2+0.5)/log(2))/2). + ip[0],ip[1] are pointers of the cos/sin table. + w[0...n*5/4-1] :cos/sin table (double *) + w[],ip[] are initialized if ip[0] == 0. + [remark] + Inverse of + ddct(n, -1, a, ip, w); + is + a[0] *= 0.5; + ddct(n, 1, a, ip, w); + for (j = 0; j <= n - 1; j++) { + a[j] *= 2.0 / n; + } + . + + +-------- DST (Discrete Sine Transform) / Inverse of DST -------- + [definition] + IDST (excluding scale) + S[k] = sum_j=1^n A[j]*sin(pi*j*(k+1/2)/n), 0<=k DST + S[k] = sum_j=0^n-1 a[j]*sin(pi*(j+1/2)*k/n), 0 + ip[0] = 0; // first time only + ddst(n, 1, a, ip, w); + + ip[0] = 0; // first time only + ddst(n, -1, a, ip, w); + [parameters] + n :data length (int) + n >= 2, n = power of 2 + a[0...n-1] :input/output data (double *) + + input data + a[j] = A[j], 0 + output data + a[k] = S[k], 0= 2+sqrt(n/2) + strictly, + length of ip >= + 2+(1<<(int)(log(n/2+0.5)/log(2))/2). + ip[0],ip[1] are pointers of the cos/sin table. + w[0...n*5/4-1] :cos/sin table (double *) + w[],ip[] are initialized if ip[0] == 0. + [remark] + Inverse of + ddst(n, -1, a, ip, w); + is + a[0] *= 0.5; + ddst(n, 1, a, ip, w); + for (j = 0; j <= n - 1; j++) { + a[j] *= 2.0 / n; + } + . + + +-------- Cosine Transform of RDFT (Real Symmetric DFT) -------- + [definition] + C[k] = sum_j=0^n a[j]*cos(pi*j*k/n), 0<=k<=n + [usage] + ip[0] = 0; // first time only + dfct(n, a, t, ip, w); + [parameters] + n :data length - 1 (int) + n >= 2, n = power of 2 + a[0...n] :input/output data (double *) + output data + a[k] = C[k], 0<=k<=n + t[0...n/2] :work area (double *) + ip[0...*] :work area for bit reversal (int *) + length of ip >= 2+sqrt(n/4) + strictly, + length of ip >= + 2+(1<<(int)(log(n/4+0.5)/log(2))/2). + ip[0],ip[1] are pointers of the cos/sin table. + w[0...n*5/8-1] :cos/sin table (double *) + w[],ip[] are initialized if ip[0] == 0. + [remark] + Inverse of + a[0] *= 0.5; + a[n] *= 0.5; + dfct(n, a, t, ip, w); + is + a[0] *= 0.5; + a[n] *= 0.5; + dfct(n, a, t, ip, w); + for (j = 0; j <= n; j++) { + a[j] *= 2.0 / n; + } + . + + +-------- Sine Transform of RDFT (Real Anti-symmetric DFT) -------- + [definition] + S[k] = sum_j=1^n-1 a[j]*sin(pi*j*k/n), 0= 2, n = power of 2 + a[0...n-1] :input/output data (double *) + output data + a[k] = S[k], 0= 2+sqrt(n/4) + strictly, + length of ip >= + 2+(1<<(int)(log(n/4+0.5)/log(2))/2). + ip[0],ip[1] are pointers of the cos/sin table. + w[0...n*5/8-1] :cos/sin table (double *) + w[],ip[] are initialized if ip[0] == 0. + [remark] + Inverse of + dfst(n, a, t, ip, w); + is + dfst(n, a, t, ip, w); + for (j = 1; j <= n - 1; j++) { + a[j] *= 2.0 / n; + } + . + + +Appendix : + The cos/sin table is recalculated when the larger table required. + w[] and ip[] are compatible with all routines. +*/ + + +#include "math-wrap.h" +#include "fft4g.h" + +#ifdef FFT4G_FLOAT + #define double float + #define one_half 0.5f + + #define sin(x) sinf(x) + #define cos(x) cosf(x) + #define atan(x) atanf(x) + + #define cdft lsx_cdft_f + #define rdft lsx_rdft_f + #define ddct lsx_ddct_f + #define ddst lsx_ddst_f + #define dfct lsx_dfct_f + #define dfst lsx_dfst_f +#else + #define one_half 0.5 + #define cdft lsx_cdft + #define rdft lsx_rdft + #define ddct lsx_ddct + #define ddst lsx_ddst + #define dfct lsx_dfct + #define dfst lsx_dfst +#endif + +static void bitrv2conj(int n, int *ip, double *a); +static void bitrv2(int n, int *ip, double *a); +static void cft1st(int n, double *a, double const *w); +static void cftbsub(int n, double *a, double const *w); +static void cftfsub(int n, double *a, double const *w); +static void cftmdl(int n, int l, double *a, double const *w); +static void dctsub(int n, double *a, int nc, double const *c); +static void dstsub(int n, double *a, int nc, double const *c); +static void makect(int nc, int *ip, double *c); +static void makewt(int nw, int *ip, double *w); +static void rftbsub(int n, double *a, int nc, double const *c); +static void rftfsub(int n, double *a, int nc, double const *c); + + +void cdft(int n, int isgn, double *a, int *ip, double *w) +{ + if (n > (ip[0] << 2)) { + makewt(n >> 2, ip, w); + } + if (n > 4) { + if (isgn >= 0) { + bitrv2(n, ip + 2, a); + cftfsub(n, a, w); + } else { + bitrv2conj(n, ip + 2, a); + cftbsub(n, a, w); + } + } else if (n == 4) { + cftfsub(n, a, w); + } +} + + +void rdft(int n, int isgn, double *a, int *ip, double *w) +{ + int nw, nc; + double xi; + + nw = ip[0]; + if (n > (nw << 2)) { + nw = n >> 2; + makewt(nw, ip, w); + } + nc = ip[1]; + if (n > (nc << 2)) { + nc = n >> 2; + makect(nc, ip, w + nw); + } + if (isgn >= 0) { + if (n > 4) { + bitrv2(n, ip + 2, a); + cftfsub(n, a, w); + rftfsub(n, a, nc, w + nw); + } else if (n == 4) { + cftfsub(n, a, w); + } + xi = a[0] - a[1]; + a[0] += a[1]; + a[1] = xi; + } else { + a[1] = one_half * (a[0] - a[1]); + a[0] -= a[1]; + if (n > 4) { + rftbsub(n, a, nc, w + nw); + bitrv2(n, ip + 2, a); + cftbsub(n, a, w); + } else if (n == 4) { + cftfsub(n, a, w); + } + } +} + + +void ddct(int n, int isgn, double *a, int *ip, double *w) +{ + int j, nw, nc; + double xr; + + nw = ip[0]; + if (n > (nw << 2)) { + nw = n >> 2; + makewt(nw, ip, w); + } + nc = ip[1]; + if (n > nc) { + nc = n; + makect(nc, ip, w + nw); + } + if (isgn < 0) { + xr = a[n - 1]; + for (j = n - 2; j >= 2; j -= 2) { + a[j + 1] = a[j] - a[j - 1]; + a[j] += a[j - 1]; + } + a[1] = a[0] - xr; + a[0] += xr; + if (n > 4) { + rftbsub(n, a, nc, w + nw); + bitrv2(n, ip + 2, a); + cftbsub(n, a, w); + } else if (n == 4) { + cftfsub(n, a, w); + } + } + dctsub(n, a, nc, w + nw); + if (isgn >= 0) { + if (n > 4) { + bitrv2(n, ip + 2, a); + cftfsub(n, a, w); + rftfsub(n, a, nc, w + nw); + } else if (n == 4) { + cftfsub(n, a, w); + } + xr = a[0] - a[1]; + a[0] += a[1]; + for (j = 2; j < n; j += 2) { + a[j - 1] = a[j] - a[j + 1]; + a[j] += a[j + 1]; + } + a[n - 1] = xr; + } +} + + +void ddst(int n, int isgn, double *a, int *ip, double *w) +{ + int j, nw, nc; + double xr; + + nw = ip[0]; + if (n > (nw << 2)) { + nw = n >> 2; + makewt(nw, ip, w); + } + nc = ip[1]; + if (n > nc) { + nc = n; + makect(nc, ip, w + nw); + } + if (isgn < 0) { + xr = a[n - 1]; + for (j = n - 2; j >= 2; j -= 2) { + a[j + 1] = -a[j] - a[j - 1]; + a[j] -= a[j - 1]; + } + a[1] = a[0] + xr; + a[0] -= xr; + if (n > 4) { + rftbsub(n, a, nc, w + nw); + bitrv2(n, ip + 2, a); + cftbsub(n, a, w); + } else if (n == 4) { + cftfsub(n, a, w); + } + } + dstsub(n, a, nc, w + nw); + if (isgn >= 0) { + if (n > 4) { + bitrv2(n, ip + 2, a); + cftfsub(n, a, w); + rftfsub(n, a, nc, w + nw); + } else if (n == 4) { + cftfsub(n, a, w); + } + xr = a[0] - a[1]; + a[0] += a[1]; + for (j = 2; j < n; j += 2) { + a[j - 1] = -a[j] - a[j + 1]; + a[j] -= a[j + 1]; + } + a[n - 1] = -xr; + } +} + + +void dfct(int n, double *a, double *t, int *ip, double *w) +{ + int j, k, l, m, mh, nw, nc; + double xr, xi, yr, yi; + + nw = ip[0]; + if (n > (nw << 3)) { + nw = n >> 3; + makewt(nw, ip, w); + } + nc = ip[1]; + if (n > (nc << 1)) { + nc = n >> 1; + makect(nc, ip, w + nw); + } + m = n >> 1; + yi = a[m]; + xi = a[0] + a[n]; + a[0] -= a[n]; + t[0] = xi - yi; + t[m] = xi + yi; + if (n > 2) { + mh = m >> 1; + for (j = 1; j < mh; j++) { + k = m - j; + xr = a[j] - a[n - j]; + xi = a[j] + a[n - j]; + yr = a[k] - a[n - k]; + yi = a[k] + a[n - k]; + a[j] = xr; + a[k] = yr; + t[j] = xi - yi; + t[k] = xi + yi; + } + t[mh] = a[mh] + a[n - mh]; + a[mh] -= a[n - mh]; + dctsub(m, a, nc, w + nw); + if (m > 4) { + bitrv2(m, ip + 2, a); + cftfsub(m, a, w); + rftfsub(m, a, nc, w + nw); + } else if (m == 4) { + cftfsub(m, a, w); + } + a[n - 1] = a[0] - a[1]; + a[1] = a[0] + a[1]; + for (j = m - 2; j >= 2; j -= 2) { + a[2 * j + 1] = a[j] + a[j + 1]; + a[2 * j - 1] = a[j] - a[j + 1]; + } + l = 2; + m = mh; + while (m >= 2) { + dctsub(m, t, nc, w + nw); + if (m > 4) { + bitrv2(m, ip + 2, t); + cftfsub(m, t, w); + rftfsub(m, t, nc, w + nw); + } else if (m == 4) { + cftfsub(m, t, w); + } + a[n - l] = t[0] - t[1]; + a[l] = t[0] + t[1]; + k = 0; + for (j = 2; j < m; j += 2) { + k += l << 2; + a[k - l] = t[j] - t[j + 1]; + a[k + l] = t[j] + t[j + 1]; + } + l <<= 1; + mh = m >> 1; + for (j = 0; j < mh; j++) { + k = m - j; + t[j] = t[m + k] - t[m + j]; + t[k] = t[m + k] + t[m + j]; + } + t[mh] = t[m + mh]; + m = mh; + } + a[l] = t[0]; + a[n] = t[2] - t[1]; + a[0] = t[2] + t[1]; + } else { + a[1] = a[0]; + a[2] = t[0]; + a[0] = t[1]; + } +} + + +void dfst(int n, double *a, double *t, int *ip, double *w) +{ + int j, k, l, m, mh, nw, nc; + double xr, xi, yr, yi; + + nw = ip[0]; + if (n > (nw << 3)) { + nw = n >> 3; + makewt(nw, ip, w); + } + nc = ip[1]; + if (n > (nc << 1)) { + nc = n >> 1; + makect(nc, ip, w + nw); + } + if (n > 2) { + m = n >> 1; + mh = m >> 1; + for (j = 1; j < mh; j++) { + k = m - j; + xr = a[j] + a[n - j]; + xi = a[j] - a[n - j]; + yr = a[k] + a[n - k]; + yi = a[k] - a[n - k]; + a[j] = xr; + a[k] = yr; + t[j] = xi + yi; + t[k] = xi - yi; + } + t[0] = a[mh] - a[n - mh]; + a[mh] += a[n - mh]; + a[0] = a[m]; + dstsub(m, a, nc, w + nw); + if (m > 4) { + bitrv2(m, ip + 2, a); + cftfsub(m, a, w); + rftfsub(m, a, nc, w + nw); + } else if (m == 4) { + cftfsub(m, a, w); + } + a[n - 1] = a[1] - a[0]; + a[1] = a[0] + a[1]; + for (j = m - 2; j >= 2; j -= 2) { + a[2 * j + 1] = a[j] - a[j + 1]; + a[2 * j - 1] = -a[j] - a[j + 1]; + } + l = 2; + m = mh; + while (m >= 2) { + dstsub(m, t, nc, w + nw); + if (m > 4) { + bitrv2(m, ip + 2, t); + cftfsub(m, t, w); + rftfsub(m, t, nc, w + nw); + } else if (m == 4) { + cftfsub(m, t, w); + } + a[n - l] = t[1] - t[0]; + a[l] = t[0] + t[1]; + k = 0; + for (j = 2; j < m; j += 2) { + k += l << 2; + a[k - l] = -t[j] - t[j + 1]; + a[k + l] = t[j] - t[j + 1]; + } + l <<= 1; + mh = m >> 1; + for (j = 1; j < mh; j++) { + k = m - j; + t[j] = t[m + k] + t[m + j]; + t[k] = t[m + k] - t[m + j]; + } + t[0] = t[m + mh]; + m = mh; + } + a[l] = t[0]; + } + a[0] = 0; +} + + +/* -------- initializing routines -------- */ + + +static void makewt(int nw, int *ip, double *w) +{ + int j, nwh; + double delta, x, y; + + ip[0] = nw; + ip[1] = 1; + if (nw > 2) { + nwh = nw >> 1; + delta = atan(1.0) / (double)nwh; + w[0] = 1; + w[1] = 0; + w[nwh] = cos(delta * (double)nwh); + w[nwh + 1] = w[nwh]; + if (nwh > 2) { + for (j = 2; j < nwh; j += 2) { + x = cos(delta * (double)j); + y = sin(delta * (double)j); + w[j] = x; + w[j + 1] = y; + w[nw - j] = y; + w[nw - j + 1] = x; + } + bitrv2(nw, ip + 2, w); + } + } +} + + +static void makect(int nc, int *ip, double *c) +{ + int j, nch; + double delta; + + ip[1] = nc; + if (nc > 1) { + nch = nc >> 1; + delta = atan(1.0) / (double)nch; + c[0] = cos(delta * (double)nch); + c[nch] = one_half * c[0]; + for (j = 1; j < nch; j++) { + c[j] = one_half * cos(delta * (double)j); + c[nc - j] = one_half * sin(delta * (double)j); + } + } +} + + +/* -------- child routines -------- */ + + +static void bitrv2(int n, int *ip0, double *a) +{ + int j, j1, k, k1, l, m, m2, ip[1024]; + double xr, xi, yr, yi; + + (void)ip0; + ip[0] = 0; + l = n; + m = 1; + while ((m << 3) < l) { + l >>= 1; + for (j = 0; j < m; j++) { + ip[m + j] = ip[j] + l; + } + m <<= 1; + } + m2 = 2 * m; + if ((m << 3) == l) { + for (k = 0; k < m; k++) { + for (j = 0; j < k; j++) { + j1 = 2 * j + ip[k]; + k1 = 2 * k + ip[j]; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m2; + k1 += 2 * m2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m2; + k1 -= m2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m2; + k1 += 2 * m2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + } + j1 = 2 * k + m2 + ip[k]; + k1 = j1 + m2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + } + } else { + for (k = 1; k < m; k++) { + for (j = 0; j < k; j++) { + j1 = 2 * j + ip[k]; + k1 = 2 * k + ip[j]; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m2; + k1 += m2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + } + } + } +} + + +static void bitrv2conj(int n, int *ip0, double *a) +{ + int j, j1, k, k1, l, m, m2, ip[512]; + double xr, xi, yr, yi; + + (void)ip0; + ip[0] = 0; + l = n; + m = 1; + while ((m << 3) < l) { + l >>= 1; + for (j = 0; j < m; j++) { + ip[m + j] = ip[j] + l; + } + m <<= 1; + } + m2 = 2 * m; + if ((m << 3) == l) { + for (k = 0; k < m; k++) { + for (j = 0; j < k; j++) { + j1 = 2 * j + ip[k]; + k1 = 2 * k + ip[j]; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m2; + k1 += 2 * m2; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m2; + k1 -= m2; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m2; + k1 += 2 * m2; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + } + k1 = 2 * k + ip[k]; + a[k1 + 1] = -a[k1 + 1]; + j1 = k1 + m2; + k1 = j1 + m2; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + k1 += m2; + a[k1 + 1] = -a[k1 + 1]; + } + } else { + a[1] = -a[1]; + a[m2 + 1] = -a[m2 + 1]; + for (k = 1; k < m; k++) { + for (j = 0; j < k; j++) { + j1 = 2 * j + ip[k]; + k1 = 2 * k + ip[j]; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m2; + k1 += m2; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + } + k1 = 2 * k + ip[k]; + a[k1 + 1] = -a[k1 + 1]; + a[k1 + m2 + 1] = -a[k1 + m2 + 1]; + } + } +} + + +static void cftfsub(int n, double *a, double const *w) +{ + int j, j1, j2, j3, l; + double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; + + l = 2; + if (n > 8) { + cft1st(n, a, w); + l = 8; + while ((l << 2) < n) { + cftmdl(n, l, a, w); + l <<= 2; + } + } + if ((l << 2) == n) { + for (j = 0; j < l; j += 2) { + j1 = j + l; + j2 = j1 + l; + j3 = j2 + l; + x0r = a[j] + a[j1]; + x0i = a[j + 1] + a[j1 + 1]; + x1r = a[j] - a[j1]; + x1i = a[j + 1] - a[j1 + 1]; + x2r = a[j2] + a[j3]; + x2i = a[j2 + 1] + a[j3 + 1]; + x3r = a[j2] - a[j3]; + x3i = a[j2 + 1] - a[j3 + 1]; + a[j] = x0r + x2r; + a[j + 1] = x0i + x2i; + a[j2] = x0r - x2r; + a[j2 + 1] = x0i - x2i; + a[j1] = x1r - x3i; + a[j1 + 1] = x1i + x3r; + a[j3] = x1r + x3i; + a[j3 + 1] = x1i - x3r; + } + } else { + for (j = 0; j < l; j += 2) { + j1 = j + l; + x0r = a[j] - a[j1]; + x0i = a[j + 1] - a[j1 + 1]; + a[j] += a[j1]; + a[j + 1] += a[j1 + 1]; + a[j1] = x0r; + a[j1 + 1] = x0i; + } + } +} + + +static void cftbsub(int n, double *a, double const *w) +{ + int j, j1, j2, j3, l; + double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; + + l = 2; + if (n > 8) { + cft1st(n, a, w); + l = 8; + while ((l << 2) < n) { + cftmdl(n, l, a, w); + l <<= 2; + } + } + if ((l << 2) == n) { + for (j = 0; j < l; j += 2) { + j1 = j + l; + j2 = j1 + l; + j3 = j2 + l; + x0r = a[j] + a[j1]; + x0i = -a[j + 1] - a[j1 + 1]; + x1r = a[j] - a[j1]; + x1i = -a[j + 1] + a[j1 + 1]; + x2r = a[j2] + a[j3]; + x2i = a[j2 + 1] + a[j3 + 1]; + x3r = a[j2] - a[j3]; + x3i = a[j2 + 1] - a[j3 + 1]; + a[j] = x0r + x2r; + a[j + 1] = x0i - x2i; + a[j2] = x0r - x2r; + a[j2 + 1] = x0i + x2i; + a[j1] = x1r - x3i; + a[j1 + 1] = x1i - x3r; + a[j3] = x1r + x3i; + a[j3 + 1] = x1i + x3r; + } + } else { + for (j = 0; j < l; j += 2) { + j1 = j + l; + x0r = a[j] - a[j1]; + x0i = -a[j + 1] + a[j1 + 1]; + a[j] += a[j1]; + a[j + 1] = -a[j + 1] - a[j1 + 1]; + a[j1] = x0r; + a[j1 + 1] = x0i; + } + } +} + + +static void cft1st(int n, double *a, double const *w) +{ + int j, k1, k2; + double wk1r, wk1i, wk2r, wk2i, wk3r, wk3i; + double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; + + x0r = a[0] + a[2]; + x0i = a[1] + a[3]; + x1r = a[0] - a[2]; + x1i = a[1] - a[3]; + x2r = a[4] + a[6]; + x2i = a[5] + a[7]; + x3r = a[4] - a[6]; + x3i = a[5] - a[7]; + a[0] = x0r + x2r; + a[1] = x0i + x2i; + a[4] = x0r - x2r; + a[5] = x0i - x2i; + a[2] = x1r - x3i; + a[3] = x1i + x3r; + a[6] = x1r + x3i; + a[7] = x1i - x3r; + wk1r = w[2]; + x0r = a[8] + a[10]; + x0i = a[9] + a[11]; + x1r = a[8] - a[10]; + x1i = a[9] - a[11]; + x2r = a[12] + a[14]; + x2i = a[13] + a[15]; + x3r = a[12] - a[14]; + x3i = a[13] - a[15]; + a[8] = x0r + x2r; + a[9] = x0i + x2i; + a[12] = x2i - x0i; + a[13] = x0r - x2r; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[10] = wk1r * (x0r - x0i); + a[11] = wk1r * (x0r + x0i); + x0r = x3i + x1r; + x0i = x3r - x1i; + a[14] = wk1r * (x0i - x0r); + a[15] = wk1r * (x0i + x0r); + k1 = 0; + for (j = 16; j < n; j += 16) { + k1 += 2; + k2 = 2 * k1; + wk2r = w[k1]; + wk2i = w[k1 + 1]; + wk1r = w[k2]; + wk1i = w[k2 + 1]; + wk3r = wk1r - 2 * wk2i * wk1i; + wk3i = 2 * wk2i * wk1r - wk1i; + x0r = a[j] + a[j + 2]; + x0i = a[j + 1] + a[j + 3]; + x1r = a[j] - a[j + 2]; + x1i = a[j + 1] - a[j + 3]; + x2r = a[j + 4] + a[j + 6]; + x2i = a[j + 5] + a[j + 7]; + x3r = a[j + 4] - a[j + 6]; + x3i = a[j + 5] - a[j + 7]; + a[j] = x0r + x2r; + a[j + 1] = x0i + x2i; + x0r -= x2r; + x0i -= x2i; + a[j + 4] = wk2r * x0r - wk2i * x0i; + a[j + 5] = wk2r * x0i + wk2i * x0r; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j + 2] = wk1r * x0r - wk1i * x0i; + a[j + 3] = wk1r * x0i + wk1i * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + a[j + 6] = wk3r * x0r - wk3i * x0i; + a[j + 7] = wk3r * x0i + wk3i * x0r; + wk1r = w[k2 + 2]; + wk1i = w[k2 + 3]; + wk3r = wk1r - 2 * wk2r * wk1i; + wk3i = 2 * wk2r * wk1r - wk1i; + x0r = a[j + 8] + a[j + 10]; + x0i = a[j + 9] + a[j + 11]; + x1r = a[j + 8] - a[j + 10]; + x1i = a[j + 9] - a[j + 11]; + x2r = a[j + 12] + a[j + 14]; + x2i = a[j + 13] + a[j + 15]; + x3r = a[j + 12] - a[j + 14]; + x3i = a[j + 13] - a[j + 15]; + a[j + 8] = x0r + x2r; + a[j + 9] = x0i + x2i; + x0r -= x2r; + x0i -= x2i; + a[j + 12] = -wk2i * x0r - wk2r * x0i; + a[j + 13] = -wk2i * x0i + wk2r * x0r; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j + 10] = wk1r * x0r - wk1i * x0i; + a[j + 11] = wk1r * x0i + wk1i * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + a[j + 14] = wk3r * x0r - wk3i * x0i; + a[j + 15] = wk3r * x0i + wk3i * x0r; + } +} + + +static void cftmdl(int n, int l, double *a, double const *w) +{ + int j, j1, j2, j3, k, k1, k2, m, m2; + double wk1r, wk1i, wk2r, wk2i, wk3r, wk3i; + double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; + + m = l << 2; + for (j = 0; j < l; j += 2) { + j1 = j + l; + j2 = j1 + l; + j3 = j2 + l; + x0r = a[j] + a[j1]; + x0i = a[j + 1] + a[j1 + 1]; + x1r = a[j] - a[j1]; + x1i = a[j + 1] - a[j1 + 1]; + x2r = a[j2] + a[j3]; + x2i = a[j2 + 1] + a[j3 + 1]; + x3r = a[j2] - a[j3]; + x3i = a[j2 + 1] - a[j3 + 1]; + a[j] = x0r + x2r; + a[j + 1] = x0i + x2i; + a[j2] = x0r - x2r; + a[j2 + 1] = x0i - x2i; + a[j1] = x1r - x3i; + a[j1 + 1] = x1i + x3r; + a[j3] = x1r + x3i; + a[j3 + 1] = x1i - x3r; + } + wk1r = w[2]; + for (j = m; j < l + m; j += 2) { + j1 = j + l; + j2 = j1 + l; + j3 = j2 + l; + x0r = a[j] + a[j1]; + x0i = a[j + 1] + a[j1 + 1]; + x1r = a[j] - a[j1]; + x1i = a[j + 1] - a[j1 + 1]; + x2r = a[j2] + a[j3]; + x2i = a[j2 + 1] + a[j3 + 1]; + x3r = a[j2] - a[j3]; + x3i = a[j2 + 1] - a[j3 + 1]; + a[j] = x0r + x2r; + a[j + 1] = x0i + x2i; + a[j2] = x2i - x0i; + a[j2 + 1] = x0r - x2r; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j1] = wk1r * (x0r - x0i); + a[j1 + 1] = wk1r * (x0r + x0i); + x0r = x3i + x1r; + x0i = x3r - x1i; + a[j3] = wk1r * (x0i - x0r); + a[j3 + 1] = wk1r * (x0i + x0r); + } + k1 = 0; + m2 = 2 * m; + for (k = m2; k < n; k += m2) { + k1 += 2; + k2 = 2 * k1; + wk2r = w[k1]; + wk2i = w[k1 + 1]; + wk1r = w[k2]; + wk1i = w[k2 + 1]; + wk3r = wk1r - 2 * wk2i * wk1i; + wk3i = 2 * wk2i * wk1r - wk1i; + for (j = k; j < l + k; j += 2) { + j1 = j + l; + j2 = j1 + l; + j3 = j2 + l; + x0r = a[j] + a[j1]; + x0i = a[j + 1] + a[j1 + 1]; + x1r = a[j] - a[j1]; + x1i = a[j + 1] - a[j1 + 1]; + x2r = a[j2] + a[j3]; + x2i = a[j2 + 1] + a[j3 + 1]; + x3r = a[j2] - a[j3]; + x3i = a[j2 + 1] - a[j3 + 1]; + a[j] = x0r + x2r; + a[j + 1] = x0i + x2i; + x0r -= x2r; + x0i -= x2i; + a[j2] = wk2r * x0r - wk2i * x0i; + a[j2 + 1] = wk2r * x0i + wk2i * x0r; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j1] = wk1r * x0r - wk1i * x0i; + a[j1 + 1] = wk1r * x0i + wk1i * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + a[j3] = wk3r * x0r - wk3i * x0i; + a[j3 + 1] = wk3r * x0i + wk3i * x0r; + } + wk1r = w[k2 + 2]; + wk1i = w[k2 + 3]; + wk3r = wk1r - 2 * wk2r * wk1i; + wk3i = 2 * wk2r * wk1r - wk1i; + for (j = k + m; j < l + (k + m); j += 2) { + j1 = j + l; + j2 = j1 + l; + j3 = j2 + l; + x0r = a[j] + a[j1]; + x0i = a[j + 1] + a[j1 + 1]; + x1r = a[j] - a[j1]; + x1i = a[j + 1] - a[j1 + 1]; + x2r = a[j2] + a[j3]; + x2i = a[j2 + 1] + a[j3 + 1]; + x3r = a[j2] - a[j3]; + x3i = a[j2 + 1] - a[j3 + 1]; + a[j] = x0r + x2r; + a[j + 1] = x0i + x2i; + x0r -= x2r; + x0i -= x2i; + a[j2] = -wk2i * x0r - wk2r * x0i; + a[j2 + 1] = -wk2i * x0i + wk2r * x0r; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j1] = wk1r * x0r - wk1i * x0i; + a[j1 + 1] = wk1r * x0i + wk1i * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + a[j3] = wk3r * x0r - wk3i * x0i; + a[j3 + 1] = wk3r * x0i + wk3i * x0r; + } + } +} + + +static void rftfsub(int n, double *a, int nc, double const *c) +{ + int j, k, kk, ks, m; + double wkr, wki, xr, xi, yr, yi; + + m = n >> 1; + ks = 2 * nc / m; + kk = 0; + for (j = 2; j < m; j += 2) { + k = n - j; + kk += ks; + wkr = one_half - c[nc - kk]; + wki = c[kk]; + xr = a[j] - a[k]; + xi = a[j + 1] + a[k + 1]; + yr = wkr * xr - wki * xi; + yi = wkr * xi + wki * xr; + a[j] -= yr; + a[j + 1] -= yi; + a[k] += yr; + a[k + 1] -= yi; + } +} + + +static void rftbsub(int n, double *a, int nc, double const *c) +{ + int j, k, kk, ks, m; + double wkr, wki, xr, xi, yr, yi; + + a[1] = -a[1]; + m = n >> 1; + ks = 2 * nc / m; + kk = 0; + for (j = 2; j < m; j += 2) { + k = n - j; + kk += ks; + wkr = one_half - c[nc - kk]; + wki = c[kk]; + xr = a[j] - a[k]; + xi = a[j + 1] + a[k + 1]; + yr = wkr * xr + wki * xi; + yi = wkr * xi - wki * xr; + a[j] -= yr; + a[j + 1] = yi - a[j + 1]; + a[k] += yr; + a[k + 1] = yi - a[k + 1]; + } + a[m + 1] = -a[m + 1]; +} + + +static void dctsub(int n, double *a, int nc, double const *c) +{ + int j, k, kk, ks, m; + double wkr, wki, xr; + + m = n >> 1; + ks = nc / n; + kk = 0; + for (j = 1; j < m; j++) { + k = n - j; + kk += ks; + wkr = c[kk] - c[nc - kk]; + wki = c[kk] + c[nc - kk]; + xr = wki * a[j] - wkr * a[k]; + a[j] = wkr * a[j] + wki * a[k]; + a[k] = xr; + } + a[m] *= c[0]; +} + + +static void dstsub(int n, double *a, int nc, double const *c) +{ + int j, k, kk, ks, m; + double wkr, wki, xr; + + m = n >> 1; + ks = nc / n; + kk = 0; + for (j = 1; j < m; j++) { + k = n - j; + kk += ks; + wkr = c[kk] - c[nc - kk]; + wki = c[kk] + c[nc - kk]; + xr = wki * a[k] - wkr * a[j]; + a[k] = wkr * a[k] + wki * a[j]; + a[j] = xr; + } + a[m] *= c[0]; +} diff --git a/soxr-sys/src/fft4g.h b/soxr-sys/src/fft4g.h new file mode 100644 index 000000000..0f906abcf --- /dev/null +++ b/soxr-sys/src/fft4g.h @@ -0,0 +1,23 @@ +/* SoX Resampler Library Copyright (c) 2007-13 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +void lsx_cdft(int, int, double *, int *, double *); +void lsx_rdft(int, int, double *, int *, double *); +void lsx_ddct(int, int, double *, int *, double *); +void lsx_ddst(int, int, double *, int *, double *); +void lsx_dfct(int, double *, double *, int *, double *); +void lsx_dfst(int, double *, double *, int *, double *); + +void lsx_cdft_f(int, int, float *, int *, float *); +void lsx_rdft_f(int, int, float *, int *, float *); +void lsx_ddct_f(int, int, float *, int *, float *); +void lsx_ddst_f(int, int, float *, int *, float *); +void lsx_dfct_f(int, float *, float *, int *, float *); +void lsx_dfst_f(int, float *, float *, int *, float *); + +#define dft_br_len(l) (2ul + (1ul << (int)(log(l / 2 + .5) / log(2.)) / 2)) +#define dft_sc_len(l) ((unsigned long)l / 2) + +/* Over-allocate h by 2 to use these macros */ +#define LSX_PACK(h, n) h[1] = h[n] +#define LSX_UNPACK(h, n) h[n] = h[1], h[n + 1] = h[1] = 0; diff --git a/soxr-sys/src/fft4g32.c b/soxr-sys/src/fft4g32.c new file mode 100644 index 000000000..7a31ba4bb --- /dev/null +++ b/soxr-sys/src/fft4g32.c @@ -0,0 +1,36 @@ +/* SoX Resampler Library Copyright (c) 2007-13 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +#include +#include "filter.h" +#define FFT4G_FLOAT +#include "fft4g.c" +#include "soxr-config.h" + +#if WITH_CR32 +#include "rdft_t.h" +static void * null(void) {return 0;} +static void forward (int length, void * setup, double * H) {lsx_safe_rdft_f(length, 1, H); (void)setup;} +static void backward(int length, void * setup, double * H) {lsx_safe_rdft_f(length, -1, H); (void)setup;} +static int multiplier(void) {return 2;} +static void nothing(void) {} +static int flags(void) {return 0;} + +fn_t _soxr_rdft32_cb[] = { + (fn_t)null, + (fn_t)null, + (fn_t)nothing, + (fn_t)forward, + (fn_t)forward, + (fn_t)backward, + (fn_t)backward, + (fn_t)_soxr_ordered_convolve_f, + (fn_t)_soxr_ordered_partial_convolve_f, + (fn_t)multiplier, + (fn_t)nothing, + (fn_t)malloc, + (fn_t)calloc, + (fn_t)free, + (fn_t)flags, +}; +#endif diff --git a/soxr-sys/src/fft4g32s.c b/soxr-sys/src/fft4g32s.c new file mode 100644 index 000000000..8ce9726ef --- /dev/null +++ b/soxr-sys/src/fft4g32s.c @@ -0,0 +1,31 @@ +/* SoX Resampler Library Copyright (c) 2007-13 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +#include "filter.h" +#include "util32s.h" +#include "rdft_t.h" + +static void * null(void) {return 0;} +static void nothing(void) {} +static void forward (int length, void * setup, float * H) {lsx_safe_rdft_f(length, 1, H); (void)setup;} +static void backward(int length, void * setup, float * H) {lsx_safe_rdft_f(length, -1, H); (void)setup;} +static int multiplier(void) {return 2;} +static int flags(void) {return RDFT_IS_SIMD;} + +fn_t _soxr_rdft32s_cb[] = { + (fn_t)null, + (fn_t)null, + (fn_t)nothing, + (fn_t)forward, + (fn_t)forward, + (fn_t)backward, + (fn_t)backward, + (fn_t)ORDERED_CONVOLVE_SIMD, + (fn_t)ORDERED_PARTIAL_CONVOLVE_SIMD, + (fn_t)multiplier, + (fn_t)nothing, + (fn_t)SIMD_ALIGNED_MALLOC, + (fn_t)SIMD_ALIGNED_CALLOC, + (fn_t)SIMD_ALIGNED_FREE, + (fn_t)flags, +}; diff --git a/soxr-sys/src/fft4g64.c b/soxr-sys/src/fft4g64.c new file mode 100644 index 000000000..0018516a0 --- /dev/null +++ b/soxr-sys/src/fft4g64.c @@ -0,0 +1,35 @@ +/* SoX Resampler Library Copyright (c) 2007-13 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +#include +#include "filter.h" +#include "fft4g.c" +#include "soxr-config.h" + +#if WITH_CR64 +static void * null(void) {return 0;} +static void nothing(void) {} +static void forward (int length, void * setup, double * H) {lsx_safe_rdft(length, 1, H); (void)setup;} +static void backward(int length, void * setup, double * H) {lsx_safe_rdft(length, -1, H); (void)setup;} +static int multiplier(void) {return 2;} +static int flags(void) {return 0;} + +typedef void (* fn_t)(void); +fn_t _soxr_rdft64_cb[] = { + (fn_t)null, + (fn_t)null, + (fn_t)nothing, + (fn_t)forward, + (fn_t)forward, + (fn_t)backward, + (fn_t)backward, + (fn_t)_soxr_ordered_convolve, + (fn_t)_soxr_ordered_partial_convolve, + (fn_t)multiplier, + (fn_t)nothing, + (fn_t)malloc, + (fn_t)calloc, + (fn_t)free, + (fn_t)flags, +}; +#endif diff --git a/soxr-sys/src/fft4g_cache.h b/soxr-sys/src/fft4g_cache.h new file mode 100644 index 000000000..d776c16c4 --- /dev/null +++ b/soxr-sys/src/fft4g_cache.h @@ -0,0 +1,92 @@ +/* SoX Resampler Library Copyright (c) 2007-13 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +static int * LSX_FFT_BR; +static DFT_FLOAT * LSX_FFT_SC; +static int FFT_LEN = -1; +static ccrw2_t FFT_CACHE_CCRW; + +void LSX_INIT_FFT_CACHE(void) +{ + if (FFT_LEN >= 0) + return; + assert(LSX_FFT_BR == NULL); + assert(LSX_FFT_SC == NULL); + assert(FFT_LEN == -1); + ccrw2_init(FFT_CACHE_CCRW); + FFT_LEN = 0; +} + +void LSX_CLEAR_FFT_CACHE(void) +{ + assert(FFT_LEN >= 0); + ccrw2_clear(FFT_CACHE_CCRW); + free(LSX_FFT_BR); + free(LSX_FFT_SC); + LSX_FFT_SC = NULL; + LSX_FFT_BR = NULL; + FFT_LEN = -1; +} + +static bool UPDATE_FFT_CACHE(int len) +{ + LSX_INIT_FFT_CACHE(); + assert(lsx_is_power_of_2(len)); + assert(FFT_LEN >= 0); + ccrw2_become_reader(FFT_CACHE_CCRW); + if (len > FFT_LEN) { + ccrw2_cease_reading(FFT_CACHE_CCRW); + ccrw2_become_writer(FFT_CACHE_CCRW); + if (len > FFT_LEN) { + int old_n = FFT_LEN; + FFT_LEN = len; + LSX_FFT_BR = realloc(LSX_FFT_BR, dft_br_len(FFT_LEN) * sizeof(*LSX_FFT_BR)); + LSX_FFT_SC = realloc(LSX_FFT_SC, dft_sc_len(FFT_LEN) * sizeof(*LSX_FFT_SC)); + if (!old_n) { + LSX_FFT_BR[0] = 0; +#if SOXR_LIB + atexit(LSX_CLEAR_FFT_CACHE); +#endif + } + return true; + } + ccrw2_cease_writing(FFT_CACHE_CCRW); + ccrw2_become_reader(FFT_CACHE_CCRW); + } + return false; +} + +static void DONE_WITH_FFT_CACHE(bool is_writer) +{ + if (is_writer) + ccrw2_cease_writing(FFT_CACHE_CCRW); + else ccrw2_cease_reading(FFT_CACHE_CCRW); +} + +void LSX_SAFE_RDFT(int len, int type, DFT_FLOAT * d) +{ + bool is_writer = UPDATE_FFT_CACHE(len); + LSX_RDFT(len, type, d, LSX_FFT_BR, LSX_FFT_SC); + DONE_WITH_FFT_CACHE(is_writer); +} + +void LSX_SAFE_CDFT(int len, int type, DFT_FLOAT * d) +{ + bool is_writer = UPDATE_FFT_CACHE(len); + LSX_CDFT(len, type, d, LSX_FFT_BR, LSX_FFT_SC); + DONE_WITH_FFT_CACHE(is_writer); +} + +#undef UPDATE_FFT_CACHE +#undef LSX_SAFE_RDFT +#undef LSX_SAFE_CDFT +#undef LSX_RDFT +#undef LSX_INIT_FFT_CACHE +#undef LSX_FFT_SC +#undef LSX_FFT_BR +#undef LSX_CLEAR_FFT_CACHE +#undef LSX_CDFT +#undef FFT_LEN +#undef FFT_CACHE_CCRW +#undef DONE_WITH_FFT_CACHE +#undef DFT_FLOAT diff --git a/soxr-sys/src/fifo.h b/soxr-sys/src/fifo.h new file mode 100644 index 000000000..33af9fe63 --- /dev/null +++ b/soxr-sys/src/fifo.h @@ -0,0 +1,125 @@ +/* SoX Resampler Library Copyright (c) 2007-13 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +#ifndef soxr_fifo_included +#define soxr_fifo_included + +#if !defined FIFO_SIZE_T +#define FIFO_SIZE_T size_t +#endif + +#if !defined FIFO_REALLOC +#include + #define FIFO_REALLOC(a,b,c) realloc(a,b) + #undef FIFO_FREE + #define FIFO_FREE free + #undef FIFO_MALLOC + #define FIFO_MALLOC malloc +#endif + +typedef struct { + char * data; + size_t allocation; /* Number of bytes allocated for data. */ + size_t item_size; /* Size of each item in data */ + size_t begin; /* Offset of the first byte to read. */ + size_t end; /* 1 + Offset of the last byte byte to read. */ +} fifo_t; + +#if !defined FIFO_MIN + #define FIFO_MIN 0x4000 +#endif + +#if !defined UNUSED + #define UNUSED +#endif + +UNUSED static void fifo_clear(fifo_t * f) +{ + f->end = f->begin = 0; +} + +UNUSED static void * fifo_reserve(fifo_t * f, FIFO_SIZE_T n0) +{ + size_t n = (size_t)n0; + n *= f->item_size; + + if (f->begin == f->end) + fifo_clear(f); + + while (1) { + if (f->end + n <= f->allocation) { + void *p = f->data + f->end; + + f->end += n; + return p; + } + if (f->begin > FIFO_MIN) { + memmove(f->data, f->data + f->begin, f->end - f->begin); + f->end -= f->begin; + f->begin = 0; + continue; + } + f->data = FIFO_REALLOC(f->data, f->allocation + n, f->allocation); + f->allocation += n; + if (!f->data) + return 0; + } +} + +UNUSED static void * fifo_write(fifo_t * f, FIFO_SIZE_T n0, void const * data) +{ + size_t n = (size_t)n0; + void * s = fifo_reserve(f, n0); + if (data) + memcpy(s, data, n * f->item_size); + return s; +} + +UNUSED static void fifo_trim_to(fifo_t * f, FIFO_SIZE_T n0) +{ + size_t n = (size_t)n0; + n *= f->item_size; + f->end = f->begin + n; +} + +UNUSED static void fifo_trim_by(fifo_t * f, FIFO_SIZE_T n0) +{ + size_t n = (size_t)n0; + n *= f->item_size; + f->end -= n; +} + +UNUSED static FIFO_SIZE_T fifo_occupancy(fifo_t * f) +{ + return (FIFO_SIZE_T)((f->end - f->begin) / f->item_size); +} + +UNUSED static void * fifo_read(fifo_t * f, FIFO_SIZE_T n0, void * data) +{ + size_t n = (size_t)n0; + char * ret = f->data + f->begin; + n *= f->item_size; + if (n > (f->end - f->begin)) + return NULL; + if (data) + memcpy(data, ret, (size_t)n); + f->begin += n; + return ret; +} + +#define fifo_read_ptr(f) fifo_read(f, (FIFO_SIZE_T)0, NULL) + +UNUSED static void fifo_delete(fifo_t * f) +{ + FIFO_FREE(f->data); +} + +UNUSED static int fifo_create(fifo_t * f, FIFO_SIZE_T item_size) +{ + f->item_size = (size_t)item_size; + f->allocation = FIFO_MIN; + fifo_clear(f); + return !(f->data = FIFO_MALLOC(f->allocation)); +} + +#endif diff --git a/soxr-sys/src/filter.c b/soxr-sys/src/filter.c new file mode 100644 index 000000000..019d24d90 --- /dev/null +++ b/soxr-sys/src/filter.c @@ -0,0 +1,277 @@ +/* SoX Resampler Library Copyright (c) 2007-16 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +#include "filter.h" + +#include "math-wrap.h" +#include +#include +#include + +#include "fft4g.h" +#include "ccrw2.h" + +#if 1 || WITH_CR64 || WITH_CR64S /* Always need this, for lsx_fir_to_phase. */ +#define DFT_FLOAT double +#define DONE_WITH_FFT_CACHE done_with_fft_cache +#define FFT_CACHE_CCRW fft_cache_ccrw +#define FFT_LEN fft_len +#define LSX_CDFT lsx_cdft +#define LSX_CLEAR_FFT_CACHE lsx_clear_fft_cache +#define LSX_FFT_BR lsx_fft_br +#define LSX_FFT_SC lsx_fft_sc +#define LSX_INIT_FFT_CACHE lsx_init_fft_cache +#define LSX_RDFT lsx_rdft +#define LSX_SAFE_CDFT lsx_safe_cdft +#define LSX_SAFE_RDFT lsx_safe_rdft +#define UPDATE_FFT_CACHE update_fft_cache +#include "fft4g_cache.h" +#endif + +#if (WITH_CR32 && !AVCODEC_FOUND) || (WITH_CR32S && !AVCODEC_FOUND && !WITH_PFFFT) +#define DFT_FLOAT float +#define DONE_WITH_FFT_CACHE done_with_fft_cache_f +#define FFT_CACHE_CCRW fft_cache_ccrw_f +#define FFT_LEN fft_len_f +#define LSX_CDFT lsx_cdft_f +#define LSX_CLEAR_FFT_CACHE lsx_clear_fft_cache_f +#define LSX_FFT_BR lsx_fft_br_f +#define LSX_FFT_SC lsx_fft_sc_f +#define LSX_INIT_FFT_CACHE lsx_init_fft_cache_f +#define LSX_RDFT lsx_rdft_f +#define LSX_SAFE_CDFT lsx_safe_cdft_f +#define LSX_SAFE_RDFT lsx_safe_rdft_f +#define UPDATE_FFT_CACHE update_fft_cache_f +#include "fft4g_cache.h" +#endif + +#if WITH_CR64 || WITH_CR64S || !SOXR_LIB +#define DFT_FLOAT double +#define ORDERED_CONVOLVE lsx_ordered_convolve +#define ORDERED_PARTIAL_CONVOLVE lsx_ordered_partial_convolve +#include "rdft.h" +#endif + +#if WITH_CR32 +#define DFT_FLOAT float +#define ORDERED_CONVOLVE lsx_ordered_convolve_f +#define ORDERED_PARTIAL_CONVOLVE lsx_ordered_partial_convolve_f +#include "rdft.h" +#endif + +double lsx_kaiser_beta(double att, double tr_bw) +{ + if (att >= 60) { + static const double coefs[][4] = { + {-6.784957e-10,1.02856e-05,0.1087556,-0.8988365+.001}, + {-6.897885e-10,1.027433e-05,0.10876,-0.8994658+.002}, + {-1.000683e-09,1.030092e-05,0.1087677,-0.9007898+.003}, + {-3.654474e-10,1.040631e-05,0.1087085,-0.8977766+.006}, + {8.106988e-09,6.983091e-06,0.1091387,-0.9172048+.015}, + {9.519571e-09,7.272678e-06,0.1090068,-0.9140768+.025}, + {-5.626821e-09,1.342186e-05,0.1083999,-0.9065452+.05}, + {-9.965946e-08,5.073548e-05,0.1040967,-0.7672778+.085}, + {1.604808e-07,-5.856462e-05,0.1185998,-1.34824+.1}, + {-1.511964e-07,6.363034e-05,0.1064627,-0.9876665+.18}, + }; + double realm = log(tr_bw/.0005)/log(2.); + double const * c0 = coefs[range_limit( (int)realm, 0, (int)array_length(coefs)-1)]; + double const * c1 = coefs[range_limit(1+(int)realm, 0, (int)array_length(coefs)-1)]; + double b0 = ((c0[0]*att + c0[1])*att + c0[2])*att + c0[3]; + double b1 = ((c1[0]*att + c1[1])*att + c1[2])*att + c1[3]; + return b0 + (b1 - b0) * (realm - (int)realm); + } + if (att > 50 ) return .1102 * (att - 8.7); + if (att > 20.96) return .58417 * pow(att -20.96, .4) + .07886 * (att - 20.96); + return 0; +} + +double * lsx_make_lpf( + int num_taps, double Fc, double beta, double rho, double scale) +{ + int i, m = num_taps - 1; + double * h = malloc((size_t)num_taps * sizeof(*h)); + double mult = scale / lsx_bessel_I_0(beta), mult1 = 1 / (.5 * m + rho); + assert(Fc >= 0 && Fc <= 1); + lsx_debug("make_lpf(n=%i Fc=%.7g beta=%g rho=%g scale=%g)", + num_taps, Fc, beta, rho, scale); + + if (h) for (i = 0; i <= m / 2; ++i) { + double z = i - .5 * m, x = z * M_PI, y = z * mult1; + h[i] = x!=0? sin(Fc * x) / x : Fc; + h[i] *= lsx_bessel_I_0(beta * sqrt(1 - y * y)) * mult; + if (m - i != i) + h[m - i] = h[i]; + } + return h; +} + +void lsx_kaiser_params(double att, double Fc, double tr_bw, double * beta, int * num_taps) +{ + *beta = *beta < 0? lsx_kaiser_beta(att, tr_bw * .5 / Fc): *beta; + att = att < 60? (att - 7.95) / (2.285 * M_PI * 2) : + ((.0007528358-1.577737e-05**beta)**beta+.6248022)**beta+.06186902; + *num_taps = !*num_taps? (int)ceil(att/tr_bw + 1) : *num_taps; +} + +double * lsx_design_lpf( + double Fp, /* End of pass-band */ + double Fs, /* Start of stop-band */ + double Fn, /* Nyquist freq; e.g. 0.5, 1, PI */ + double att, /* Stop-band attenuation in dB */ + int * num_taps, /* 0: value will be estimated */ + int k, /* >0: number of phases; <0: num_taps = 1 (mod -k) */ + double beta) /* <0: value will be estimated */ +{ + int n = *num_taps, phases = max(k, 1), modulo = max(-k, 1); + double tr_bw, Fc, rho = phases == 1? .5 : att < 120? .63 : .75; + + lsx_debug_more("./sinctest %-12.7g %-12.7g %g 0 %-5g %i %i 50 %g %g -4 >1", + Fp, Fs, Fn, att, *num_taps, k, beta, rho); + + Fp /= fabs(Fn), Fs /= fabs(Fn); /* Normalise to Fn = 1 */ + tr_bw = .5 * (Fs - Fp); /* Transition band-width: 6dB to stop points */ + tr_bw /= phases, Fs /= phases; + tr_bw = min(tr_bw, .5 * Fs); + Fc = Fs - tr_bw; + assert(Fc - tr_bw >= 0); + lsx_kaiser_params(att, Fc, tr_bw, &beta, num_taps); + if (!n) + *num_taps = phases > 1? *num_taps / phases * phases + phases - 1 : + (*num_taps + modulo - 2) / modulo * modulo + 1; + return Fn < 0? 0 : lsx_make_lpf(*num_taps, Fc, beta, rho, (double)phases); +} + +static double safe_log(double x) +{ + assert(x >= 0); + if (x!=0) + return log(x); + lsx_debug("log(0)"); + return -26; +} + +void lsx_fir_to_phase(double * * h, int * len, int * post_len, double phase) +{ + double * pi_wraps, * work, phase1 = (phase > 50 ? 100 - phase : phase) / 50; + int i, work_len, begin, end, imp_peak = 0, peak = 0; + double imp_sum = 0, peak_imp_sum = 0; + double prev_angle2 = 0, cum_2pi = 0, prev_angle1 = 0, cum_1pi = 0; + + for (i = *len, work_len = 2 * 2 * 8; i > 1; work_len <<= 1, i >>= 1); + + work = calloc((size_t)work_len + 2, sizeof(*work)); /* +2: (UN)PACK */ + pi_wraps = malloc((((size_t)work_len + 2) / 2) * sizeof(*pi_wraps)); + + memcpy(work, *h, (size_t)*len * sizeof(*work)); + lsx_safe_rdft(work_len, 1, work); /* Cepstral: */ + LSX_UNPACK(work, work_len); + + for (i = 0; i <= work_len; i += 2) { + double angle = atan2(work[i + 1], work[i]); + double detect = 2 * M_PI; + double delta = angle - prev_angle2; + double adjust = detect * ((delta < -detect * .7) - (delta > detect * .7)); + prev_angle2 = angle; + cum_2pi += adjust; + angle += cum_2pi; + detect = M_PI; + delta = angle - prev_angle1; + adjust = detect * ((delta < -detect * .7) - (delta > detect * .7)); + prev_angle1 = angle; + cum_1pi += fabs(adjust); /* fabs for when 2pi and 1pi have combined */ + pi_wraps[i >> 1] = cum_1pi; + + work[i] = safe_log(sqrt(sqr(work[i]) + sqr(work[i + 1]))); + work[i + 1] = 0; + } + LSX_PACK(work, work_len); + lsx_safe_rdft(work_len, -1, work); + for (i = 0; i < work_len; ++i) work[i] *= 2. / work_len; + + for (i = 1; i < work_len / 2; ++i) { /* Window to reject acausal components */ + work[i] *= 2; + work[i + work_len / 2] = 0; + } + lsx_safe_rdft(work_len, 1, work); + + for (i = 2; i < work_len; i += 2) /* Interpolate between linear & min phase */ + work[i + 1] = phase1 * i / work_len * pi_wraps[work_len >> 1] + + (1 - phase1) * (work[i + 1] + pi_wraps[i >> 1]) - pi_wraps[i >> 1]; + + work[0] = exp(work[0]), work[1] = exp(work[1]); + for (i = 2; i < work_len; i += 2) { + double x = exp(work[i]); + work[i ] = x * cos(work[i + 1]); + work[i + 1] = x * sin(work[i + 1]); + } + + lsx_safe_rdft(work_len, -1, work); + for (i = 0; i < work_len; ++i) work[i] *= 2. / work_len; + + /* Find peak pos. */ + for (i = 0; i <= (int)(pi_wraps[work_len >> 1] / M_PI + .5); ++i) { + imp_sum += work[i]; + if (fabs(imp_sum) > fabs(peak_imp_sum)) { + peak_imp_sum = imp_sum; + peak = i; + } + if (work[i] > work[imp_peak]) /* For debug check only */ + imp_peak = i; + } + while (peak && fabs(work[peak-1]) > fabs(work[peak]) && work[peak-1] * work[peak] > 0) + --peak; + + if (phase1==0) + begin = 0; + else if (phase1 == 1) + begin = peak - *len / 2; + else { + begin = (int)((.997 - (2 - phase1) * .22) * *len + .5); + end = (int)((.997 + (0 - phase1) * .22) * *len + .5); + begin = peak - (begin & ~3); + end = peak + 1 + ((end + 3) & ~3); + *len = end - begin; + *h = realloc(*h, (size_t)*len * sizeof(**h)); + } + for (i = 0; i < *len; ++i) (*h)[i] = + work[(begin + (phase > 50 ? *len - 1 - i : i) + work_len) & (work_len - 1)]; + *post_len = phase > 50 ? peak - begin : begin + *len - (peak + 1); + + lsx_debug("nPI=%g peak-sum@%i=%g (val@%i=%g); len=%i post=%i (%g%%)", + pi_wraps[work_len >> 1] / M_PI, peak, peak_imp_sum, imp_peak, + work[imp_peak], *len, *post_len, 100 - 100. * *post_len / (*len - 1)); + free(pi_wraps), free(work); +} + +#define F_x(F,expr) static double F(double x) {return expr;} +F_x(sinePhi, ((2.0517e-07*x-1.1303e-04)*x+.023154)*x+.55924 ) +F_x(sinePsi, ((9.0667e-08*x-5.6114e-05)*x+.013658)*x+1.0977 ) +F_x(sinePow, log(.5)/log(sin(x*.5)) ) +#define dB_to_linear(x) exp((x) * (M_LN10 * 0.05)) + +double lsx_f_resp(double t, double a) +{ + double x; + if (t > (a <= 160? .8 : .82)) { + double a1 = a+15; + double p = .00035*a+.375; + double w = 1/(1-.597)*asin(pow((a1-10.6)/a1,1/p)); + double c = 1+asin(pow(1-a/a1,1/p))/w; + return a1*(pow(sin((c-t)*w),p)-1); + } + if (t > .5) + x = sinePsi(a), x = pow(sin((1-t) * x), sinePow(x)); + else + x = sinePhi(a), x = 1 - pow(sin(t * x), sinePow(x)); + return linear_to_dB(x); +} + +double lsx_inv_f_resp(double drop, double a) +{ + double x = sinePhi(a), s; + drop = dB_to_linear(drop); + s = drop > .5 ? 1 - drop : drop; + x = asin(pow(s, 1/sinePow(x))) / x; + return drop > .5? x : 1 -x; +} diff --git a/soxr-sys/src/filter.h b/soxr-sys/src/filter.h new file mode 100644 index 000000000..ccb3ba836 --- /dev/null +++ b/soxr-sys/src/filter.h @@ -0,0 +1,44 @@ +/* SoX Resampler Library Copyright (c) 2007-13 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +#if !defined soxr_filter_included +#define soxr_filter_included + +#include "aliases.h" + +double lsx_bessel_I_0(double x); +void lsx_init_fft_cache(void); +void lsx_clear_fft_cache(void); +void lsx_init_fft_cache_f(void); +void lsx_clear_fft_cache_f(void); +#define lsx_is_power_of_2(x) !(x < 2 || (x & (x - 1))) +void lsx_safe_rdft(int len, int type, double * d); +void lsx_safe_cdft(int len, int type, double * d); +void lsx_safe_rdft_f(int len, int type, float * d); +void lsx_safe_cdft_f(int len, int type, float * d); +void lsx_ordered_convolve(int n, void * not_used, double * a, const double * b); +void lsx_ordered_convolve_f(int n, void * not_used, float * a, const float * b); +void lsx_ordered_partial_convolve(int n, double * a, const double * b); +void lsx_ordered_partial_convolve_f(int n, float * a, const float * b); + +double lsx_kaiser_beta(double att, double tr_bw); +double * lsx_make_lpf(int num_taps, double Fc, double beta, double rho, + double scale); +void lsx_kaiser_params(double att, double Fc, double tr_bw, double * beta, int * num_taps); +double * lsx_design_lpf( + double Fp, /* End of pass-band */ + double Fs, /* Start of stop-band */ + double Fn, /* Nyquist freq; e.g. 0.5, 1, PI; < 0: dummy run */ + double att, /* Stop-band attenuation in dB */ + int * num_taps, /* 0: value will be estimated */ + int k, /* >0: number of phases; <0: num_taps = 1 (mod -k) */ + double beta); /* <0: value will be estimated */ + +void lsx_fir_to_phase(double * * h, int * len, + int * post_len, double phase0); + +double lsx_f_resp(double t, double a); +double lsx_inv_f_resp(double drop, double a); +#define lsx_to_3dB(a) (1 - lsx_inv_f_resp(-3., a)) + +#endif diff --git a/soxr-sys/src/half-coefs.h b/soxr-sys/src/half-coefs.h new file mode 100644 index 000000000..a5a0882bc --- /dev/null +++ b/soxr-sys/src/half-coefs.h @@ -0,0 +1,75 @@ +/* SoX Resampler Library Copyright (c) 2007-13 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +#if defined __GNUC__ + #pragma GCC system_header +#elif defined __SUNPRO_C + #pragma disable_warn +#elif defined _MSC_VER + #pragma warning(push, 1) +#endif + +#if CORE_TYPE & CORE_SIMD_HALF + #define VALIGN vAlign +#else + #define VALIGN +#endif + +#if !(CORE_TYPE & CORE_SIMD_HALF) +static VALIGN const sample_t half_fir_coefs_7[] = { + 3.1062656496657370e-01, -8.4998810699955796e-02, 3.4007044621123500e-02, +-1.2839903789829387e-02, 3.9899380181723145e-03, -8.9355202017945374e-04, + 1.0918292424806546e-04, +}; +#endif + +static VALIGN const sample_t half_fir_coefs_8[] = { + 3.1154652365332069e-01, -8.7344917685739543e-02, 3.6814458353637280e-02, +-1.5189204581464479e-02, 5.4540855610738801e-03, -1.5643862626630416e-03, + 3.1816575906323303e-04, -3.4799449225005688e-05, +}; + +static VALIGN const sample_t half_fir_coefs_9[] = { + 3.1227034755311189e-01, -8.9221517147969526e-02, 3.9139704015071934e-02, +-1.7250558515852023e-02, 6.8589440230476112e-03, -2.3045049636430419e-03, + 6.0963740543348963e-04, -1.1323803957431231e-04, 1.1197769991000046e-05, +}; + +#if CORE_TYPE & CORE_DBL +static VALIGN const sample_t half_fir_coefs_10[] = { + 3.1285456012000523e-01, -9.0756740799292787e-02, 4.1096398104193160e-02, +-1.9066319572525220e-02, 8.1840569787684902e-03, -3.0766876176359834e-03, + 9.6396524429277980e-04, -2.3585679989922018e-04, 4.0252189026627833e-05, +-3.6298196342497932e-06, +}; + +static VALIGN const sample_t half_fir_coefs_11[] = { + 3.1333588822574199e-01, -9.2035898673019811e-02, 4.2765169698406408e-02, +-2.0673580894964429e-02, 9.4225426824512421e-03, -3.8563379950013192e-03, + 1.3634742159642453e-03, -3.9874150714431009e-04, 9.0586723632664806e-05, +-1.4285617244076783e-05, 1.1834642946400529e-06, +}; + +static VALIGN const sample_t half_fir_coefs_12[] = { + 3.1373928463345568e-01, -9.3118180335301962e-02, 4.4205005881659098e-02, +-2.2103860986973051e-02, 1.0574689371162864e-02, -4.6276428065385065e-03, + 1.7936153397572132e-03, -5.9617527051353237e-04, 1.6314517495669067e-04, +-3.4555126770115446e-05, 5.0617615610782593e-06, -3.8768958592971409e-07, +}; + +static VALIGN const sample_t half_fir_coefs_13[] = { + 3.1408224847888910e-01, -9.4045836332667387e-02, 4.5459878763259978e-02, +-2.3383369012219993e-02, 1.1644273044890753e-02, -5.3806714579057013e-03, + 2.2429072878264022e-03, -8.2204347506606424e-04, 2.5724946477840893e-04, +-6.6072709864248668e-05, 1.3099163296288644e-05, -1.7907147069136000e-06, + 1.2750825595240592e-07, +}; +#endif + +#undef VALIGN + +#if defined __SUNPRO_C + #pragma enable_warn +#elif defined _MSC_VER + #pragma warning(pop) +#endif diff --git a/soxr-sys/src/half-fir.h b/soxr-sys/src/half-fir.h new file mode 100644 index 000000000..782be1bc7 --- /dev/null +++ b/soxr-sys/src/half-fir.h @@ -0,0 +1,61 @@ +/* SoX Resampler Library Copyright (c) 2007-16 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +/* Decimate by 2 using a FIR with odd length (LEN). */ +/* Input must be preceded and followed by LEN >> 1 samples. */ + +#define COEFS ((sample_t const *)p->coefs) + +#if SIMD_SSE + #define BEGINNING v4_t sum, q1, q2, t + #define ____ \ + q1 = _mm_shuffle_ps(t=vLdu(input+2*j),vLdu(input+2*j+4),_MM_SHUFFLE(3,1,3,1)); \ + q2 = _mm_shuffle_ps(vLdu(input-2*j-4),vLdu(input-2*j-8),_MM_SHUFFLE(1,3,1,3)); \ + sum = vAdd(j? sum : vMul(vSet1(.5), t), vMul(vAdd(q1, q2), vLd(COEFS+j))); \ + j += 4; + #define __ \ + q1 = _mm_shuffle_ps(vLdu(input+2*j), vLdu(input-2*j-4), _MM_SHUFFLE(1,3,3,1)); \ + q2 = _mm_loadl_pi(q2, (__m64*)(COEFS+j)), q2 = _mm_movelh_ps(q2, q2); \ + sum = vAdd(sum, vMul(q1, q2)); \ + j += 2; + #define _ \ + q1 = _mm_add_ss(_mm_load_ss(input+2*j+1), _mm_load_ss(input-2*j-1)); \ + sum = _mm_add_ss(sum, _mm_mul_ss(q1, _mm_load_ss(COEFS+j))); \ + ++j; + #define END vStorSum(output+i, sum) +/* #elif SIMD_AVX; No good solution found. */ +/* #elif SIMD_NEON; No need: gcc -O3 does a good job by itself. */ +#else + #define BEGINNING sample_t sum = input[0] * .5f + #define ____ __ __ + #define __ _ _ + #define _ sum += (input[-(2*j +1)] + input[(2*j +1)]) * COEFS[j], ++j; + #define END output[i] = sum +#endif + + + +static void FUNCTION_H(stage_t * p, fifo_t * output_fifo) +{ + sample_t const * __restrict input = stage_read_p(p); + int num_in = min(stage_occupancy(p), p->input_size); + int i, num_out = (num_in + 1) >> 1; + sample_t * __restrict output = fifo_reserve(output_fifo, num_out); + + for (i = 0; i < num_out; ++i, input += 2) { + int j = 0; + BEGINNING; CONVOLVE; END; + } + fifo_read(&p->fifo, 2 * num_out, NULL); +} + + + +#undef _ +#undef __ +#undef ____ +#undef BEGINNING +#undef END +#undef COEFS +#undef CONVOLVE +#undef FUNCTION_H diff --git a/soxr-sys/src/internal.h b/soxr-sys/src/internal.h new file mode 100644 index 000000000..08924d500 --- /dev/null +++ b/soxr-sys/src/internal.h @@ -0,0 +1,84 @@ +/* SoX Resampler Library Copyright (c) 2007-16 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +#if !defined soxr_internal_included +#define soxr_internal_included + +#include "std-types.h" + + + +#undef min +#undef max +#define min(a, b) ((a) <= (b) ? (a) : (b)) +#define max(a, b) ((a) >= (b) ? (a) : (b)) + + + +#define range_limit(x, lower, upper) (min(max(x, lower), upper)) +#define linear_to_dB(x) (log10(x) * 20) +#define array_length(a) (sizeof(a)/sizeof(a[0])) +#if !defined AL +#define AL(a) array_length(a) +#endif +#define iAL(a) (int)AL(a) +#define sqr(a) ((a) * (a)) + + + +#if defined __GNUC__ + #define UNUSED __attribute__ ((unused)) +#else + #define UNUSED +#endif + + + +#if !WITH_DEV_TRACE + #ifdef __GNUC__ + void lsx_dummy(char const *, ...); + #else + static __inline void lsx_dummy(char const * x, ...) {} + #endif + #define lsx_debug if(0) lsx_dummy + #define lsx_debug_more lsx_debug +#else + extern int _soxr_trace_level; + void _soxr_trace(char const * fmt, ...); + #define lsx_debug if (_soxr_trace_level > 0) _soxr_trace + #define lsx_debug_more if (_soxr_trace_level > 1) _soxr_trace +#endif + + + +/* soxr_quality_spec_t.flags: */ + +#define SOXR_ROLLOFF_LSR2Q 3u /* Reserved for internal use. */ +#define SOXR_ROLLOFF_MASK 3u /* For masking these bits. */ +#define SOXR_MAINTAIN_3DB_PT 4u /* Reserved for internal use. */ +#define SOXR_PROMOTE_TO_LQ 64u /* Reserved for internal use. */ + + + +/* soxr_runtime_spec_t.flags: */ + +#define SOXR_STRICT_BUFFERING 4u /* Reserved for future use. */ +#define SOXR_NOSMALLINTOPT 8u /* For test purposes only. */ + + + +/* soxr_quality_spec recipe: */ + +#define SOXR_PRECISIONQ 11 /* Quality specified by the precision parameter. */ + +#define SOXR_PHASE_MASK 0x30 /* For masking these bits. */ + + + +/* soxr_quality_spec flags: */ + +#define RESET_ON_CLEAR (1u<<31) + + + +#endif diff --git a/soxr-sys/src/lib.rs b/soxr-sys/src/lib.rs new file mode 100644 index 000000000..31c0d8dbf --- /dev/null +++ b/soxr-sys/src/lib.rs @@ -0,0 +1,152 @@ +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] + +include!("soxr.rs"); + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + unsafe { + let version = soxr_version(); + let version = std::ffi::CStr::from_ptr(version).to_str().unwrap(); + println!("version: {}", version); + } + } + + #[test] + fn test_stream() { + use std::ffi::c_void; + use std::ffi::CStr; + use std::ptr; + + use hound::{WavReader, WavSpec, WavWriter}; + + let input_wav_path = "input.wav"; + let output_wav_path = "output.wav"; + + let mut reader = WavReader::open(input_wav_path).expect("Failed to open input WAV file"); + + let wav_spec = reader.spec(); + let input_rate = wav_spec.sample_rate as f64; + let output_rate = 44100.0; + + let num_channels = wav_spec.channels as u32; + + let samples: Vec = reader + .samples::() + .map(|s| s.expect("Failed to read sample")) + .collect(); + + let buf_total_len = samples.len(); + let olen = + ((output_rate * buf_total_len as f64) / (input_rate + output_rate) + 0.5) as usize; + let ilen = buf_total_len - olen; + + let mut obuf = vec![0i16; olen]; + + let mut odone: usize = 0; + let mut need_input = true; + + let mut error: soxr_error_t = ptr::null(); + + let io_spec = soxr_io_spec { + itype: SOXR_INT16_I as u32, + otype: SOXR_INT16_I as u32, + scale: 1.0, + e: ptr::null_mut(), + flags: 0, + }; + + let soxr = unsafe { + soxr_create( + input_rate, + output_rate, + num_channels, + &mut error, + &io_spec, + ptr::null(), + ptr::null(), + ) + }; + + if error.is_null() { + let mut input_pos = 0; + let input_len = samples.len(); + + let mut output_samples = Vec::new(); + + while error.is_null() && (need_input || odone > 0) { + let mut ilen1 = 0; + let mut ibuf: Option<&[i16]> = None; + + if need_input { + if input_pos < input_len { + let remaining_samples = input_len - input_pos; + let samples_to_read = std::cmp::min(ilen, remaining_samples); + + ibuf = Some(&samples[input_pos..input_pos + samples_to_read]); + ilen1 = samples_to_read; + input_pos += samples_to_read; + } else { + ibuf = None; + } + } + + let in_ptr = match ibuf { + Some(slice) => slice.as_ptr() as *const c_void, + None => ptr::null(), + }; + + let process_error = unsafe { + soxr_process( + soxr, + in_ptr, + ilen1, + ptr::null_mut(), + obuf.as_mut_ptr() as *mut c_void, + olen, + &mut odone, + ) + }; + + if !process_error.is_null() { + break; + } + + if odone > 0 { + output_samples.extend_from_slice(&obuf[..odone]); + } + + need_input = (odone < olen) && ibuf.is_some(); + } + + let spec = WavSpec { + channels: wav_spec.channels, + sample_rate: output_rate as u32, + bits_per_sample: 16, + sample_format: hound::SampleFormat::Int, + }; + + let mut writer = + WavWriter::create(output_wav_path, spec).expect("Failed to create output WAV file"); + + for sample in output_samples { + writer.write_sample(sample).expect("Failed to write sample"); + } + + writer.finalize().expect("Failed to finalize WAV file"); + + println!("Resampling completed successfully."); + } else { + let error_str = unsafe { CStr::from_ptr(error) }; + eprintln!("Error creating resampler: {}", error_str.to_string_lossy()); + } + + unsafe { + soxr_delete(soxr); + } + } +} diff --git a/soxr-sys/src/math-wrap.h b/soxr-sys/src/math-wrap.h new file mode 100644 index 000000000..8a526f13e --- /dev/null +++ b/soxr-sys/src/math-wrap.h @@ -0,0 +1,31 @@ +/* SoX Resampler Library Copyright (c) 2007-16 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +#if !defined soxr_math_wrap_included +#define soxr_math_wrap_included + +#include + +#if defined __STRICT_ANSI__ + #define sinf(x) (float)sin ((double)(x)) + #define cosf(x) (float)cos ((double)(x)) + #define atanf(x) (float)atan((double)(x)) +#endif + +#if !defined M_PI + #define M_PI 3.141592653589793238462643383279502884 +#endif + +#if !defined M_LN10 + #define M_LN10 2.302585092994045684017991454684364208 +#endif + +#if !defined M_SQRT2 + #define M_SQRT2 1.414213562373095048801688724209698079 +#endif + +#if !defined M_LN2 + #define M_LN2 0.693147180559945309417232121458176568 +#endif + +#endif diff --git a/soxr-sys/src/pffft-avx.h b/soxr-sys/src/pffft-avx.h new file mode 100644 index 000000000..ace19b57d --- /dev/null +++ b/soxr-sys/src/pffft-avx.h @@ -0,0 +1,40 @@ +/* SoX Resampler Library Copyright (c) 2007-16 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +/* AVX support macros */ + +#if !defined soxr_avx_included +#define soxr_avx_included + +#include + +typedef __m256d v4sf; +#define VZERO() _mm256_setzero_pd() +#define VMUL(a,b) _mm256_mul_pd(a,b) +#define VADD(a,b) _mm256_add_pd(a,b) +#define VMADD(a,b,c) VADD(VMUL(a,b),c) /* Note: gcc -mfma will `fuse' these */ +#define VSUB(a,b) _mm256_sub_pd(a,b) +#define LD_PS1(p) _mm256_set1_pd(p) +#define INTERLEAVE2(in1, in2, out1, out2) {v4sf \ + t1 = _mm256_unpacklo_pd(in1, in2), \ + t2 = _mm256_unpackhi_pd(in1, in2); \ + out1 = _mm256_permute2f128_pd(t1,t2,0x20); \ + out2 = _mm256_permute2f128_pd(t1,t2,0x31); } +#define UNINTERLEAVE2(in1, in2, out1, out2) {v4sf \ + t1 = _mm256_permute2f128_pd(in1,in2,0x20), \ + t2 = _mm256_permute2f128_pd(in1,in2,0x31); \ + out1 = _mm256_unpacklo_pd(t1, t2); \ + out2 = _mm256_unpackhi_pd(t1, t2);} +#define VTRANSPOSE4(x0,x1,x2,x3) {v4sf \ + t0 = _mm256_shuffle_pd(x0,x1, 0x0), \ + t2 = _mm256_shuffle_pd(x0,x1, 0xf), \ + t1 = _mm256_shuffle_pd(x2,x3, 0x0), \ + t3 = _mm256_shuffle_pd(x2,x3, 0xf); \ + x0 = _mm256_permute2f128_pd(t0,t1, 0x20); \ + x1 = _mm256_permute2f128_pd(t2,t3, 0x20); \ + x2 = _mm256_permute2f128_pd(t0,t1, 0x31); \ + x3 = _mm256_permute2f128_pd(t2,t3, 0x31);} +#define VSWAPHL(a,b) _mm256_permute2f128_pd(b, a, 0x30) +#define VALIGNED(ptr) ((((long)(ptr)) & 0x1F) == 0) + +#endif diff --git a/soxr-sys/src/pffft-wrap.c b/soxr-sys/src/pffft-wrap.c new file mode 100644 index 000000000..c920f06ea --- /dev/null +++ b/soxr-sys/src/pffft-wrap.c @@ -0,0 +1,110 @@ +/* SoX Resampler Library Copyright (c) 2007-16 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +#if !defined PFFT_MACROS_ONLY + +#include "math-wrap.h" + +#if PFFFT_DOUBLE + #include "util64s.h" +#else + #include "util32s.h" + #define sin(x) sinf(x) + #define cos(x) cosf(x) +#endif + +#define pffft_aligned_free SIMD_ALIGNED_FREE +#define pffft_aligned_malloc SIMD_ALIGNED_MALLOC +#define pffft_aligned_calloc SIMD_ALIGNED_CALLOC + +#undef inline +#define inline __inline + +#endif + + + +#include "pffft.c" + + + +#if !defined PFFT_MACROS_ONLY + +#if !defined PFFFT_SIMD_DISABLE + +static void pffft_zconvolve(PFFFT_Setup *s, const float *a, const float *b, float *ab) { + int i, Ncvec = s->Ncvec; + const v4sf * /*RESTRICT*/ va = (const v4sf*)a; + const v4sf * RESTRICT vb = (const v4sf*)b; + v4sf * /*RESTRICT*/ vab = (v4sf*)ab; + + float ar, ai, br, bi; + +#ifdef __arm__ + __builtin_prefetch(va); + __builtin_prefetch(vb); + __builtin_prefetch(va+2); + __builtin_prefetch(vb+2); + __builtin_prefetch(va+4); + __builtin_prefetch(vb+4); + __builtin_prefetch(va+6); + __builtin_prefetch(vb+6); +#endif + + assert(VALIGNED(a) && VALIGNED(b) && VALIGNED(ab)); + ar = ((v4sf_union*)va)[0].f[0]; + ai = ((v4sf_union*)va)[1].f[0]; + br = ((v4sf_union*)vb)[0].f[0]; + bi = ((v4sf_union*)vb)[1].f[0]; + + for (i=0; i < Ncvec; i += 2) { + v4sf ar, ai, br, bi; + ar = va[2*i+0]; ai = va[2*i+1]; + br = vb[2*i+0]; bi = vb[2*i+1]; + VCPLXMUL(ar, ai, br, bi); + vab[2*i+0] = ar; + vab[2*i+1] = ai; + ar = va[2*i+2]; ai = va[2*i+3]; + br = vb[2*i+2]; bi = vb[2*i+3]; + VCPLXMUL(ar, ai, br, bi); + vab[2*i+2] = ar; + vab[2*i+3] = ai; + } + if (s->transform == PFFFT_REAL) { + ((v4sf_union*)vab)[0].f[0] = ar*br; + ((v4sf_union*)vab)[1].f[0] = ai*bi; + } +} + +#else + +static void pffft_zconvolve(PFFFT_Setup *s, const float *a, const float *b, float *ab) { + int i, Ncvec = s->Ncvec; + + if (s->transform == PFFFT_REAL) { + /* take care of the fftpack ordering */ + ab[0] = a[0]*b[0]; + ab[2*Ncvec-1] = a[2*Ncvec-1]*b[2*Ncvec-1]; + ++ab; ++a; ++b; --Ncvec; + } + for (i=0; i < Ncvec; ++i) { + float ar, ai, br, bi; + ar = a[2*i+0]; ai = a[2*i+1]; + br = b[2*i+0]; bi = b[2*i+1]; + VCPLXMUL(ar, ai, br, bi); + ab[2*i+0] = ar; + ab[2*i+1] = ai; + } +} + +#endif + +#include + +static void pffft_reorder_back(int length, void * setup, float * data, float * work) +{ + memcpy(work, data, (unsigned)length * sizeof(*work)); + pffft_zreorder(setup, work, data, PFFFT_BACKWARD); +} + +#endif diff --git a/soxr-sys/src/pffft.c b/soxr-sys/src/pffft.c new file mode 100644 index 000000000..46c841e74 --- /dev/null +++ b/soxr-sys/src/pffft.c @@ -0,0 +1,1946 @@ +/* https://bitbucket.org/jpommier/pffft/raw/483453d8f7661058e74aa4e7cf5c27bcd7887e7a/pffft.c + * with minor changes for libsoxr. */ + +/* Copyright (c) 2013 Julien Pommier ( pommier@modartt.com ) + + Based on original fortran 77 code from FFTPACKv4 from NETLIB + (http://www.netlib.org/fftpack), authored by Dr Paul Swarztrauber + of NCAR, in 1985. + + As confirmed by the NCAR fftpack software curators, the following + FFTPACKv5 license applies to FFTPACKv4 sources. My changes are + released under the same terms. + + FFTPACK license: + + http://www.cisl.ucar.edu/css/software/fftpack5/ftpk.html + + Copyright (c) 2004 the University Corporation for Atmospheric + Research ("UCAR"). All rights reserved. Developed by NCAR's + Computational and Information Systems Laboratory, UCAR, + www.cisl.ucar.edu. + + Redistribution and use of the Software in source and binary forms, + with or without modification, is permitted provided that the + following conditions are met: + + - Neither the names of NCAR's Computational and Information Systems + Laboratory, the University Corporation for Atmospheric Research, + nor the names of its sponsors or contributors may be used to + endorse or promote products derived from this Software without + specific prior written permission. + + - Redistributions of source code must retain the above copyright + notices, this list of conditions, and the disclaimer below. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer below in the + documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE + SOFTWARE. + + + PFFFT : a Pretty Fast FFT. + + This file is largerly based on the original FFTPACK implementation, modified in + order to take advantage of SIMD instructions of modern CPUs. +*/ + +/* + ChangeLog: + - 2011/10/02, version 1: This is the very first release of this file. +*/ + +#include "pffft.h" +#include +#include +#include +#include + +/* detect compiler flavour */ +#if defined(_MSC_VER) +# define COMPILER_MSVC +#elif defined(__GNUC__) +# define COMPILER_GCC +#endif + +#if defined(COMPILER_GCC) +# define ALWAYS_INLINE(return_type) inline return_type __attribute__ ((always_inline)) +# define NEVER_INLINE(return_type) return_type __attribute__ ((noinline)) +# define RESTRICT __restrict +# define VLA_ARRAY_ON_STACK(type__, varname__, size__) type__ varname__[size__]; +#elif defined(COMPILER_MSVC) +# define ALWAYS_INLINE(return_type) __forceinline return_type +# define NEVER_INLINE(return_type) __declspec(noinline) return_type +# define RESTRICT __restrict +# define VLA_ARRAY_ON_STACK(type__, varname__, size__) type__ *varname__ = (type__*)_alloca(size__ * sizeof(type__)) +#endif + + +/* + vector support macros: the rest of the code is independant of + SSE/Altivec/NEON -- adding support for other platforms with 4-element + vectors should be limited to these macros +*/ + + +/* define PFFFT_SIMD_DISABLE if you want to use scalar code instead of simd code */ +/*#define PFFFT_SIMD_DISABLE */ + +/* + Altivec support macros +*/ +#if !defined(PFFFT_SIMD_DISABLE) && (defined(__ppc__) || defined(__ppc64__)) +typedef vector float v4sf; +# define SIMD_SZ 4 +# define VZERO() ((vector float) vec_splat_u8(0)) +# define VMUL(a,b) vec_madd(a,b, VZERO()) +# define VADD(a,b) vec_add(a,b) +# define VMADD(a,b,c) vec_madd(a,b,c) +# define VSUB(a,b) vec_sub(a,b) +inline v4sf ld_ps1(const float *p) { v4sf v=vec_lde(0,p); return vec_splat(vec_perm(v, v, vec_lvsl(0, p)), 0); } +# define LD_PS1(p) ld_ps1(&p) +# define INTERLEAVE2(in1, in2, out1, out2) { v4sf tmp__ = vec_mergeh(in1, in2); out2 = vec_mergel(in1, in2); out1 = tmp__; } +# define UNINTERLEAVE2(in1, in2, out1, out2) { \ + vector unsigned char vperm1 = (vector unsigned char)(0,1,2,3,8,9,10,11,16,17,18,19,24,25,26,27); \ + vector unsigned char vperm2 = (vector unsigned char)(4,5,6,7,12,13,14,15,20,21,22,23,28,29,30,31); \ + v4sf tmp__ = vec_perm(in1, in2, vperm1); out2 = vec_perm(in1, in2, vperm2); out1 = tmp__; \ + } +# define VTRANSPOSE4(x0,x1,x2,x3) { \ + v4sf y0 = vec_mergeh(x0, x2); \ + v4sf y1 = vec_mergel(x0, x2); \ + v4sf y2 = vec_mergeh(x1, x3); \ + v4sf y3 = vec_mergel(x1, x3); \ + x0 = vec_mergeh(y0, y2); \ + x1 = vec_mergel(y0, y2); \ + x2 = vec_mergeh(y1, y3); \ + x3 = vec_mergel(y1, y3); \ + } +# define VSWAPHL(a,b) vec_perm(a,b, (vector unsigned char)(16,17,18,19,20,21,22,23,8,9,10,11,12,13,14,15)) +# define VALIGNED(ptr) ((((long)(ptr)) & 0xF) == 0) + +/* + SSE1 support macros +*/ +#elif !defined(PFFFT_SIMD_DISABLE) && (defined(__x86_64__) || defined(_M_X64) || defined(i386) || defined(_M_IX86)) + +# define SIMD_SZ 4 /* 4 floats by simd vector -- this is pretty much hardcoded in the preprocess/finalize functions anyway so you will have to work if you want to enable AVX with its 256-bit vectors. */ + +#if !PFFFT_DOUBLE +#include +typedef __m128 v4sf; +# define VZERO() _mm_setzero_ps() +# define VMUL(a,b) _mm_mul_ps(a,b) +# define VADD(a,b) _mm_add_ps(a,b) +# define VMADD(a,b,c) _mm_add_ps(_mm_mul_ps(a,b), c) +# define VSUB(a,b) _mm_sub_ps(a,b) +# define LD_PS1(p) _mm_set1_ps(p) +# define INTERLEAVE2(in1, in2, out1, out2) { v4sf tmp__ = _mm_unpacklo_ps(in1, in2); out2 = _mm_unpackhi_ps(in1, in2); out1 = tmp__; } +# define UNINTERLEAVE2(in1, in2, out1, out2) { v4sf tmp__ = _mm_shuffle_ps(in1, in2, _MM_SHUFFLE(2,0,2,0)); out2 = _mm_shuffle_ps(in1, in2, _MM_SHUFFLE(3,1,3,1)); out1 = tmp__; } +# define VTRANSPOSE4(x0,x1,x2,x3) _MM_TRANSPOSE4_PS(x0,x1,x2,x3) +# define VSWAPHL(a,b) _mm_shuffle_ps(b, a, _MM_SHUFFLE(3,2,1,0)) +# define VALIGNED(ptr) ((((long)(ptr)) & 0xF) == 0) + +#else +#include "pffft-avx.h" +#endif + +/* + ARM NEON support macros +*/ +#elif !defined(PFFFT_SIMD_DISABLE) && defined(__arm__) +# include +typedef float32x4_t v4sf; +# define SIMD_SZ 4 +# define VZERO() vdupq_n_f32(0) +# define VMUL(a,b) vmulq_f32(a,b) +# define VADD(a,b) vaddq_f32(a,b) +# define VMADD(a,b,c) vmlaq_f32(c,a,b) +# define VSUB(a,b) vsubq_f32(a,b) +# define LD_PS1(p) vld1q_dup_f32(&(p)) +# define INTERLEAVE2(in1, in2, out1, out2) { float32x4x2_t tmp__ = vzipq_f32(in1,in2); out1=tmp__.val[0]; out2=tmp__.val[1]; } +# define UNINTERLEAVE2(in1, in2, out1, out2) { float32x4x2_t tmp__ = vuzpq_f32(in1,in2); out1=tmp__.val[0]; out2=tmp__.val[1]; } +# define VTRANSPOSE4(x0,x1,x2,x3) { \ + float32x4x2_t t0_ = vzipq_f32(x0, x2); \ + float32x4x2_t t1_ = vzipq_f32(x1, x3); \ + float32x4x2_t u0_ = vzipq_f32(t0_.val[0], t1_.val[0]); \ + float32x4x2_t u1_ = vzipq_f32(t0_.val[1], t1_.val[1]); \ + x0 = u0_.val[0]; x1 = u0_.val[1]; x2 = u1_.val[0]; x3 = u1_.val[1]; \ + } +/* marginally faster version */ +/*# define VTRANSPOSE4(x0,x1,x2,x3) { asm("vtrn.32 %q0, %q1;\n vtrn.32 %q2,%q3\n vswp %f0,%e2\n vswp %f1,%e3" : "+w"(x0), "+w"(x1), "+w"(x2), "+w"(x3)::); } */ +# define VSWAPHL(a,b) vcombine_f32(vget_low_f32(b), vget_high_f32(a)) +# define VALIGNED(ptr) ((((long)(ptr)) & 0x3) == 0) +#else +# if !defined(PFFFT_SIMD_DISABLE) +# warning "building with simd disabled !\n"; +# define PFFFT_SIMD_DISABLE /* fallback to scalar code */ +# endif +#endif + +#if PFFFT_DOUBLE +#define float double +#endif + +/* fallback mode for situations where SSE/Altivec are not available, use scalar mode instead */ +#ifdef PFFFT_SIMD_DISABLE +typedef float v4sf; +# define SIMD_SZ 1 +# define VZERO() 0.f +# define VMUL(a,b) ((a)*(b)) +# define VADD(a,b) ((a)+(b)) +# define VMADD(a,b,c) ((a)*(b)+(c)) +# define VSUB(a,b) ((a)-(b)) +# define LD_PS1(p) (p) +# define VALIGNED(ptr) ((((long)(ptr)) & 0x3) == 0) +#endif + +/* shortcuts for complex multiplcations */ +#define VCPLXMUL(ar,ai,br,bi) { v4sf tmp; tmp=VMUL(ar,bi); ar=VMUL(ar,br); ar=VSUB(ar,VMUL(ai,bi)); ai=VMUL(ai,br); ai=VADD(ai,tmp); } +#define VCPLXMULCONJ(ar,ai,br,bi) { v4sf tmp; tmp=VMUL(ar,bi); ar=VMUL(ar,br); ar=VADD(ar,VMUL(ai,bi)); ai=VMUL(ai,br); ai=VSUB(ai,tmp); } +#ifndef SVMUL +/* multiply a scalar with a vector */ +#define SVMUL(f,v) VMUL(LD_PS1(f),v) +#endif + +#if !defined PFFT_MACROS_ONLY + +#if !defined(PFFFT_SIMD_DISABLE) +typedef union v4sf_union { + v4sf v; + float f[4]; +} v4sf_union; + +#if 0 +#include + +#define assertv4(v,f0,f1,f2,f3) assert(v.f[0] == (f0) && v.f[1] == (f1) && v.f[2] == (f2) && v.f[3] == (f3)) + +/* detect bugs with the vector support macros */ +void validate_pffft_simd(void); +void validate_pffft_simd(void) { + float f[16] = { 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 }; + v4sf_union a0, a1, a2, a3, t, u; + memcpy(a0.f, f, 4*sizeof(float)); + memcpy(a1.f, f+4, 4*sizeof(float)); + memcpy(a2.f, f+8, 4*sizeof(float)); + memcpy(a3.f, f+12, 4*sizeof(float)); + + t = a0; u = a1; t.v = VZERO(); + printf("VZERO=[%2g %2g %2g %2g]\n", t.f[0], t.f[1], t.f[2], t.f[3]); assertv4(t, 0, 0, 0, 0); + t.v = VADD(a1.v, a2.v); + printf("VADD(4:7,8:11)=[%2g %2g %2g %2g]\n", t.f[0], t.f[1], t.f[2], t.f[3]); assertv4(t, 12, 14, 16, 18); + t.v = VMUL(a1.v, a2.v); + printf("VMUL(4:7,8:11)=[%2g %2g %2g %2g]\n", t.f[0], t.f[1], t.f[2], t.f[3]); assertv4(t, 32, 45, 60, 77); + t.v = VMADD(a1.v, a2.v,a0.v); + printf("VMADD(4:7,8:11,0:3)=[%2g %2g %2g %2g]\n", t.f[0], t.f[1], t.f[2], t.f[3]); assertv4(t, 32, 46, 62, 80); + INTERLEAVE2(a1.v,a2.v,t.v,u.v); + printf("INTERLEAVE2(4:7,8:11)=[%2g %2g %2g %2g] [%2g %2g %2g %2g]\n", t.f[0], t.f[1], t.f[2], t.f[3], u.f[0], u.f[1], u.f[2], u.f[3]); + assertv4(t, 4, 8, 5, 9); assertv4(u, 6, 10, 7, 11); + UNINTERLEAVE2(a1.v,a2.v,t.v,u.v); + printf("UNINTERLEAVE2(4:7,8:11)=[%2g %2g %2g %2g] [%2g %2g %2g %2g]\n", t.f[0], t.f[1], t.f[2], t.f[3], u.f[0], u.f[1], u.f[2], u.f[3]); + assertv4(t, 4, 6, 8, 10); assertv4(u, 5, 7, 9, 11); + + t.v=LD_PS1(f[15]); + printf("LD_PS1(15)=[%2g %2g %2g %2g]\n", t.f[0], t.f[1], t.f[2], t.f[3]); + assertv4(t, 15, 15, 15, 15); + t.v = VSWAPHL(a1.v, a2.v); + printf("VSWAPHL(4:7,8:11)=[%2g %2g %2g %2g]\n", t.f[0], t.f[1], t.f[2], t.f[3]); + assertv4(t, 8, 9, 6, 7); + VTRANSPOSE4(a0.v, a1.v, a2.v, a3.v); + printf("VTRANSPOSE4(0:3,4:7,8:11,12:15)=[%2g %2g %2g %2g] [%2g %2g %2g %2g] [%2g %2g %2g %2g] [%2g %2g %2g %2g]\n", + a0.f[0], a0.f[1], a0.f[2], a0.f[3], a1.f[0], a1.f[1], a1.f[2], a1.f[3], + a2.f[0], a2.f[1], a2.f[2], a2.f[3], a3.f[0], a3.f[1], a3.f[2], a3.f[3]); + assertv4(a0, 0, 4, 8, 12); assertv4(a1, 1, 5, 9, 13); assertv4(a2, 2, 6, 10, 14); assertv4(a3, 3, 7, 11, 15); +} +#endif +#endif /*!PFFFT_SIMD_DISABLE */ + +#if 0 +/* SSE and co like 16-bytes aligned pointers */ +#define MALLOC_V4SF_ALIGNMENT 64 /* with a 64-byte alignment, we are even aligned on L2 cache lines... */ +void *pffft_aligned_malloc(size_t nb_bytes) { + void *p, *p0 = malloc(nb_bytes + MALLOC_V4SF_ALIGNMENT); + if (!p0) return (void *) 0; + p = (void *) (((size_t) p0 + MALLOC_V4SF_ALIGNMENT) & (~((size_t) (MALLOC_V4SF_ALIGNMENT-1)))); + *((void **) p - 1) = p0; + return p; +} + +void pffft_aligned_free(void *p) { + if (p) free(*((void **) p - 1)); +} + +int pffft_simd_size() { return SIMD_SZ; } +#endif + +/* + passf2 and passb2 has been merged here, fsign = -1 for passf2, +1 for passb2 +*/ +static NEVER_INLINE(void) passf2_ps(int ido, int l1, const v4sf *cc, v4sf *ch, const float *wa1, float fsign) { + int k, i; + int l1ido = l1*ido; + if (ido <= 2) { + for (k=0; k < l1ido; k += ido, ch += ido, cc+= 2*ido) { + ch[0] = VADD(cc[0], cc[ido+0]); + ch[l1ido] = VSUB(cc[0], cc[ido+0]); + ch[1] = VADD(cc[1], cc[ido+1]); + ch[l1ido + 1] = VSUB(cc[1], cc[ido+1]); + } + } else { + for (k=0; k < l1ido; k += ido, ch += ido, cc += 2*ido) { + for (i=0; i 2); + for (k=0; k< l1ido; k += ido, cc+= 3*ido, ch +=ido) { + for (i=0; i 2); + for (k = 0; k < l1; ++k, cc += 5*ido, ch += ido) { + for (i = 0; i < ido-1; i += 2) { + ti5 = VSUB(cc_ref(i , 2), cc_ref(i , 5)); + ti2 = VADD(cc_ref(i , 2), cc_ref(i , 5)); + ti4 = VSUB(cc_ref(i , 3), cc_ref(i , 4)); + ti3 = VADD(cc_ref(i , 3), cc_ref(i , 4)); + tr5 = VSUB(cc_ref(i-1, 2), cc_ref(i-1, 5)); + tr2 = VADD(cc_ref(i-1, 2), cc_ref(i-1, 5)); + tr4 = VSUB(cc_ref(i-1, 3), cc_ref(i-1, 4)); + tr3 = VADD(cc_ref(i-1, 3), cc_ref(i-1, 4)); + ch_ref(i-1, 1) = VADD(cc_ref(i-1, 1), VADD(tr2, tr3)); + ch_ref(i , 1) = VADD(cc_ref(i , 1), VADD(ti2, ti3)); + cr2 = VADD(cc_ref(i-1, 1), VADD(SVMUL(tr11, tr2),SVMUL(tr12, tr3))); + ci2 = VADD(cc_ref(i , 1), VADD(SVMUL(tr11, ti2),SVMUL(tr12, ti3))); + cr3 = VADD(cc_ref(i-1, 1), VADD(SVMUL(tr12, tr2),SVMUL(tr11, tr3))); + ci3 = VADD(cc_ref(i , 1), VADD(SVMUL(tr12, ti2),SVMUL(tr11, ti3))); + cr5 = VADD(SVMUL(ti11, tr5), SVMUL(ti12, tr4)); + ci5 = VADD(SVMUL(ti11, ti5), SVMUL(ti12, ti4)); + cr4 = VSUB(SVMUL(ti12, tr5), SVMUL(ti11, tr4)); + ci4 = VSUB(SVMUL(ti12, ti5), SVMUL(ti11, ti4)); + dr3 = VSUB(cr3, ci4); + dr4 = VADD(cr3, ci4); + di3 = VADD(ci3, cr4); + di4 = VSUB(ci3, cr4); + dr5 = VADD(cr2, ci5); + dr2 = VSUB(cr2, ci5); + di5 = VSUB(ci2, cr5); + di2 = VADD(ci2, cr5); + wr1=wa1[i], wi1=fsign*wa1[i+1], wr2=wa2[i], wi2=fsign*wa2[i+1]; + wr3=wa3[i], wi3=fsign*wa3[i+1], wr4=wa4[i], wi4=fsign*wa4[i+1]; + VCPLXMUL(dr2, di2, LD_PS1(wr1), LD_PS1(wi1)); + ch_ref(i - 1, 2) = dr2; + ch_ref(i, 2) = di2; + VCPLXMUL(dr3, di3, LD_PS1(wr2), LD_PS1(wi2)); + ch_ref(i - 1, 3) = dr3; + ch_ref(i, 3) = di3; + VCPLXMUL(dr4, di4, LD_PS1(wr3), LD_PS1(wi3)); + ch_ref(i - 1, 4) = dr4; + ch_ref(i, 4) = di4; + VCPLXMUL(dr5, di5, LD_PS1(wr4), LD_PS1(wi4)); + ch_ref(i - 1, 5) = dr5; + ch_ref(i, 5) = di5; + } + } +#undef ch_ref +#undef cc_ref +} +#endif + +static NEVER_INLINE(void) radf2_ps(int ido, int l1, const v4sf * RESTRICT cc, v4sf * RESTRICT ch, const float *wa1) { + static const float minus_one = -1.f; + int i, k, l1ido = l1*ido; + for (k=0; k < l1ido; k += ido) { + v4sf a = cc[k], b = cc[k + l1ido]; + ch[2*k] = VADD(a, b); + ch[2*(k+ido)-1] = VSUB(a, b); + } + if (ido < 2) return; + if (ido != 2) { + for (k=0; k < l1ido; k += ido) { + for (i=2; i 5) { + wa[i1-1] = wa[i-1]; + wa[i1] = wa[i]; + } + } + l1 = l2; + } +} /* cffti1 */ + + +static +v4sf *cfftf1_ps(int n, const v4sf *input_readonly, v4sf *work1, v4sf *work2, const float *wa, const int *ifac, int isign) { + v4sf *in = (v4sf*)input_readonly; + v4sf *out = (in == work2 ? work1 : work2); + int nf = ifac[1], k1; + int l1 = 1; + int iw = 0; + assert(in != out && work1 != work2); + for (k1=2; k1<=nf+1; k1++) { + int ip = ifac[k1]; + int l2 = ip*l1; + int ido = n / l2; + int idot = ido + ido; + switch (ip) { +#if 0 + case 5: { + int ix2 = iw + idot; + int ix3 = ix2 + idot; + int ix4 = ix3 + idot; + passf5_ps(idot, l1, in, out, &wa[iw], &wa[ix2], &wa[ix3], &wa[ix4], (float)isign); + } break; +#endif + case 4: { + int ix2 = iw + idot; + int ix3 = ix2 + idot; + passf4_ps(idot, l1, in, out, &wa[iw], &wa[ix2], &wa[ix3], (float)isign); + } break; + case 2: { + passf2_ps(idot, l1, in, out, &wa[iw], (float)isign); + } break; +#if 0 + case 3: { + int ix2 = iw + idot; + passf3_ps(idot, l1, in, out, &wa[iw], &wa[ix2], (float)isign); + } break; +#endif + default: + assert(0); + } + l1 = l2; + iw += (ip - 1)*idot; + if (out == work2) { + out = work1; in = work2; + } else { + out = work2; in = work1; + } + } + + return in; /* this is in fact the output .. */ +} + + +struct PFFFT_Setup { + int N; + int Ncvec; /* nb of complex simd vectors (N/4 if PFFFT_COMPLEX, N/8 if PFFFT_REAL) */ + int ifac[15]; + pffft_transform_t transform; + v4sf *data; /* allocated room for twiddle coefs */ + float *e; /* points into 'data' , N/4*3 elements */ + float *twiddle; /* points into 'data', N/4 elements */ +}; + +static +PFFFT_Setup *pffft_new_setup(int N, pffft_transform_t transform) { + PFFFT_Setup *s = (PFFFT_Setup*)malloc(sizeof(PFFFT_Setup)); + int k, m; + if (!s) return s; + /* unfortunately, the fft size must be a multiple of 16 for complex FFTs + and 32 for real FFTs -- a lot of stuff would need to be rewritten to + handle other cases (or maybe just switch to a scalar fft, I don't know..) */ + if (transform == PFFFT_REAL) { assert((N%(2*SIMD_SZ*SIMD_SZ))==0 && N>0); } + if (transform == PFFFT_COMPLEX) { assert((N%(SIMD_SZ*SIMD_SZ))==0 && N>0); } + /*assert((N % 32) == 0); */ + s->N = N; + s->transform = transform; + /* nb of complex simd vectors */ + s->Ncvec = (transform == PFFFT_REAL ? N/2 : N)/SIMD_SZ; + s->data = (v4sf*)pffft_aligned_malloc(2*(size_t)s->Ncvec * sizeof(v4sf)); + if (!s->data) {free(s); return 0;} + s->e = (float*)s->data; + s->twiddle = (float*)(s->data + (2*s->Ncvec*(SIMD_SZ-1))/SIMD_SZ); + + if (transform == PFFFT_REAL) { + for (k=0; k < s->Ncvec; ++k) { + int i = k/SIMD_SZ; + int j = k%SIMD_SZ; + for (m=0; m < SIMD_SZ-1; ++m) { + float A = (float)(-2*M_PI*(m+1)*k / N); + s->e[(2*(i*3 + m) + 0) * SIMD_SZ + j] = cos(A); + s->e[(2*(i*3 + m) + 1) * SIMD_SZ + j] = sin(A); + } + } + rffti1_ps(N/SIMD_SZ, s->twiddle, s->ifac); + } else { + for (k=0; k < s->Ncvec; ++k) { + int i = k/SIMD_SZ; + int j = k%SIMD_SZ; + for (m=0; m < SIMD_SZ-1; ++m) { + float A = (float)(-2*M_PI*(m+1)*k / N); + s->e[(2*(i*3 + m) + 0)*SIMD_SZ + j] = cos(A); + s->e[(2*(i*3 + m) + 1)*SIMD_SZ + j] = sin(A); + } + } + cffti1_ps(N/SIMD_SZ, s->twiddle, s->ifac); + } + + /* check that N is decomposable with allowed prime factors */ + for (k=0, m=1; k < s->ifac[1]; ++k) { m *= s->ifac[2+k]; } + if (m != N/SIMD_SZ) { + pffft_destroy_setup(s); s = 0; + } + + return s; +} + + +static +void pffft_destroy_setup(PFFFT_Setup *s) { + if (!s) return; + pffft_aligned_free(s->data); + free(s); +} + +#if !defined(PFFFT_SIMD_DISABLE) + +/* [0 0 1 2 3 4 5 6 7 8] -> [0 8 7 6 5 4 3 2 1] */ +static void reversed_copy(int N, const v4sf *in, int in_stride, v4sf *out) { + v4sf g0, g1; + int k; + INTERLEAVE2(in[0], in[1], g0, g1); in += in_stride; + + *--out = VSWAPHL(g0, g1); /* [g0l, g0h], [g1l g1h] -> [g1l, g0h] */ + for (k=1; k < N; ++k) { + v4sf h0, h1; + INTERLEAVE2(in[0], in[1], h0, h1); in += in_stride; + *--out = VSWAPHL(g1, h0); + *--out = VSWAPHL(h0, h1); + g1 = h1; + } + *--out = VSWAPHL(g1, g0); +} + +static void unreversed_copy(int N, const v4sf *in, v4sf *out, int out_stride) { + v4sf g0, g1, h0, h1; + int k; + g0 = g1 = in[0]; ++in; + for (k=1; k < N; ++k) { + h0 = *in++; h1 = *in++; + g1 = VSWAPHL(g1, h0); + h0 = VSWAPHL(h0, h1); + UNINTERLEAVE2(h0, g1, out[0], out[1]); out += out_stride; + g1 = h1; + } + h0 = *in++; h1 = g0; + g1 = VSWAPHL(g1, h0); + h0 = VSWAPHL(h0, h1); + UNINTERLEAVE2(h0, g1, out[0], out[1]); +} + +static +void pffft_zreorder(PFFFT_Setup *setup, const float *in, float *out, pffft_direction_t direction) { + int k, N = setup->N, Ncvec = setup->Ncvec; + const v4sf *vin = (const v4sf*)in; + v4sf *vout = (v4sf*)out; + assert(in != out); + if (setup->transform == PFFFT_REAL) { + int k, dk = N/32; + if (direction == PFFFT_FORWARD) { + for (k=0; k < dk; ++k) { + INTERLEAVE2(vin[k*8 + 0], vin[k*8 + 1], vout[2*(0*dk + k) + 0], vout[2*(0*dk + k) + 1]); + INTERLEAVE2(vin[k*8 + 4], vin[k*8 + 5], vout[2*(2*dk + k) + 0], vout[2*(2*dk + k) + 1]); + } + reversed_copy(dk, vin+2, 8, (v4sf*)(out + N/2)); + reversed_copy(dk, vin+6, 8, (v4sf*)(out + N)); + } else { + for (k=0; k < dk; ++k) { + UNINTERLEAVE2(vin[2*(0*dk + k) + 0], vin[2*(0*dk + k) + 1], vout[k*8 + 0], vout[k*8 + 1]); + UNINTERLEAVE2(vin[2*(2*dk + k) + 0], vin[2*(2*dk + k) + 1], vout[k*8 + 4], vout[k*8 + 5]); + } + unreversed_copy(dk, (v4sf*)(in + N/4), (v4sf*)(out + N - 6*SIMD_SZ), -8); + unreversed_copy(dk, (v4sf*)(in + 3*N/4), (v4sf*)(out + N - 2*SIMD_SZ), -8); + } + } else { + if (direction == PFFFT_FORWARD) { + for (k=0; k < Ncvec; ++k) { + int kk = (k/4) + (k%4)*(Ncvec/4); + INTERLEAVE2(vin[k*2], vin[k*2+1], vout[kk*2], vout[kk*2+1]); + } + } else { + for (k=0; k < Ncvec; ++k) { + int kk = (k/4) + (k%4)*(Ncvec/4); + UNINTERLEAVE2(vin[kk*2], vin[kk*2+1], vout[k*2], vout[k*2+1]); + } + } + } +} + +static +void pffft_cplx_finalize(int Ncvec, const v4sf *in, v4sf *out, const v4sf *e) { + int k, dk = Ncvec/SIMD_SZ; /* number of 4x4 matrix blocks */ + v4sf r0, i0, r1, i1, r2, i2, r3, i3; + v4sf sr0, dr0, sr1, dr1, si0, di0, si1, di1; + assert(in != out); + for (k=0; k < dk; ++k) { + r0 = in[8*k+0]; i0 = in[8*k+1]; + r1 = in[8*k+2]; i1 = in[8*k+3]; + r2 = in[8*k+4]; i2 = in[8*k+5]; + r3 = in[8*k+6]; i3 = in[8*k+7]; + VTRANSPOSE4(r0,r1,r2,r3); + VTRANSPOSE4(i0,i1,i2,i3); + VCPLXMUL(r1,i1,e[k*6+0],e[k*6+1]); + VCPLXMUL(r2,i2,e[k*6+2],e[k*6+3]); + VCPLXMUL(r3,i3,e[k*6+4],e[k*6+5]); + + sr0 = VADD(r0,r2); dr0 = VSUB(r0, r2); + sr1 = VADD(r1,r3); dr1 = VSUB(r1, r3); + si0 = VADD(i0,i2); di0 = VSUB(i0, i2); + si1 = VADD(i1,i3); di1 = VSUB(i1, i3); + + /* + transformation for each column is: + + [1 1 1 1 0 0 0 0] [r0] + [1 0 -1 0 0 -1 0 1] [r1] + [1 -1 1 -1 0 0 0 0] [r2] + [1 0 -1 0 0 1 0 -1] [r3] + [0 0 0 0 1 1 1 1] * [i0] + [0 1 0 -1 1 0 -1 0] [i1] + [0 0 0 0 1 -1 1 -1] [i2] + [0 -1 0 1 1 0 -1 0] [i3] + */ + + r0 = VADD(sr0, sr1); i0 = VADD(si0, si1); + r1 = VADD(dr0, di1); i1 = VSUB(di0, dr1); + r2 = VSUB(sr0, sr1); i2 = VSUB(si0, si1); + r3 = VSUB(dr0, di1); i3 = VADD(di0, dr1); + + *out++ = r0; *out++ = i0; *out++ = r1; *out++ = i1; + *out++ = r2; *out++ = i2; *out++ = r3; *out++ = i3; + } +} + +static +void pffft_cplx_preprocess(int Ncvec, const v4sf *in, v4sf *out, const v4sf *e) { + int k, dk = Ncvec/SIMD_SZ; /* number of 4x4 matrix blocks */ + v4sf r0, i0, r1, i1, r2, i2, r3, i3; + v4sf sr0, dr0, sr1, dr1, si0, di0, si1, di1; + assert(in != out); + for (k=0; k < dk; ++k) { + r0 = in[8*k+0]; i0 = in[8*k+1]; + r1 = in[8*k+2]; i1 = in[8*k+3]; + r2 = in[8*k+4]; i2 = in[8*k+5]; + r3 = in[8*k+6]; i3 = in[8*k+7]; + + sr0 = VADD(r0,r2); dr0 = VSUB(r0, r2); + sr1 = VADD(r1,r3); dr1 = VSUB(r1, r3); + si0 = VADD(i0,i2); di0 = VSUB(i0, i2); + si1 = VADD(i1,i3); di1 = VSUB(i1, i3); + + r0 = VADD(sr0, sr1); i0 = VADD(si0, si1); + r1 = VSUB(dr0, di1); i1 = VADD(di0, dr1); + r2 = VSUB(sr0, sr1); i2 = VSUB(si0, si1); + r3 = VADD(dr0, di1); i3 = VSUB(di0, dr1); + + VCPLXMULCONJ(r1,i1,e[k*6+0],e[k*6+1]); + VCPLXMULCONJ(r2,i2,e[k*6+2],e[k*6+3]); + VCPLXMULCONJ(r3,i3,e[k*6+4],e[k*6+5]); + + VTRANSPOSE4(r0,r1,r2,r3); + VTRANSPOSE4(i0,i1,i2,i3); + + *out++ = r0; *out++ = i0; *out++ = r1; *out++ = i1; + *out++ = r2; *out++ = i2; *out++ = r3; *out++ = i3; + } +} + + +static ALWAYS_INLINE(void) pffft_real_finalize_4x4(const v4sf *in0, const v4sf *in1, const v4sf *in, + const v4sf *e, v4sf *out) { + v4sf r0, i0, r1, i1, r2, i2, r3, i3; + v4sf sr0, dr0, sr1, dr1, si0, di0, si1, di1; + r0 = *in0; i0 = *in1; + r1 = *in++; i1 = *in++; r2 = *in++; i2 = *in++; r3 = *in++; i3 = *in++; + VTRANSPOSE4(r0,r1,r2,r3); + VTRANSPOSE4(i0,i1,i2,i3); + + /* + transformation for each column is: + + [1 1 1 1 0 0 0 0] [r0] + [1 0 -1 0 0 -1 0 1] [r1] + [1 0 -1 0 0 1 0 -1] [r2] + [1 -1 1 -1 0 0 0 0] [r3] + [0 0 0 0 1 1 1 1] * [i0] + [0 -1 0 1 -1 0 1 0] [i1] + [0 -1 0 1 1 0 -1 0] [i2] + [0 0 0 0 -1 1 -1 1] [i3] + */ + + /*cerr << "matrix initial, before e , REAL:\n 1: " << r0 << "\n 1: " << r1 << "\n 1: " << r2 << "\n 1: " << r3 << "\n"; */ + /*cerr << "matrix initial, before e, IMAG :\n 1: " << i0 << "\n 1: " << i1 << "\n 1: " << i2 << "\n 1: " << i3 << "\n"; */ + + VCPLXMUL(r1,i1,e[0],e[1]); + VCPLXMUL(r2,i2,e[2],e[3]); + VCPLXMUL(r3,i3,e[4],e[5]); + + /*cerr << "matrix initial, real part:\n 1: " << r0 << "\n 1: " << r1 << "\n 1: " << r2 << "\n 1: " << r3 << "\n"; */ + /*cerr << "matrix initial, imag part:\n 1: " << i0 << "\n 1: " << i1 << "\n 1: " << i2 << "\n 1: " << i3 << "\n"; */ + + sr0 = VADD(r0,r2); dr0 = VSUB(r0,r2); + sr1 = VADD(r1,r3); dr1 = VSUB(r3,r1); + si0 = VADD(i0,i2); di0 = VSUB(i0,i2); + si1 = VADD(i1,i3); di1 = VSUB(i3,i1); + + r0 = VADD(sr0, sr1); + r3 = VSUB(sr0, sr1); + i0 = VADD(si0, si1); + i3 = VSUB(si1, si0); + r1 = VADD(dr0, di1); + r2 = VSUB(dr0, di1); + i1 = VSUB(dr1, di0); + i2 = VADD(dr1, di0); + + *out++ = r0; + *out++ = i0; + *out++ = r1; + *out++ = i1; + *out++ = r2; + *out++ = i2; + *out++ = r3; + *out++ = i3; + +} + +static NEVER_INLINE(void) pffft_real_finalize(int Ncvec, const v4sf *in, v4sf *out, const v4sf *e) { + int k, dk = Ncvec/SIMD_SZ; /* number of 4x4 matrix blocks */ + /* fftpack order is f0r f1r f1i f2r f2i ... f(n-1)r f(n-1)i f(n)r */ + + v4sf_union cr, ci, *uout = (v4sf_union*)out; + v4sf save = in[7], zero=VZERO(); + float xr0, xi0, xr1, xi1, xr2, xi2, xr3, xi3; + static const float s = (float)(M_SQRT2/2); + + cr.v = in[0]; ci.v = in[Ncvec*2-1]; + assert(in != out); + pffft_real_finalize_4x4(&zero, &zero, in+1, e, out); + + /* + [cr0 cr1 cr2 cr3 ci0 ci1 ci2 ci3] + + [Xr(1)] ] [1 1 1 1 0 0 0 0] + [Xr(N/4) ] [0 0 0 0 1 s 0 -s] + [Xr(N/2) ] [1 0 -1 0 0 0 0 0] + [Xr(3N/4)] [0 0 0 0 1 -s 0 s] + [Xi(1) ] [1 -1 1 -1 0 0 0 0] + [Xi(N/4) ] [0 0 0 0 0 -s -1 -s] + [Xi(N/2) ] [0 -1 0 1 0 0 0 0] + [Xi(3N/4)] [0 0 0 0 0 -s 1 -s] + */ + + xr0=(cr.f[0]+cr.f[2]) + (cr.f[1]+cr.f[3]); uout[0].f[0] = xr0; + xi0=(cr.f[0]+cr.f[2]) - (cr.f[1]+cr.f[3]); uout[1].f[0] = xi0; + xr2=(cr.f[0]-cr.f[2]); uout[4].f[0] = xr2; + xi2=(cr.f[3]-cr.f[1]); uout[5].f[0] = xi2; + xr1= ci.f[0] + s*(ci.f[1]-ci.f[3]); uout[2].f[0] = xr1; + xi1=-ci.f[2] - s*(ci.f[1]+ci.f[3]); uout[3].f[0] = xi1; + xr3= ci.f[0] - s*(ci.f[1]-ci.f[3]); uout[6].f[0] = xr3; + xi3= ci.f[2] - s*(ci.f[1]+ci.f[3]); uout[7].f[0] = xi3; + + for (k=1; k < dk; ++k) { + v4sf save_next = in[8*k+7]; + pffft_real_finalize_4x4(&save, &in[8*k+0], in + 8*k+1, + e + k*6, out + k*8); + save = save_next; + } + +} + +static ALWAYS_INLINE(void) pffft_real_preprocess_4x4(const v4sf *in, + const v4sf *e, v4sf *out, int first) { + v4sf r0=in[0], i0=in[1], r1=in[2], i1=in[3], r2=in[4], i2=in[5], r3=in[6], i3=in[7]; + /* + transformation for each column is: + + [1 1 1 1 0 0 0 0] [r0] + [1 0 0 -1 0 -1 -1 0] [r1] + [1 -1 -1 1 0 0 0 0] [r2] + [1 0 0 -1 0 1 1 0] [r3] + [0 0 0 0 1 -1 1 -1] * [i0] + [0 -1 1 0 1 0 0 1] [i1] + [0 0 0 0 1 1 -1 -1] [i2] + [0 1 -1 0 1 0 0 1] [i3] + */ + + v4sf sr0 = VADD(r0,r3), dr0 = VSUB(r0,r3); + v4sf sr1 = VADD(r1,r2), dr1 = VSUB(r1,r2); + v4sf si0 = VADD(i0,i3), di0 = VSUB(i0,i3); + v4sf si1 = VADD(i1,i2), di1 = VSUB(i1,i2); + + r0 = VADD(sr0, sr1); + r2 = VSUB(sr0, sr1); + r1 = VSUB(dr0, si1); + r3 = VADD(dr0, si1); + i0 = VSUB(di0, di1); + i2 = VADD(di0, di1); + i1 = VSUB(si0, dr1); + i3 = VADD(si0, dr1); + + VCPLXMULCONJ(r1,i1,e[0],e[1]); + VCPLXMULCONJ(r2,i2,e[2],e[3]); + VCPLXMULCONJ(r3,i3,e[4],e[5]); + + VTRANSPOSE4(r0,r1,r2,r3); + VTRANSPOSE4(i0,i1,i2,i3); + + if (!first) { + *out++ = r0; + *out++ = i0; + } + *out++ = r1; + *out++ = i1; + *out++ = r2; + *out++ = i2; + *out++ = r3; + *out++ = i3; +} + +static NEVER_INLINE(void) pffft_real_preprocess(int Ncvec, const v4sf *in, v4sf *out, const v4sf *e) { + int k, dk = Ncvec/SIMD_SZ; /* number of 4x4 matrix blocks */ + /* fftpack order is f0r f1r f1i f2r f2i ... f(n-1)r f(n-1)i f(n)r */ + + v4sf_union Xr, Xi, *uout = (v4sf_union*)out; + float cr0, ci0, cr1, ci1, cr2, ci2, cr3, ci3; + static const float s = (float)M_SQRT2; + assert(in != out); + for (k=0; k < 4; ++k) { + Xr.f[k] = ((float*)in)[8*k]; + Xi.f[k] = ((float*)in)[8*k+4]; + } + + pffft_real_preprocess_4x4(in, e, out+1, 1); /* will write only 6 values */ + + /* + [Xr0 Xr1 Xr2 Xr3 Xi0 Xi1 Xi2 Xi3] + + [cr0] [1 0 2 0 1 0 0 0] + [cr1] [1 0 0 0 -1 0 -2 0] + [cr2] [1 0 -2 0 1 0 0 0] + [cr3] [1 0 0 0 -1 0 2 0] + [ci0] [0 2 0 2 0 0 0 0] + [ci1] [0 s 0 -s 0 -s 0 -s] + [ci2] [0 0 0 0 0 -2 0 2] + [ci3] [0 -s 0 s 0 -s 0 -s] + */ + for (k=1; k < dk; ++k) { + pffft_real_preprocess_4x4(in+8*k, e + k*6, out-1+k*8, 0); + } + + cr0=(Xr.f[0]+Xi.f[0]) + 2*Xr.f[2]; uout[0].f[0] = cr0; + cr1=(Xr.f[0]-Xi.f[0]) - 2*Xi.f[2]; uout[0].f[1] = cr1; + cr2=(Xr.f[0]+Xi.f[0]) - 2*Xr.f[2]; uout[0].f[2] = cr2; + cr3=(Xr.f[0]-Xi.f[0]) + 2*Xi.f[2]; uout[0].f[3] = cr3; + ci0= 2*(Xr.f[1]+Xr.f[3]); uout[2*Ncvec-1].f[0] = ci0; + ci1= s*(Xr.f[1]-Xr.f[3]) - s*(Xi.f[1]+Xi.f[3]); uout[2*Ncvec-1].f[1] = ci1; + ci2= 2*(Xi.f[3]-Xi.f[1]); uout[2*Ncvec-1].f[2] = ci2; + ci3=-s*(Xr.f[1]-Xr.f[3]) - s*(Xi.f[1]+Xi.f[3]); uout[2*Ncvec-1].f[3] = ci3; +} + + +static +void pffft_transform_internal(PFFFT_Setup *setup, const float *finput, float *foutput, v4sf *scratch, + pffft_direction_t direction, int ordered) { + int k, Ncvec = setup->Ncvec; + int nf_odd = (setup->ifac[1] & 1); + +#if 0 + /* temporary buffer is allocated on the stack if the scratch pointer is NULL */ + int stack_allocate = (scratch == 0 ? Ncvec*2 : 1); + VLA_ARRAY_ON_STACK(v4sf, scratch_on_stack, stack_allocate); +#endif + + const v4sf *vinput = (const v4sf*)finput; + v4sf *voutput = (v4sf*)foutput; + v4sf *buff[2]; + int ib = (nf_odd ^ ordered ? 1 : 0); + buff[0] = voutput; buff[1] = scratch; + + assert(VALIGNED(finput) && VALIGNED(foutput)); + + /*assert(finput != foutput); */ + if (direction == PFFFT_FORWARD) { + ib = !ib; + if (setup->transform == PFFFT_REAL) { + ib = (rfftf1_ps(Ncvec*2, vinput, buff[ib], buff[!ib], + setup->twiddle, &setup->ifac[0]) == buff[0] ? 0 : 1); + pffft_real_finalize(Ncvec, buff[ib], buff[!ib], (v4sf*)setup->e); + } else { + v4sf *tmp = buff[ib]; + for (k=0; k < Ncvec; ++k) { + UNINTERLEAVE2(vinput[k*2], vinput[k*2+1], tmp[k*2], tmp[k*2+1]); + } + ib = (cfftf1_ps(Ncvec, buff[ib], buff[!ib], buff[ib], + setup->twiddle, &setup->ifac[0], -1) == buff[0] ? 0 : 1); + pffft_cplx_finalize(Ncvec, buff[ib], buff[!ib], (v4sf*)setup->e); + } + if (ordered) { + pffft_zreorder(setup, (float*)buff[!ib], (float*)buff[ib], PFFFT_FORWARD); + } else ib = !ib; + } else { + if (vinput == buff[ib]) { + ib = !ib; /* may happen when finput == foutput */ + } + if (ordered) { + pffft_zreorder(setup, (float*)vinput, (float*)buff[ib], PFFFT_BACKWARD); + vinput = buff[ib]; ib = !ib; + } + if (setup->transform == PFFFT_REAL) { + pffft_real_preprocess(Ncvec, vinput, buff[ib], (v4sf*)setup->e); + ib = (rfftb1_ps(Ncvec*2, buff[ib], buff[0], buff[1], + setup->twiddle, &setup->ifac[0]) == buff[0] ? 0 : 1); + } else { + pffft_cplx_preprocess(Ncvec, vinput, buff[ib], (v4sf*)setup->e); + ib = (cfftf1_ps(Ncvec, buff[ib], buff[0], buff[1], + setup->twiddle, &setup->ifac[0], +1) == buff[0] ? 0 : 1); + for (k=0; k < Ncvec; ++k) { + INTERLEAVE2(buff[ib][k*2], buff[ib][k*2+1], buff[ib][k*2], buff[ib][k*2+1]); + } + } + } + + if (buff[ib] != voutput) { + /* extra copy required -- this situation should only happen when finput == foutput */ + assert(finput==foutput); + for (k=0; k < Ncvec; ++k) { + v4sf a = buff[ib][2*k], b = buff[ib][2*k+1]; + voutput[2*k] = a; voutput[2*k+1] = b; + } + ib = !ib; + } + assert(buff[ib] == voutput); +} + +#if 0 +void pffft_zconvolve_accumulate(PFFFT_Setup *s, const float *a, const float *b, float *ab, float scaling) { + int Ncvec = s->Ncvec; + const v4sf * RESTRICT va = (const v4sf*)a; + const v4sf * RESTRICT vb = (const v4sf*)b; + v4sf * RESTRICT vab = (v4sf*)ab; + +#ifdef __arm__ + __builtin_prefetch(va); + __builtin_prefetch(vb); + __builtin_prefetch(vab); + __builtin_prefetch(va+2); + __builtin_prefetch(vb+2); + __builtin_prefetch(vab+2); + __builtin_prefetch(va+4); + __builtin_prefetch(vb+4); + __builtin_prefetch(vab+4); + __builtin_prefetch(va+6); + __builtin_prefetch(vb+6); + __builtin_prefetch(vab+6); +# ifndef __clang__ +# define ZCONVOLVE_USING_INLINE_NEON_ASM +# endif +#endif + + float ar, ai, br, bi, abr, abi; +#ifndef ZCONVOLVE_USING_INLINE_ASM + v4sf vscal = LD_PS1(scaling); + int i; +#endif + + assert(VALIGNED(a) && VALIGNED(b) && VALIGNED(ab)); + ar = ((v4sf_union*)va)[0].f[0]; + ai = ((v4sf_union*)va)[1].f[0]; + br = ((v4sf_union*)vb)[0].f[0]; + bi = ((v4sf_union*)vb)[1].f[0]; + abr = ((v4sf_union*)vab)[0].f[0]; + abi = ((v4sf_union*)vab)[1].f[0]; + +#ifdef ZCONVOLVE_USING_INLINE_ASM /* inline asm version, unfortunately miscompiled by clang 3.2, at least on ubuntu.. so this will be restricted to gcc */ + const float *a_ = a, *b_ = b; float *ab_ = ab; + int N = Ncvec; + asm volatile("mov r8, %2 \n" + "vdup.f32 q15, %4 \n" + "1: \n" + "pld [%0,#64] \n" + "pld [%1,#64] \n" + "pld [%2,#64] \n" + "pld [%0,#96] \n" + "pld [%1,#96] \n" + "pld [%2,#96] \n" + "vld1.f32 {q0,q1}, [%0,:128]! \n" + "vld1.f32 {q4,q5}, [%1,:128]! \n" + "vld1.f32 {q2,q3}, [%0,:128]! \n" + "vld1.f32 {q6,q7}, [%1,:128]! \n" + "vld1.f32 {q8,q9}, [r8,:128]! \n" + + "vmul.f32 q10, q0, q4 \n" + "vmul.f32 q11, q0, q5 \n" + "vmul.f32 q12, q2, q6 \n" + "vmul.f32 q13, q2, q7 \n" + "vmls.f32 q10, q1, q5 \n" + "vmla.f32 q11, q1, q4 \n" + "vld1.f32 {q0,q1}, [r8,:128]! \n" + "vmls.f32 q12, q3, q7 \n" + "vmla.f32 q13, q3, q6 \n" + "vmla.f32 q8, q10, q15 \n" + "vmla.f32 q9, q11, q15 \n" + "vmla.f32 q0, q12, q15 \n" + "vmla.f32 q1, q13, q15 \n" + "vst1.f32 {q8,q9},[%2,:128]! \n" + "vst1.f32 {q0,q1},[%2,:128]! \n" + "subs %3, #2 \n" + "bne 1b \n" + : "+r"(a_), "+r"(b_), "+r"(ab_), "+r"(N) : "r"(scaling) : "r8", "q0","q1","q2","q3","q4","q5","q6","q7","q8","q9", "q10","q11","q12","q13","q15","memory"); +#else /* default routine, works fine for non-arm cpus with current compilers */ + for (i=0; i < Ncvec; i += 2) { + v4sf ar, ai, br, bi; + ar = va[2*i+0]; ai = va[2*i+1]; + br = vb[2*i+0]; bi = vb[2*i+1]; + VCPLXMUL(ar, ai, br, bi); + vab[2*i+0] = VMADD(ar, vscal, vab[2*i+0]); + vab[2*i+1] = VMADD(ai, vscal, vab[2*i+1]); + ar = va[2*i+2]; ai = va[2*i+3]; + br = vb[2*i+2]; bi = vb[2*i+3]; + VCPLXMUL(ar, ai, br, bi); + vab[2*i+2] = VMADD(ar, vscal, vab[2*i+2]); + vab[2*i+3] = VMADD(ai, vscal, vab[2*i+3]); + } +#endif + if (s->transform == PFFFT_REAL) { + ((v4sf_union*)vab)[0].f[0] = abr + ar*br*scaling; + ((v4sf_union*)vab)[1].f[0] = abi + ai*bi*scaling; + } +} +#endif + + +#else /* defined(PFFFT_SIMD_DISABLE) */ + +/* standard routine using scalar floats, without SIMD stuff. */ + +#define pffft_zreorder_nosimd pffft_zreorder +static +void pffft_zreorder_nosimd(PFFFT_Setup *setup, const float *in, float *out, pffft_direction_t direction) { + int k, N = setup->N; + if (setup->transform == PFFFT_COMPLEX) { + for (k=0; k < 2*N; ++k) out[k] = in[k]; + return; + } + else if (direction == PFFFT_FORWARD) { + float x_N = in[N-1]; + for (k=N-1; k > 1; --k) out[k] = in[k-1]; + out[0] = in[0]; + out[1] = x_N; + } else { + float x_N = in[1]; + for (k=1; k < N-1; ++k) out[k] = in[k+1]; + out[0] = in[0]; + out[N-1] = x_N; + } +} + +#define pffft_transform_internal_nosimd pffft_transform_internal +static +void pffft_transform_internal_nosimd(PFFFT_Setup *setup, const float *input, float *output, float *scratch, + pffft_direction_t direction, int ordered) { + int Ncvec = setup->Ncvec; + int nf_odd = (setup->ifac[1] & 1); + +#if 0 + /* temporary buffer is allocated on the stack if the scratch pointer is NULL */ + int stack_allocate = (scratch == 0 ? Ncvec*2 : 1); + VLA_ARRAY_ON_STACK(v4sf, scratch_on_stack, stack_allocate); +#endif + float *buff[2]; + int ib; + /* if (scratch == 0) scratch = scratch_on_stack; */ + buff[0] = output; buff[1] = scratch; + + if (setup->transform == PFFFT_COMPLEX) ordered = 0; /* it is always ordered. */ + ib = (nf_odd ^ ordered ? 1 : 0); + + if (direction == PFFFT_FORWARD) { + if (setup->transform == PFFFT_REAL) { + ib = (rfftf1_ps(Ncvec*2, input, buff[ib], buff[!ib], + setup->twiddle, &setup->ifac[0]) == buff[0] ? 0 : 1); + } else { + ib = (cfftf1_ps(Ncvec, input, buff[ib], buff[!ib], + setup->twiddle, &setup->ifac[0], -1) == buff[0] ? 0 : 1); + } + if (ordered) { + pffft_zreorder(setup, buff[ib], buff[!ib], PFFFT_FORWARD); ib = !ib; + } + } else { + if (input == buff[ib]) { + ib = !ib; /* may happen when finput == foutput */ + } + if (ordered) { + pffft_zreorder(setup, input, buff[!ib], PFFFT_BACKWARD); + input = buff[!ib]; + } + if (setup->transform == PFFFT_REAL) { + ib = (rfftb1_ps(Ncvec*2, input, buff[ib], buff[!ib], + setup->twiddle, &setup->ifac[0]) == buff[0] ? 0 : 1); + } else { + ib = (cfftf1_ps(Ncvec, input, buff[ib], buff[!ib], + setup->twiddle, &setup->ifac[0], +1) == buff[0] ? 0 : 1); + } + } + if (buff[ib] != output) { + int k; + /* extra copy required -- this situation should happens only when finput == foutput */ + assert(input==output); + for (k=0; k < Ncvec; ++k) { + float a = buff[ib][2*k], b = buff[ib][2*k+1]; + output[2*k] = a; output[2*k+1] = b; + } + ib = !ib; + } + assert(buff[ib] == output); +} + +#if 0 +#define pffft_zconvolve_accumulate_nosimd pffft_zconvolve_accumulate +void pffft_zconvolve_accumulate_nosimd(PFFFT_Setup *s, const float *a, const float *b, + float *ab, float scaling) { + int i, Ncvec = s->Ncvec; + + if (s->transform == PFFFT_REAL) { + /* take care of the fftpack ordering */ + ab[0] += a[0]*b[0]*scaling; + ab[2*Ncvec-1] += a[2*Ncvec-1]*b[2*Ncvec-1]*scaling; + ++ab; ++a; ++b; --Ncvec; + } + for (i=0; i < Ncvec; ++i) { + float ar, ai, br, bi; + ar = a[2*i+0]; ai = a[2*i+1]; + br = b[2*i+0]; bi = b[2*i+1]; + VCPLXMUL(ar, ai, br, bi); + ab[2*i+0] += ar*scaling; + ab[2*i+1] += ai*scaling; + } +} +#endif + +#endif /* defined(PFFFT_SIMD_DISABLE) */ + +static +void pffft_transform(PFFFT_Setup *setup, const float *input, float *output, float *work, pffft_direction_t direction) { + pffft_transform_internal(setup, input, output, (v4sf*)work, direction, 0); +} + +static +void pffft_transform_ordered(PFFFT_Setup *setup, const float *input, float *output, float *work, pffft_direction_t direction) { + pffft_transform_internal(setup, input, output, (v4sf*)work, direction, 1); +} + +#endif diff --git a/soxr-sys/src/pffft.h b/soxr-sys/src/pffft.h new file mode 100644 index 000000000..63522cacb --- /dev/null +++ b/soxr-sys/src/pffft.h @@ -0,0 +1,197 @@ +/* https://bitbucket.org/jpommier/pffft/raw/483453d8f7661058e74aa4e7cf5c27bcd7887e7a/pffft.h + * with minor changes for libsoxr. */ + +#if !defined PFFT_MACROS_ONLY + +/* Copyright (c) 2013 Julien Pommier ( pommier@modartt.com ) + + Based on original fortran 77 code from FFTPACKv4 from NETLIB, + authored by Dr Paul Swarztrauber of NCAR, in 1985. + + As confirmed by the NCAR fftpack software curators, the following + FFTPACKv5 license applies to FFTPACKv4 sources. My changes are + released under the same terms. + + FFTPACK license: + + http://www.cisl.ucar.edu/css/software/fftpack5/ftpk.html + + Copyright (c) 2004 the University Corporation for Atmospheric + Research ("UCAR"). All rights reserved. Developed by NCAR's + Computational and Information Systems Laboratory, UCAR, + www.cisl.ucar.edu. + + Redistribution and use of the Software in source and binary forms, + with or without modification, is permitted provided that the + following conditions are met: + + - Neither the names of NCAR's Computational and Information Systems + Laboratory, the University Corporation for Atmospheric Research, + nor the names of its sponsors or contributors may be used to + endorse or promote products derived from this Software without + specific prior written permission. + + - Redistributions of source code must retain the above copyright + notices, this list of conditions, and the disclaimer below. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer below in the + documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE + SOFTWARE. +*/ + +/* + PFFFT : a Pretty Fast FFT. + + This is basically an adaptation of the single precision fftpack + (v4) as found on netlib taking advantage of SIMD instruction found + on cpus such as intel x86 (SSE1), powerpc (Altivec), and arm (NEON). + + For architectures where no SIMD instruction is available, the code + falls back to a scalar version. + + Restrictions: + + - 1D transforms only, with 32-bit single precision. + + - supports only transforms for inputs of length N of the form + N=(2^a)*(3^b)*(5^c), a >= 5, b >=0, c >= 0 (32, 48, 64, 96, 128, + 144, 160, etc are all acceptable lengths). Performance is best for + 128<=N<=8192. + + - all (float*) pointers in the functions below are expected to + have an "simd-compatible" alignment, that is 16 bytes on x86 and + powerpc CPUs. + + You can allocate such buffers with the functions + pffft_aligned_malloc / pffft_aligned_free (or with stuff like + posix_memalign..) + +*/ + +#ifndef PFFFT_H +#define PFFFT_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#if PFFFT_DOUBLE +#define float double +#endif + + /* opaque struct holding internal stuff (precomputed twiddle factors) + this struct can be shared by many threads as it contains only + read-only data. + */ + typedef struct PFFFT_Setup PFFFT_Setup; + + /* direction of the transform */ + typedef enum { PFFFT_FORWARD, PFFFT_BACKWARD } pffft_direction_t; + + /* type of transform */ + typedef enum { PFFFT_REAL, PFFFT_COMPLEX } pffft_transform_t; + + /* + prepare for performing transforms of size N -- the returned + PFFFT_Setup structure is read-only so it can safely be shared by + multiple concurrent threads. + */ + static + PFFFT_Setup *pffft_new_setup(int N, pffft_transform_t transform); + static + void pffft_destroy_setup(PFFFT_Setup *); + /* + Perform a Fourier transform , The z-domain data is stored in the + most efficient order for transforming it back, or using it for + convolution. If you need to have its content sorted in the + "usual" way, that is as an array of interleaved complex numbers, + either use pffft_transform_ordered , or call pffft_zreorder after + the forward fft, and before the backward fft. + + Transforms are not scaled: PFFFT_BACKWARD(PFFFT_FORWARD(x)) = N*x. + Typically you will want to scale the backward transform by 1/N. + + The 'work' pointer should point to an area of N (2*N for complex + fft) floats, properly aligned. If 'work' is NULL, then stack will + be used instead (this is probably the best strategy for small + FFTs, say for N < 16384). + + input and output may alias. + */ + static + void pffft_transform(PFFFT_Setup *setup, const float *input, float *output, float *work, pffft_direction_t direction); + + /* + Similar to pffft_transform, but makes sure that the output is + ordered as expected (interleaved complex numbers). This is + similar to calling pffft_transform and then pffft_zreorder. + + input and output may alias. + */ + static + void pffft_transform_ordered(PFFFT_Setup *setup, const float *input, float *output, float *work, pffft_direction_t direction); + + /* + call pffft_zreorder(.., PFFFT_FORWARD) after pffft_transform(..., + PFFFT_FORWARD) if you want to have the frequency components in + the correct "canonical" order, as interleaved complex numbers. + + (for real transforms, both 0-frequency and half frequency + components, which are real, are assembled in the first entry as + F(0)+i*F(n/2+1). Note that the original fftpack did place + F(n/2+1) at the end of the arrays). + + input and output should not alias. + */ + static + void pffft_zreorder(PFFFT_Setup *setup, const float *input, float *output, pffft_direction_t direction); + + /* + Perform a multiplication of the frequency components of dft_a and + dft_b and accumulate them into dft_ab. The arrays should have + been obtained with pffft_transform(.., PFFFT_FORWARD) and should + *not* have been reordered with pffft_zreorder (otherwise just + perform the operation yourself as the dft coefs are stored as + interleaved complex numbers). + + the operation performed is: dft_ab += (dft_a * fdt_b)*scaling + + The dft_a, dft_b and dft_ab pointers may alias. + */ + void pffft_zconvolve_accumulate(PFFFT_Setup *setup, const float *dft_a, const float *dft_b, float *dft_ab, float scaling); + + /* + the float buffers must have the correct alignment (16-byte boundary + on intel and powerpc). This function may be used to obtain such + correctly aligned buffers. + */ +#if 0 + void *pffft_aligned_malloc(size_t nb_bytes); + void pffft_aligned_free(void *); + + /* return 4 or 1 wether support SSE/Altivec instructions was enable when building pffft.c */ + int pffft_simd_size(); +#endif + +#undef float + +#ifdef __cplusplus +} +#endif + +#endif + +#endif diff --git a/soxr-sys/src/pffft32.c b/soxr-sys/src/pffft32.c new file mode 100644 index 000000000..f48080949 --- /dev/null +++ b/soxr-sys/src/pffft32.c @@ -0,0 +1,39 @@ +/* SoX Resampler Library Copyright (c) 2007-13 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +#define SIMD_ALIGNED_FREE free +#define SIMD_ALIGNED_MALLOC malloc +#define PFFFT_SIMD_DISABLE +#define PFFFT_DOUBLE 0 +#include "pffft-wrap.c" + +#include "filter.h" +#include "rdft_t.h" + +static void * setup(int len) {return pffft_new_setup(len, PFFFT_REAL);} +static void delete_setup(void * setup) {pffft_destroy_setup(setup);} +static void forward (int length, void * setup, float * h, float * scratch) {pffft_transform (setup, h, h, scratch, PFFFT_FORWARD); (void)length;} +static void oforward (int length, void * setup, float * h, float * scratch) {pffft_transform_ordered(setup, h, h, scratch, PFFFT_FORWARD); (void)length;} +static void backward (int length, void * setup, float * H, float * scratch) {pffft_transform (setup, H, H, scratch, PFFFT_BACKWARD);(void)length;} +static void obackward(int length, void * setup, float * H, float * scratch) {pffft_transform_ordered(setup, H, H, scratch, PFFFT_BACKWARD);(void)length;} +static void convolve(int length, void * setup, float * H, float const * with) { pffft_zconvolve(setup, H, with, H); (void)length;} +static int multiplier(void) {return 1;} +static int flags(void) {return RDFT_NEEDS_SCRATCH;} + +fn_t _soxr_rdft32_cb[] = { + (fn_t)setup, + (fn_t)setup, + (fn_t)delete_setup, + (fn_t)forward, + (fn_t)oforward, + (fn_t)backward, + (fn_t)obackward, + (fn_t)convolve, + (fn_t)_soxr_ordered_partial_convolve_f, + (fn_t)multiplier, + (fn_t)pffft_reorder_back, + (fn_t)malloc, + (fn_t)calloc, + (fn_t)free, + (fn_t)flags, +}; diff --git a/soxr-sys/src/pffft32s.c b/soxr-sys/src/pffft32s.c new file mode 100644 index 000000000..7798a45c0 --- /dev/null +++ b/soxr-sys/src/pffft32s.c @@ -0,0 +1,34 @@ +/* SoX Resampler Library Copyright (c) 2007-13 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +#define PFFFT_DOUBLE 0 +#include "pffft-wrap.c" + +#include "rdft_t.h" + +static void * setup(int len) {return pffft_new_setup(len, PFFFT_REAL);} +static void forward (int length, void * setup, float * h, float * scratch) {pffft_transform (setup, h, h, scratch, PFFFT_FORWARD); (void)length;} +static void oforward (int length, void * setup, float * h, float * scratch) {pffft_transform_ordered(setup, h, h, scratch, PFFFT_FORWARD); (void)length;} +static void backward (int length, void * setup, float * H, float * scratch) {pffft_transform (setup, H, H, scratch, PFFFT_BACKWARD);(void)length;} +static void obackward(int length, void * setup, float * H, float * scratch) {pffft_transform_ordered(setup, H, H, scratch, PFFFT_BACKWARD);(void)length;} +static void convolve(int length, void * setup, float * H, float const * with) {pffft_zconvolve(setup, H, with, H); (void)length;} +static int multiplier(void) {return 1;} +static int flags(void) {return RDFT_IS_SIMD | RDFT_NEEDS_SCRATCH;} + +fn_t _soxr_rdft32s_cb[] = { + (fn_t)setup, + (fn_t)setup, + (fn_t)pffft_destroy_setup, + (fn_t)forward, + (fn_t)oforward, + (fn_t)backward, + (fn_t)obackward, + (fn_t)convolve, + (fn_t)ORDERED_PARTIAL_CONVOLVE_SIMD, + (fn_t)multiplier, + (fn_t)pffft_reorder_back, + (fn_t)SIMD_ALIGNED_MALLOC, + (fn_t)SIMD_ALIGNED_CALLOC, + (fn_t)SIMD_ALIGNED_FREE, + (fn_t)flags, +}; diff --git a/soxr-sys/src/pffft64s.c b/soxr-sys/src/pffft64s.c new file mode 100644 index 000000000..7c37c9d4d --- /dev/null +++ b/soxr-sys/src/pffft64s.c @@ -0,0 +1,34 @@ +/* SoX Resampler Library Copyright (c) 2007-16 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +#define PFFFT_DOUBLE 1 +#include "pffft-wrap.c" + +#include "rdft_t.h" + +static void * setup(int len) {return pffft_new_setup(len, PFFFT_REAL);} +static void forward (int length, void * setup, double * h, double * scratch) {pffft_transform (setup, h, h, scratch, PFFFT_FORWARD); (void)length;} +static void oforward (int length, void * setup, double * h, double * scratch) {pffft_transform_ordered(setup, h, h, scratch, PFFFT_FORWARD); (void)length;} +static void backward (int length, void * setup, double * H, double * scratch) {pffft_transform (setup, H, H, scratch, PFFFT_BACKWARD);(void)length;} +static void obackward(int length, void * setup, double * H, double * scratch) {pffft_transform_ordered(setup, H, H, scratch, PFFFT_BACKWARD);(void)length;} +static void convolve(int length, void * setup, double * H, double const * with) {pffft_zconvolve(setup, H, with, H); (void)length;} +static int multiplier(void) {return 1;} +static int flags(void) {return RDFT_IS_SIMD | RDFT_NEEDS_SCRATCH;} + +fn_t _soxr_rdft64s_cb[] = { + (fn_t)setup, + (fn_t)setup, + (fn_t)pffft_destroy_setup, + (fn_t)forward, + (fn_t)oforward, + (fn_t)backward, + (fn_t)obackward, + (fn_t)convolve, + (fn_t)ORDERED_PARTIAL_CONVOLVE_SIMD, + (fn_t)multiplier, + (fn_t)pffft_reorder_back, + (fn_t)SIMD_ALIGNED_MALLOC, + (fn_t)SIMD_ALIGNED_CALLOC, + (fn_t)SIMD_ALIGNED_FREE, + (fn_t)flags, +}; diff --git a/soxr-sys/src/poly-fir.h b/soxr-sys/src/poly-fir.h new file mode 100644 index 000000000..d138e030f --- /dev/null +++ b/soxr-sys/src/poly-fir.h @@ -0,0 +1,150 @@ +/* SoX Resampler Library Copyright (c) 2007-16 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +/* Resample using an interpolated poly-phase FIR with length LEN. */ +/* Input must be followed by FIR_LENGTH-1 samples. */ + +#if COEF_INTERP != 1 && COEF_INTERP != 2 && COEF_INTERP != 3 + #error COEF_INTERP +#endif + +#if SIMD_AVX || SIMD_SSE || SIMD_NEON + #define N (FIR_LENGTH>>2) + + #if COEF_INTERP == 1 + #define _ sum=vMac(vMac(b,X,a),vLdu(in+j*4),sum), ++j; + #elif COEF_INTERP == 2 + #define _ sum=vMac(vMac(vMac(c,X,b),X,a),vLdu(in+j*4),sum), ++j; + #else + #define _ sum=vMac(vMac(vMac(vMac(d,X,c),X,b),X,a),vLdu(in+j*4),sum), ++j; + #endif + + #define a coefs[(COEF_INTERP+1)*(N*phase+j)+(COEF_INTERP-0)] + #define b coefs[(COEF_INTERP+1)*(N*phase+j)+(COEF_INTERP-1)] + #define c coefs[(COEF_INTERP+1)*(N*phase+j)+(COEF_INTERP-2)] + #define d coefs[(COEF_INTERP+1)*(N*phase+j)+(COEF_INTERP-3)] + + #define BEGINNING v4_t X = vLds(x), sum = vZero(); \ + v4_t const * const __restrict coefs = (v4_t *)COEFS + #define END vStorSum(output+i, sum) + #define cc(n) case n: core(n); break + #define CORE(n) switch (n) {cc(2); cc(3); cc(4); cc(5); cc(6); default: core(n);} +#else + #define N FIR_LENGTH + + #if COEF_INTERP == 1 + #define _ sum += (b*x + a)*in[j], ++j; + #elif COEF_INTERP == 2 + #define _ sum += ((c*x + b)*x + a)*in[j], ++j; + #else + #define _ sum += (((d*x + c)*x + b)*x + a)*in[j], ++j; + #endif + + #define a (coef(COEFS, COEF_INTERP, N, phase, 0,j)) + #define b (coef(COEFS, COEF_INTERP, N, phase, 1,j)) + #define c (coef(COEFS, COEF_INTERP, N, phase, 2,j)) + #define d (coef(COEFS, COEF_INTERP, N, phase, 3,j)) + + #define BEGINNING sample_t sum = 0 + #define END output[i] = sum + #define CORE(n) core(n) +#endif + + + +#define floatPrecCore(n) { \ + float_step_t at = p->at.flt; \ + for (i = 0; (int)at < num_in; ++i, at += p->step.flt) { \ + sample_t const * const __restrict in = input + (int)at; \ + float_step_t frac = at - (int)at; \ + int phase = (int)(frac * (1 << PHASE_BITS)); \ + sample_t x = (sample_t)(frac * (1 << PHASE_BITS) - phase); \ + int j = 0; \ + BEGINNING; CONVOLVE(n); END; \ + } \ + fifo_read(&p->fifo, (int)at, NULL); \ + p->at.flt = at - (int)at; } /* Could round to 1 in some cirmcumstances. */ + + + +#define highPrecCore(n) { \ + step_t at; at.fix = p->at.fix; \ + for (i = 0; at.integer < num_in; ++i, \ + at.fix.ls.all += p->step.fix.ls.all, \ + at.whole += p->step.whole + (at.fix.ls.all < p->step.fix.ls.all)) { \ + sample_t const * const __restrict in = input + at.integer; \ + uint32_t frac = at.fraction; \ + int phase = (int)(frac >> (32 - PHASE_BITS)); /* High-order bits */ \ + /* Low-order bits, scaled to [0,1): */ \ + sample_t x = (sample_t)((frac << PHASE_BITS) * (1 / MULT32)); \ + int j = 0; \ + BEGINNING; CONVOLVE(n); END; \ + } \ + fifo_read(&p->fifo, at.integer, NULL); \ + p->at.whole = at.fraction; \ + p->at.fix.ls = at.fix.ls; } + + + +#define stdPrecCore(n) { \ + int64p_t at; at.all = p->at.whole; \ + for (i = 0; at.parts.ms < num_in; ++i, at.all += p->step.whole) { \ + sample_t const * const __restrict in = input + at.parts.ms; \ + uint32_t const frac = at.parts.ls; \ + int phase = (int)(frac >> (32 - PHASE_BITS)); /* high-order bits */ \ + /* Low-order bits, scaled to [0,1): */ \ + sample_t x = (sample_t)((frac << PHASE_BITS) * (1 / MULT32)); \ + int j = 0; \ + BEGINNING; CONVOLVE(n); END; \ + } \ + fifo_read(&p->fifo, at.parts.ms, NULL); \ + p->at.whole = at.parts.ls; } + + + +#if WITH_FLOAT_STD_PREC_CLOCK + #define SPCORE floatPrecCore +#else + #define SPCORE stdPrecCore +#endif + + + +#if WITH_HI_PREC_CLOCK + #define core(n) if (p->use_hi_prec_clock) highPrecCore(n) else SPCORE(n) +#else + #define core(n) SPCORE(n) +#endif + + + +static void FUNCTION(stage_t * p, fifo_t * output_fifo) +{ + sample_t const * input = stage_read_p(p); + int num_in = min(stage_occupancy(p), p->input_size); + int i, max_num_out = 1 + (int)(num_in * p->out_in_ratio); + sample_t * const __restrict output = fifo_reserve(output_fifo, max_num_out); + + CORE(N); + assert(max_num_out - i >= 0); + fifo_trim_by(output_fifo, max_num_out - i); +} + + + +#undef _ +#undef a +#undef b +#undef c +#undef d +#undef CORE +#undef cc +#undef core +#undef COEF_INTERP +#undef N +#undef BEGINNING +#undef END +#undef CONVOLVE +#undef FIR_LENGTH +#undef FUNCTION +#undef PHASE_BITS diff --git a/soxr-sys/src/poly-fir0.h b/soxr-sys/src/poly-fir0.h new file mode 100644 index 000000000..76fca2d6b --- /dev/null +++ b/soxr-sys/src/poly-fir0.h @@ -0,0 +1,56 @@ +/* SoX Resampler Library Copyright (c) 2007-16 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +/* Resample using a non-interpolated poly-phase FIR with length LEN. */ +/* Input must be followed by FIR_LENGTH-1 samples. */ + +#if SIMD_AVX || SIMD_SSE || SIMD_NEON + #define N (FIR_LENGTH>>2) + #define BEGINNING v4_t sum = vZero(); \ + v4_t const * const __restrict coefs = (v4_t *)COEFS + N * rem; + #define _ sum = vMac(vLdu(at+j*4), coefs[j], sum), ++j; + #define END vStorSum(output+i, sum) + #define cc(n) case n: core(n); break + #define CORE(n) switch (n) {cc(2); cc(3); cc(4); cc(5); cc(6); default: core(n);} +#else + #define N FIR_LENGTH + #define BEGINNING sample_t sum = 0; \ + sample_t const * const __restrict coefs = (sample_t *)COEFS + N * rem; + #define _ sum += coefs[j]*at[j], ++j; + #define END output[i] = sum + #define CORE(n) core(n) +#endif + +#define core(n) \ + for (i = 0; at < num_in * p->L; ++i, at += step) { \ + int const div = at / p->L, rem = at % p->L; \ + sample_t const * const __restrict at = input + div; \ + int j = 0; BEGINNING; CONVOLVE(n); END;} + +static void FUNCTION(stage_t * p, fifo_t * output_fifo) +{ + int num_in = min(stage_occupancy(p), p->input_size); + if (num_in) { + sample_t const * input = stage_read_p(p); + int at = p->at.integer, step = p->step.integer; + int i, num_out = (num_in * p->L - at + step - 1) / step; + sample_t * __restrict output = fifo_reserve(output_fifo, num_out); + + CORE(N); + assert(i == num_out); + fifo_read(&p->fifo, at / p->L, NULL); + p->at.integer = at % p->L; + } +} + +#undef _ +#undef CORE +#undef cc +#undef core +#undef N +#undef BEGINNING +#undef MIDDLE +#undef END +#undef CONVOLVE +#undef FIR_LENGTH +#undef FUNCTION diff --git a/soxr-sys/src/rdft.h b/soxr-sys/src/rdft.h new file mode 100644 index 000000000..59ba17417 --- /dev/null +++ b/soxr-sys/src/rdft.h @@ -0,0 +1,31 @@ +/* SoX Resampler Library Copyright (c) 2007-13 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +void ORDERED_CONVOLVE(int n, void * not_used, DFT_FLOAT * a, const DFT_FLOAT * b) +{ + int i; + a[0] *= b[0]; + a[1] *= b[1]; + for (i = 2; i < n; i += 2) { + DFT_FLOAT tmp = a[i]; + a[i ] = b[i ] * tmp - b[i+1] * a[i+1]; + a[i+1] = b[i+1] * tmp + b[i ] * a[i+1]; + } + (void)not_used; +} + +void ORDERED_PARTIAL_CONVOLVE(int n, DFT_FLOAT * a, const DFT_FLOAT * b) +{ + int i; + a[0] *= b[0]; + for (i = 2; i < n; i += 2) { + DFT_FLOAT tmp = a[i]; + a[i ] = b[i ] * tmp - b[i+1] * a[i+1]; + a[i+1] = b[i+1] * tmp + b[i ] * a[i+1]; + } + a[1] = b[i] * a[i] - b[i+1] * a[i+1]; +} + +#undef ORDERED_CONVOLVE +#undef ORDERED_PARTIAL_CONVOLVE +#undef DFT_FLOAT diff --git a/soxr-sys/src/rdft_t.h b/soxr-sys/src/rdft_t.h new file mode 100644 index 000000000..293d9c37b --- /dev/null +++ b/soxr-sys/src/rdft_t.h @@ -0,0 +1,24 @@ +/* SoX Resampler Library Copyright (c) 2007-13 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +typedef void (* fn_t)(void); + +#define rdft_forward_setup (*(void * (*)(int))RDFT_CB[0]) +#define rdft_backward_setup (*(void * (*)(int))RDFT_CB[1]) +#define rdft_delete_setup (*(void (*)(void *))RDFT_CB[2]) +#define rdft_forward (*(void (*)(int, void *, void *, void *))RDFT_CB[3]) +#define rdft_oforward (*(void (*)(int, void *, void *, void *))RDFT_CB[4]) +#define rdft_backward (*(void (*)(int, void *, void *, void *))RDFT_CB[5]) +#define rdft_obackward (*(void (*)(int, void *, void *, void *))RDFT_CB[6]) +#define rdft_convolve (*(void (*)(int, void *, void *, void const *))RDFT_CB[7]) +#define rdft_convolve_portion (*(void (*)(int, void *, void const *))RDFT_CB[8]) +#define rdft_multiplier (*(int (*)(void))RDFT_CB[9]) +#define rdft_reorder_back (*(void (*)(int, void *, void *, void *))RDFT_CB[10]) +#define rdft_malloc (*(void * (*)(size_t))RDFT_CB[11]) +#define rdft_calloc (*(void * (*)(size_t, size_t))RDFT_CB[12]) +#define rdft_free (*(void (*)(void *))RDFT_CB[13]) +#define rdft_flags (*(int (*)(void))RDFT_CB[14]) + +/* Flag templates: */ +#define RDFT_IS_SIMD 1 +#define RDFT_NEEDS_SCRATCH 2 diff --git a/soxr-sys/src/rint-clip.h b/soxr-sys/src/rint-clip.h new file mode 100644 index 000000000..bfb645847 --- /dev/null +++ b/soxr-sys/src/rint-clip.h @@ -0,0 +1,158 @@ +/* SoX Resampler Library Copyright (c) 2007-16 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +#if defined DITHER + +#define DITHERING + (1./32)*(int)(((ran1>>=3)&31)-((ran2>>=3)&31)) +#define DITHER_RAND (seed = 1664525UL * seed + 1013904223UL) >> 3 +#define DITHER_VARS unsigned long ran1 = DITHER_RAND, ran2 = DITHER_RAND +#define SEED_ARG , unsigned long * seed0 +#define SAVE_SEED *seed0 = seed +#define COPY_SEED unsigned long seed = *seed0; +#define COPY_SEED1 unsigned long seed1 = seed +#define PASS_SEED1 , &seed1 +#define PASS_SEED , &seed +#define FLOATD double + +#else + +#define DITHERING +#define DITHER_VARS +#define SEED_ARG +#define SAVE_SEED +#define COPY_SEED +#define COPY_SEED1 +#define PASS_SEED1 +#define PASS_SEED +#define FLOATD FLOATX + +#endif + +#define DO_16 _;_;_;_;_;_;_;_;_;_;_;_;_;_;_;_ + + + +#if defined FE_INVALID && defined FPU_RINT +static void RINT_CLIP(RINT_T * const dest, FLOATX const * const src, + unsigned stride, size_t i, size_t const n, size_t * const clips SEED_ARG) +{ + COPY_SEED + DITHER_VARS; + for (; i < n; ++i) { + FLOATD const d = src[i] DITHERING; + RINT(dest[stride * i], d); + if (fe_test_invalid()) { + fe_clear_invalid(); + dest[stride * i] = d > 0? RINT_MAX : -RINT_MAX - 1; + ++*clips; + } + } + SAVE_SEED; +} +#endif + + + +static size_t LSX_RINT_CLIP(void * * const dest0, FLOATX const * const src, + size_t const n SEED_ARG) +{ + size_t i, clips = 0; + RINT_T * dest = *dest0; + COPY_SEED +#if defined FE_INVALID && defined FPU_RINT +#define _ RINT(dest[i], src[i] DITHERING); ++i + for (i = 0; i < (n & ~15u);) { + COPY_SEED1; + DITHER_VARS; + DO_16; + if (fe_test_invalid()) { + fe_clear_invalid(); + RINT_CLIP(dest, src, 1, i - 16, i, &clips PASS_SEED1); + } + } + RINT_CLIP(dest, src, 1, i, n, &clips PASS_SEED); +#else +#define _ d = src[i] DITHERING, dest[i++] = (RINT_T)(d > 0? \ + d+.5 >= N? ++clips, N-1 : d+.5 : d-.5 <= -N-1? ++clips, -N:d-.5) + const double N = 1. + RINT_MAX; + double d; + for (i = 0; i < (n & ~15u);) { + DITHER_VARS; + DO_16; + } + { + DITHER_VARS; + for (; i < n; _); + } +#endif + SAVE_SEED; + *dest0 = dest + n; + return clips; +} +#undef _ + + + +static size_t LSX_RINT_CLIP_2(void * * dest0, FLOATX const * const * srcs, + unsigned const stride, size_t const n SEED_ARG) +{ + unsigned j; + size_t i, clips = 0; + RINT_T * dest = *dest0; + COPY_SEED +#if defined FE_INVALID && defined FPU_RINT +#define _ RINT(dest[stride * i], src[i] DITHERING); ++i + for (j = 0; j < stride; ++j, ++dest) { + FLOATX const * const src = srcs[j]; + for (i = 0; i < (n & ~15u);) { + COPY_SEED1; + DITHER_VARS; + DO_16; + if (fe_test_invalid()) { + fe_clear_invalid(); + RINT_CLIP(dest, src, stride, i - 16, i, &clips PASS_SEED1); + } + } + RINT_CLIP(dest, src, stride, i, n, &clips PASS_SEED); + } +#else +#define _ d = src[i] DITHERING, dest[stride * i++] = (RINT_T)(d > 0? \ + d+.5 >= N? ++clips, N-1 : d+.5 : d-.5 <= -N-1? ++clips, -N:d-.5) + const double N = 1. + RINT_MAX; + double d; + for (j = 0; j < stride; ++j, ++dest) { + FLOATX const * const src = srcs[j]; + for (i = 0; i < (n & ~15u);) { + DITHER_VARS; + DO_16; + } + { + DITHER_VARS; + for (; i < n; _); + } + } +#endif + SAVE_SEED; + *dest0 = dest + stride * (n - 1); + return clips; +} +#undef _ + +#undef FLOATD +#undef PASS_SEED +#undef PASS_SEED1 +#undef COPY_SEED1 +#undef COPY_SEED +#undef SAVE_SEED +#undef SEED_ARG +#undef DITHER_VARS +#undef DITHERING +#undef DITHER + +#undef RINT_MAX +#undef RINT_T +#undef FPU_RINT +#undef RINT +#undef RINT_CLIP +#undef LSX_RINT_CLIP +#undef LSX_RINT_CLIP_2 diff --git a/soxr-sys/src/rint.h b/soxr-sys/src/rint.h new file mode 100644 index 000000000..2f1dfbed6 --- /dev/null +++ b/soxr-sys/src/rint.h @@ -0,0 +1,102 @@ +/* SoX Resampler Library Copyright (c) 2007-16 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +#if !defined soxr_rint_included +#define soxr_rint_included + +#include "std-types.h" + +/* For x86, compiler-supplied versions of these functions (where available) + * can have poor performance (e.g. mingw32), so prefer these asm versions: */ + +#if defined __GNUC__ && (defined __i386__ || defined __x86_64__) + #define FPU_RINT32 + #define FPU_RINT16 + #define rint32D(a,b) __asm__ __volatile__("fistpl %0": "=m"(a): "t"(b): "st") + #define rint16D(a,b) __asm__ __volatile__("fistps %0": "=m"(a): "t"(b): "st") + #define rint32F rint32D + #define rint16F rint16D + #define FE_INVALID 1 + static __inline int fe_test_invalid(void) { + int status_word; + __asm__ __volatile__("fnstsw %%ax": "=a"(status_word)); + return status_word & FE_INVALID; + } + static __inline int fe_clear_invalid(void) { + int32_t status[7]; + __asm__ __volatile__("fnstenv %0": "=m"(status)); + status[1] &= ~FE_INVALID; + __asm__ __volatile__("fldenv %0": : "m"(*status)); + return 0; + } +#elif defined _MSC_VER && defined _M_IX86 + #define FPU_RINT32 + #define FPU_RINT16 + #define rint_fn(N,Y,X) \ + static __inline void N(Y *y, X x) {Y t; {__asm fld x __asm fistp t} *y=t;} + rint_fn(rint32d, int32_t, double) + rint_fn(rint32f, int32_t, float ) + rint_fn(rint16d, int16_t, double) + rint_fn(rint16f, int16_t, float ) + #define rint32D(y,x) rint32d(&(y),x) + #define rint32F(y,x) rint32f(&(y),x) + #define rint16D(y,x) rint16d(&(y),x) + #define rint16F(y,x) rint16f(&(y),x) + #define FE_INVALID 1 + static __inline int fe_test_invalid(void) { + short status_word; + __asm fnstsw status_word + return status_word & FE_INVALID; + } + static __inline int fe_clear_invalid(void) { + int32_t status[7]; + __asm fnstenv status + status[1] &= ~FE_INVALID; + __asm fldenv status + return 0; + } +#elif defined _MSC_VER && defined _M_X64 + #include + #include + #define FPU_RINT32 + #define FPU_RINT16 + static __inline void rint32d(int32_t *y, double x) { + *y = _mm_cvtsd_si32(_mm_load_sd(&x));} + static __inline void rint32f(int32_t *y, float x) { + *y = _mm_cvtss_si32(_mm_load_ss(&x));} + static __inline void rint16d(int16_t *y, double x) { + x = x*65536+32738; *y = (int16_t)(_mm_cvtsd_si32(_mm_load_sd(&x)) >> 16);} + #define rint32D(y,x) rint32d(&(y),x) + #define rint32F(y,x) rint32f(&(y),x) + #define rint16D(y,x) rint16d(&(y),x) + #define rint16F(y,x) rint16d(&(y),(double)(x)) + #define FE_INVALID 1 + #define fe_test_invalid() (_statusfp() & _SW_INVALID) + #define fe_clear_invalid _clearfp /* Note: clears all. */ +#elif HAVE_LRINT && LONG_MAX == 2147483647L && HAVE_FENV_H + #include + #include + #define FPU_RINT32 + #define rint32D(y,x) ((y)=lrint(x)) + #define rint32F(y,x) ((y)=lrintf(x)) + #define fe_test_invalid() fetestexcept(FE_INVALID) + #define fe_clear_invalid() feclearexcept(FE_INVALID) +#endif + +#if !defined FPU_RINT32 + #define rint32D(y,x) ((y)=(int32_t)((x) < 0? x - .5 : x + .5)) + #define rint32F(y,x) rint32D(y,(double)(x)) +#endif + +#if !defined FPU_RINT16 + #define rint16D(y,x) ((y)=(int16_t)((x) < 0? x - .5 : x + .5)) + #define rint16F(y,x) rint16D(y,(double)(x)) +#endif + +static __inline int32_t rint32(double input) { + int32_t result; rint32D(result, input); return result;} + +static __inline int16_t rint16(double input) { + int16_t result; rint16D(result, input); return result;} + +#endif diff --git a/soxr-sys/src/samplerate.h b/soxr-sys/src/samplerate.h new file mode 100644 index 000000000..911cc5d0c --- /dev/null +++ b/soxr-sys/src/samplerate.h @@ -0,0 +1 @@ +#include "soxr-lsr.h" diff --git a/soxr-sys/src/soxr-config.h b/soxr-sys/src/soxr-config.h new file mode 100644 index 000000000..a559b5f10 --- /dev/null +++ b/soxr-sys/src/soxr-config.h @@ -0,0 +1,28 @@ +/* SoX Resampler Library Copyright (c) 2007-16 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + + +#if !defined soxr_config_included +#define soxr_config_included + +#define AVCODEC_FOUND 0 +#define AVUTIL_FOUND 0 +#define WITH_PFFFT 0 + +#define HAVE_FENV_H 1 +#define HAVE_STDBOOL_H 1 +#define HAVE_STDINT_H 1 +#define HAVE_LRINT 0 +#define HAVE_BIGENDIAN 0 + +#define WITH_CR32 1 +#define WITH_CR32S 0 +#define WITH_CR64 0 +#define WITH_CR64S 0 +#define WITH_VR32 1 + +#define WITH_HI_PREC_CLOCK 0 +#define WITH_FLOAT_STD_PREC_CLOCK 0 +#define WITH_DEV_TRACE 0 + +#endif diff --git a/soxr-sys/src/soxr-lsr.c b/soxr-sys/src/soxr-lsr.c new file mode 100644 index 000000000..58ab50a21 --- /dev/null +++ b/soxr-sys/src/soxr-lsr.c @@ -0,0 +1,198 @@ +/* SoX Resampler Library Copyright (c) 2007-18 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +/* Wrapper mostly compatible with `libsamplerate'. */ + +#include +#include +#include "soxr.h" +#include "soxr-lsr.h" +#include "rint.h" + + + +SRC_STATE *src_new(SRC_SRCTYPE id, int channels, SRC_ERROR * error) +{ + return src_callback_new(0, id, channels, error, 0); +} + + + +SRC_ERROR src_process(SRC_STATE *p, SRC_DATA * io) +{ + size_t idone , odone; + + if (!p || !io) return -1; + + soxr_set_error( + p, soxr_set_io_ratio(p, 1/io->src_ratio, (size_t)io->output_frames)); + + soxr_process(p, io->data_in, /* hack: */ + (size_t)(io->end_of_input? ~io->input_frames : io->input_frames), + &idone, io->data_out, (size_t)io->output_frames, &odone); + + io->input_frames_used = (long)idone, io->output_frames_gen = (long)odone; + return -!!soxr_error(p); +} + + + +SRC_ERROR src_set_ratio(SRC_STATE * p, double oi_ratio) +{ + return -!!soxr_set_io_ratio(p, 1/oi_ratio, 0); +} + + + +SRC_ERROR src_reset(SRC_STATE * p) +{ + return -!!soxr_clear(p); +} + + + +SRC_ERROR src_error(SRC_STATE * p) +{ + return -!!soxr_error(p); +} + + + +SRC_STATE * src_delete(SRC_STATE * p) +{ + soxr_delete(p); + return 0; +} + + + +SRC_STATE *src_callback_new(src_callback_t fn, + SRC_SRCTYPE id, int channels, SRC_ERROR * error0, void * p) +{ + soxr_quality_spec_t q_spec = soxr_quality_spec(SOXR_LSR0Q + (unsigned)id, 0); + char const * e = getenv("SOXR_LSR_NUM_THREADS"); + soxr_runtime_spec_t r_spec = soxr_runtime_spec(!(e && atoi(e) != 1)); + soxr_error_t error; + soxr_t soxr = 0; + + assert (channels > 0); + soxr = soxr_create(0, 0, (unsigned)channels, &error, 0, &q_spec, &r_spec); + + if (soxr) + error = soxr_set_input_fn(soxr, (soxr_input_fn_t)fn, p, 0); + + if (error0) + *error0 = -!!error; + + return soxr; +} + + + +long src_callback_read(SRC_STATE *p, double oi_ratio, long olen, float * obuf) +{ + if (!p || olen < 0) return -1; + + soxr_set_error(p, soxr_set_io_ratio(p, 1/oi_ratio, (size_t)olen)); + return (long)soxr_output(p, obuf, (size_t)olen); +} + + + +SRC_ERROR src_simple(SRC_DATA * io, SRC_SRCTYPE id, int channels) +{ + size_t idone, odone; + soxr_error_t error; + soxr_quality_spec_t q_spec = soxr_quality_spec(SOXR_LSR0Q + (unsigned)id, 0); + char const * e = getenv("SOXR_LSR_NUM_THREADS"); + soxr_runtime_spec_t r_spec = soxr_runtime_spec(!(e && atoi(e) != 1)); + + if (!io || channels<=0 || io->input_frames<0 || io->output_frames<0) return-1; + + error = soxr_oneshot(1, io->src_ratio, (unsigned)channels, io->data_in, + (size_t)io->input_frames, &idone, io->data_out, (size_t)io->output_frames, + &odone, 0, &q_spec, &r_spec); + + io->input_frames_used = (long)idone, io->output_frames_gen = (long)odone; + + return -!!error; +} + + + +char const * src_get_name(SRC_SRCTYPE id) +{ + static char const * const names[] = { + "LSR best sinc", "LSR medium sinc", "LSR fastest sinc", + "LSR ZOH", "LSR linear", "SoX VHQ"}; + + return (unsigned)id < 5u + !getenv("SOXR_LSR_STRICT")? names[id] : 0; +} + + + +char const * src_get_description(SRC_SRCTYPE id) +{ + return src_get_name(id); +} + + + +char const * src_get_version(void) +{ + return soxr_version(); +} + + + +char const * src_strerror(SRC_ERROR error) +{ + return error == 1? "Placeholder." : error ? "soxr error" : soxr_strerror(0); +} + + + +int src_is_valid_ratio(double oi_ratio) +{ + return getenv("SOXR_LSR_STRICT")? + oi_ratio >= 1./256 && oi_ratio <= 256 : oi_ratio > 0; +} + + + +void src_short_to_float_array(short const * src, float * dest, int len) +{ + assert (src && dest); + + while (len--) dest[len] = (float)(src[len] * (1 / (1. + SHRT_MAX))); +} + + + +void src_float_to_short_array(float const * src, short * dest, int len) +{ + double d, N = 1. + SHRT_MAX; + assert (src && dest); + + while (len--) d = src[len] * N, dest[len] = + (short)(d > N - 1? (short)(N - 1) : d < -N? (short)-N : rint16(d)); +} + + + +void src_int_to_float_array(int const * src, float * dest, int len) +{ + assert (src && dest); + while (len--) dest[len] = (float)(src[len] * (1 / (32768. * 65536.))); +} + + + +void src_float_to_int_array(float const * src, int * dest, int len) +{ + double d, N = 32768. * 65536.; /* N.B. int32, not int! (Also above fn.) */ + assert (src && dest); + + while (len--) d = src[len] * N, dest[len] = + d >= N - 1? (int)(N - 1) : d < -N? (int)(-N) : rint32(d); +} diff --git a/soxr-sys/src/soxr-lsr.h b/soxr-sys/src/soxr-lsr.h new file mode 100644 index 000000000..b1cc24706 --- /dev/null +++ b/soxr-sys/src/soxr-lsr.h @@ -0,0 +1,78 @@ +/* SoX Resampler Library Copyright (c) 2007-18 robs@users.sourceforge.net + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* Wrapper compatible with `libsamplerate' (constant-rate). + * (Libsoxr's native API can be found in soxr.h). */ + +#if !defined SAMPLERATE_H +#define SAMPLERATE_H +#if defined __cplusplus + extern "C" { +#endif + +#if defined SOXR_DLL + #if defined soxr_lsr_EXPORTS + #define SOXR __declspec(dllexport) + #else + #define SOXR __declspec(dllimport) + #endif +#elif defined SOXR_VISIBILITY && defined __GNUC__ && (__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 1) + #define SOXR __attribute__ ((visibility("default"))) +#else + #define SOXR +#endif + +typedef float SRC_SAMPLE; +enum SRC_SRCTYPE_e {SRC_SINC_BEST_QUALITY, SRC_SINC_MEDIUM_QUALITY, + SRC_SINC_FASTEST, SRC_ZERO_ORDER_HOLD, SRC_LINEAR}; +typedef int SRC_SRCTYPE; +typedef int SRC_ERROR; +typedef long (* src_callback_t)(void *, SRC_SAMPLE * *); +typedef struct soxr SRC_STATE; +typedef struct SRC_DATA { + SRC_SAMPLE * data_in, * data_out; + long input_frames, output_frames; + long input_frames_used, output_frames_gen; + int end_of_input; + double src_ratio; +} SRC_DATA; +SOXR SRC_STATE * src_new(SRC_SRCTYPE, int num_channels, SRC_ERROR *); +SOXR SRC_ERROR src_process (SRC_STATE *, SRC_DATA *); +SOXR SRC_ERROR src_set_ratio(SRC_STATE *, double); +SOXR SRC_ERROR src_reset (SRC_STATE *); +SOXR SRC_ERROR src_error (SRC_STATE *); +SOXR SRC_STATE * src_delete (SRC_STATE *); +SOXR SRC_STATE * src_callback_new( + src_callback_t, SRC_SRCTYPE, int, SRC_ERROR *, void *); +SOXR long src_callback_read( + SRC_STATE *, double src_ratio, long, SRC_SAMPLE *); +SOXR SRC_ERROR src_simple(SRC_DATA *, SRC_SRCTYPE, int); +SOXR char const * src_get_name(SRC_SRCTYPE); +SOXR char const * src_get_description(SRC_SRCTYPE); +SOXR char const * src_get_version(void); +SOXR char const * src_strerror(SRC_ERROR); +SOXR int src_is_valid_ratio(double); +SOXR void src_short_to_float_array(short const *, float *, int); +SOXR void src_float_to_short_array(float const *, short *, int); +SOXR void src_int_to_float_array(int const *, float *, int); +SOXR void src_float_to_int_array(float const *, int *, int); + +#undef SOXR +#if defined __cplusplus + } +#endif +#endif diff --git a/soxr-sys/src/soxr.c b/soxr-sys/src/soxr.c new file mode 100644 index 000000000..c2861ac7c --- /dev/null +++ b/soxr-sys/src/soxr.c @@ -0,0 +1,842 @@ +/* SoX Resampler Library Copyright (c) 2007-18 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +#include +#include +#include +#include + +#include "soxr.h" +#include "data-io.h" +#include "internal.h" + +#if AVUTIL_FOUND + #include +#endif + + + +#if WITH_DEV_TRACE + +#include +#include + +int _soxr_trace_level; + +void _soxr_trace(char const * fmt, ...) +{ + va_list args; + va_start(args, fmt); + vfprintf(stderr, fmt, args); + fputc('\n', stderr); + va_end(args); +} + +#endif + + + +char const * soxr_version(void) +{ + return "libsoxr-" SOXR_THIS_VERSION_STR; +} + + + + +typedef void sample_t; /* float or double */ +typedef void (* fn_t)(void); +typedef fn_t control_block_t[10]; + +#define resampler_input (*(sample_t * (*)(void *, sample_t * samples, size_t n))p->control_block[0]) +#define resampler_process (*(void (*)(void *, size_t))p->control_block[1]) +#define resampler_output (*(sample_t const * (*)(void *, sample_t * samples, size_t * n))p->control_block[2]) +#define resampler_flush (*(void (*)(void *))p->control_block[3]) +#define resampler_close (*(void (*)(void *))p->control_block[4]) +#define resampler_delay (*(double (*)(void *))p->control_block[5]) +#define resampler_sizes (*(void (*)(size_t * shared, size_t * channel))p->control_block[6]) +#define resampler_create (*(char const * (*)(void * channel, void * shared, double io_ratio, soxr_quality_spec_t * q_spec, soxr_runtime_spec_t * r_spec, double scale))p->control_block[7]) +#define resampler_set_io_ratio (*(void (*)(void *, double io_ratio, size_t len))p->control_block[8]) +#define resampler_id (*(char const * (*)(void))p->control_block[9]) + +typedef void * resampler_t; /* For one channel. */ +typedef void * resampler_shared_t; /* Between channels. */ +typedef void (* deinterleave_t)(sample_t * * dest, + soxr_datatype_t data_type, void const * * src0, size_t n, unsigned ch); +typedef size_t (* interleave_t)(soxr_datatype_t data_type, void * * dest, + sample_t const * const * src, size_t, unsigned, unsigned long *); + +struct soxr { + unsigned num_channels; + double io_ratio; + soxr_error_t error; + soxr_quality_spec_t q_spec; + soxr_io_spec_t io_spec; + soxr_runtime_spec_t runtime_spec; + + void * input_fn_state; + soxr_input_fn_t input_fn; + size_t max_ilen; + + resampler_shared_t shared; + resampler_t * resamplers; + control_block_t control_block; + deinterleave_t deinterleave; + interleave_t interleave; + + void * * channel_ptrs; + size_t clips; + unsigned long seed; + int flushing; +}; + + + +#if WITH_CR32 || WITH_CR32S || WITH_CR64 || WITH_CR64S + #include "filter.h" +#else + #define lsx_to_3dB(x) ((x)/(x)) +#endif + + + +soxr_quality_spec_t soxr_quality_spec(unsigned long recipe, unsigned long flags) +{ + soxr_quality_spec_t spec, * p = &spec; + unsigned q = recipe & 0xf; /* TODO: move to soxr-lsr.c: */ + unsigned quality = q > SOXR_LSR2Q+2? SOXR_VHQ : q > SOXR_LSR2Q? SOXR_QQ : q; + double rej; + memset(p, 0, sizeof(*p)); + if (quality > SOXR_PRECISIONQ) { + p->e = "invalid quality type"; + return spec; + } + flags |= quality < SOXR_LSR0Q ? RESET_ON_CLEAR : 0; + p->phase_response = "\62\31\144"[(recipe & 0x30)>>4]; + p->stopband_begin = 1; + p->precision = + quality == SOXR_QQ ? 0 : + quality <= SOXR_16_BITQ ? 16 : + quality <= SOXR_32_BITQ ? 4 + quality * 4 : + quality <= SOXR_LSR2Q ? 55 - quality * 4 : /* TODO: move to soxr-lsr.c */ + 0; + rej = p->precision * linear_to_dB(2.); + p->flags = flags; + if (quality <= SOXR_32_BITQ || quality == SOXR_PRECISIONQ) { + #define LOW_Q_BW0 (1385 / 2048.) /* 0.67625 rounded to be a FP exact. */ + p->passband_end = quality == 1? LOW_Q_BW0 : 1 - .05 / lsx_to_3dB(rej); + if (quality <= 2) + p->flags &= ~SOXR_ROLLOFF_NONE, p->flags |= SOXR_ROLLOFF_MEDIUM; + } + else { /* TODO: move to soxr-lsr.c */ + static float const bw[] = {.931f, .832f, .663f}; + p->passband_end = bw[quality - SOXR_LSR0Q]; + if (quality == SOXR_LSR2Q) { + p->flags &= ~SOXR_ROLLOFF_NONE; + p->flags |= SOXR_ROLLOFF_LSR2Q | SOXR_PROMOTE_TO_LQ; + } + } + if (recipe & SOXR_STEEP_FILTER) + p->passband_end = 1 - .01 / lsx_to_3dB(rej); + return spec; +} + + + +char const * soxr_engine(soxr_t p) +{ + return resampler_id(); +} + + + +size_t * soxr_num_clips(soxr_t p) +{ + return &p->clips; +} + + + +soxr_error_t soxr_error(soxr_t p) +{ + return p->error; +} + + + +soxr_runtime_spec_t soxr_runtime_spec(unsigned num_threads) +{ + soxr_runtime_spec_t spec, * p = &spec; + memset(p, 0, sizeof(*p)); + p->log2_min_dft_size = 10; + p->log2_large_dft_size = 17; + p->coef_size_kbytes = 400; + p->num_threads = num_threads; + return spec; +} + + + +soxr_io_spec_t soxr_io_spec( + soxr_datatype_t itype, + soxr_datatype_t otype) +{ + soxr_io_spec_t spec, * p = &spec; + memset(p, 0, sizeof(*p)); + if ((itype | otype) >= SOXR_SPLIT * 2) + p->e = "invalid io datatype(s)"; + else { + p->itype = itype; + p->otype = otype; + p->scale = 1; + } + return spec; +} + + + +#if (WITH_CR32S && WITH_CR32) || (WITH_CR64S && WITH_CR64) + #if defined __GNUC__ && defined __x86_64__ + #define CPUID(type, eax_, ebx_, ecx_, edx_) \ + __asm__ __volatile__ ( \ + "cpuid \n\t" \ + : "=a" (eax_), "=b" (ebx_), "=c" (ecx_), "=d" (edx_) \ + : "a" (type), "c" (0)); + #elif defined __GNUC__ && defined __i386__ + #define CPUID(type, eax_, ebx_, ecx_, edx_) \ + __asm__ __volatile__ ( \ + "mov %%ebx, %%edi \n\t" \ + "cpuid \n\t" \ + "xchg %%edi, %%ebx \n\t" \ + : "=a" (eax_), "=D" (ebx_), "=c" (ecx_), "=d" (edx_) \ + : "a" (type), "c" (0)); + #elif defined _M_X64 && defined _MSC_VER && _MSC_VER > 1500 + void __cpuidex(int CPUInfo[4], int info_type, int ecxvalue); + #pragma intrinsic(__cpuidex) + #define CPUID(type, eax_, ebx_, ecx_, edx_) do { \ + int regs[4]; \ + __cpuidex(regs, type, 0); \ + eax_ = regs[0], ebx_ = regs[1], ecx_ = regs[2], edx_ = regs[3]; \ + } while(0) + #elif defined _M_X64 && defined _MSC_VER + void __cpuidex(int CPUInfo[4], int info_type); + #pragma intrinsic(__cpuidex) + #define CPUID(type, eax_, ebx_, ecx_, edx_) do { \ + int regs[4]; \ + __cpuidex(regs, type); \ + eax_ = regs[0], ebx_ = regs[1], ecx_ = regs[2], edx_ = regs[3]; \ + } while(0) + #elif defined _M_IX86 && defined _MSC_VER + #define CPUID(type, eax_, ebx_, ecx_, edx_) \ + __asm pushad \ + __asm mov eax, type \ + __asm xor ecx, ecx \ + __asm cpuid \ + __asm mov eax_, eax \ + __asm mov ebx_, ebx \ + __asm mov ecx_, ecx \ + __asm mov edx_, edx \ + __asm popad + #endif +#endif + + + +#if WITH_CR32S && WITH_CR32 + static bool cpu_has_simd32(void) + { + #if defined __x86_64__ || defined _M_X64 + return true; + #elif defined __i386__ || defined _M_IX86 + enum {SSE = 1 << 25, SSE2 = 1 << 26}; + unsigned eax_, ebx_, ecx_, edx_; + CPUID(1, eax_, ebx_, ecx_, edx_); + return (edx_ & (SSE|SSE2)) != 0; + #elif defined AV_CPU_FLAG_NEON + return !!(av_get_cpu_flags() & AV_CPU_FLAG_NEON); + #else + return false; + #endif + } + + static bool should_use_simd32(void) + { + char const * e; + return ((e = getenv("SOXR_USE_SIMD" )))? !!atoi(e) : + ((e = getenv("SOXR_USE_SIMD32")))? !!atoi(e) : cpu_has_simd32(); + } +#else + #define should_use_simd32() true +#endif + + + +#if WITH_CR64S && WITH_CR64 + #if defined __GNUC__ + #define XGETBV(type, eax_, edx_) \ + __asm__ __volatile__ ( \ + ".byte 0x0f, 0x01, 0xd0\n" \ + : "=a"(eax_), "=d"(edx_) : "c" (type)); + #elif defined _M_X64 && defined _MSC_FULL_VER && _MSC_FULL_VER >= 160040219 + #include + #define XGETBV(type, eax_, edx_) do { \ + union {uint64_t x; uint32_t y[2];} a = {_xgetbv(0)}; \ + eax_ = a.y[0], edx_ = a.y[1]; \ + } while(0) + #elif defined _M_IX86 && defined _MSC_VER + #define XGETBV(type, eax_, edx_) \ + __asm pushad \ + __asm mov ecx, type \ + __asm _emit 0x0f \ + __asm _emit 0x01 \ + __asm _emit 0xd0 \ + __asm mov eax_, eax \ + __asm mov edx_, edx \ + __asm popad + #else + #define XGETBV(type, eax_, edx_) eax_ = edx_ = 0 + #endif + + static bool cpu_has_simd64(void) + { + enum {OSXSAVE = 1 << 27, AVX = 1 << 28}; + unsigned eax_, ebx_, ecx_, edx_; + CPUID(1, eax_, ebx_, ecx_, edx_); + if ((ecx_ & (OSXSAVE|AVX)) == (OSXSAVE|AVX)) { + XGETBV(0, eax_, edx_); + return (eax_ & 6) == 6; + } + return false; + } + + static bool should_use_simd64(void) + { + char const * e; + return ((e = getenv("SOXR_USE_SIMD" )))? !!atoi(e) : + ((e = getenv("SOXR_USE_SIMD64")))? !!atoi(e) : cpu_has_simd64(); + } +#else + #define should_use_simd64() true +#endif + + + +extern control_block_t + _soxr_rate32_cb, + _soxr_rate32s_cb, + _soxr_rate64_cb, + _soxr_rate64s_cb, + _soxr_vr32_cb; + + + +static void runtime_num(char const * env_name, + int min, int max, unsigned * field) +{ + char const * e = getenv(env_name); + if (e) { + int i = atoi(e); + if (i >= min && i <= max) + *field = (unsigned)i; + } +} + + + +static void runtime_flag(char const * env_name, + unsigned n_bits, unsigned n_shift, unsigned long * flags) +{ + char const * e = getenv(env_name); + if (e) { + int i = atoi(e); + unsigned long mask = (1UL << n_bits) - 1; + if (i >= 0 && i <= (int)mask) + *flags &= ~(mask << n_shift), *flags |= ((unsigned long)i << n_shift); + } +} + + + +soxr_t soxr_create( + double input_rate, double output_rate, + unsigned num_channels, + soxr_error_t * error0, + soxr_io_spec_t const * io_spec, + soxr_quality_spec_t const * q_spec, + soxr_runtime_spec_t const * runtime_spec) +{ + double io_ratio = output_rate!=0? input_rate!=0? + input_rate / output_rate : -1 : input_rate!=0? -1 : 0; + static const float datatype_full_scale[] = {1, 1, 65536.*32768, 32768}; + soxr_t p = 0; + soxr_error_t error = 0; + +#if WITH_DEV_TRACE +#define _(x) (char)(sizeof(x)>=10? 'a'+(char)(sizeof(x)-10):'0'+(char)sizeof(x)) + char const * e = getenv("SOXR_TRACE"); + _soxr_trace_level = e? atoi(e) : 0; + { + static char const arch[] = {_(char), _(short), _(int), _(long), _(long long) + , ' ', _(float), _(double), _(long double) + , ' ', _(int *), _(int (*)(int)) + , ' ', HAVE_BIGENDIAN ? 'B' : 'L' +#if defined _OPENMP + , ' ', 'O', 'M', 'P' +#endif + , 0}; +#undef _ + lsx_debug("arch: %s", arch); + } +#endif + + if (q_spec && q_spec->e) error = q_spec->e; + else if (io_spec && (io_spec->itype | io_spec->otype) >= SOXR_SPLIT * 2) + error = "invalid io datatype(s)"; + + if (!error && !(p = calloc(sizeof(*p), 1))) error = "malloc failed"; + + if (p) { + control_block_t * control_block; + + p->q_spec = q_spec? *q_spec : soxr_quality_spec(SOXR_HQ, 0); + + if (q_spec) { /* Backwards compatibility with original API: */ + if (p->q_spec.passband_end > 2) + p->q_spec.passband_end /= 100; + if (p->q_spec.stopband_begin > 2) + p->q_spec.stopband_begin = 2 - p->q_spec.stopband_begin / 100; + } + + p->io_ratio = io_ratio; + p->num_channels = num_channels; + if (io_spec) + p->io_spec = *io_spec; + else + p->io_spec.scale = 1; + + p->runtime_spec = runtime_spec? *runtime_spec : soxr_runtime_spec(1); + + runtime_num("SOXR_MIN_DFT_SIZE", 8, 15, &p->runtime_spec.log2_min_dft_size); + runtime_num("SOXR_LARGE_DFT_SIZE", 8, 20, &p->runtime_spec.log2_large_dft_size); + runtime_num("SOXR_COEFS_SIZE", 100, 800, &p->runtime_spec.coef_size_kbytes); + runtime_num("SOXR_NUM_THREADS", 0, 64, &p->runtime_spec.num_threads); + runtime_flag("SOXR_COEF_INTERP", 2, 0, &p->runtime_spec.flags); + + runtime_flag("SOXR_STRICT_BUF", 1, 2, &p->runtime_spec.flags); + runtime_flag("SOXR_NOSMALLINTOPT", 1, 3, &p->runtime_spec.flags); + + p->io_spec.scale *= datatype_full_scale[p->io_spec.otype & 3] / + datatype_full_scale[p->io_spec.itype & 3]; + + p->seed = (unsigned long)time(0) ^ (unsigned long)(size_t)p; + +#if WITH_CR32 || WITH_CR32S || WITH_VR32 + if (0 +#if WITH_VR32 + || ((!WITH_CR32 && !WITH_CR32S) || (p->q_spec.flags & SOXR_VR)) +#endif +#if WITH_CR32 || WITH_CR32S + || !(WITH_CR64 || WITH_CR64S) || (p->q_spec.precision <= 20 && !(p->q_spec.flags & SOXR_DOUBLE_PRECISION)) +#endif + ) { + p->deinterleave = (deinterleave_t)_soxr_deinterleave_f; + p->interleave = (interleave_t)_soxr_interleave_f; + control_block = +#if WITH_VR32 + ((!WITH_CR32 && !WITH_CR32S) || (p->q_spec.flags & SOXR_VR))? &_soxr_vr32_cb : +#endif +#if WITH_CR32S + !WITH_CR32 || should_use_simd32()? &_soxr_rate32s_cb : +#endif + &_soxr_rate32_cb; + } +#if WITH_CR64 || WITH_CR64S + else +#endif +#endif +#if WITH_CR64 || WITH_CR64S + { + p->deinterleave = (deinterleave_t)_soxr_deinterleave; + p->interleave = (interleave_t)_soxr_interleave; + control_block = +#if WITH_CR64S + !WITH_CR64 || should_use_simd64()? &_soxr_rate64s_cb : +#endif + &_soxr_rate64_cb; + } +#endif + memcpy(&p->control_block, control_block, sizeof(p->control_block)); + + if (p->num_channels && io_ratio!=0) + error = soxr_set_io_ratio(p, io_ratio, 0); + } + if (error) + soxr_delete(p), p = 0; + if (error0) + *error0 = error; + return p; +} + + + +soxr_error_t soxr_set_input_fn(soxr_t p, + soxr_input_fn_t input_fn, void * input_fn_state, size_t max_ilen) +{ + p->input_fn_state = input_fn_state; + p->input_fn = input_fn; + p->max_ilen = max_ilen? max_ilen : (size_t)-1; + return 0; +} + + + +static void soxr_delete0(soxr_t p) +{ + unsigned i; + + if (p->resamplers) for (i = 0; i < p->num_channels; ++i) { + if (p->resamplers[i]) + resampler_close(p->resamplers[i]); + free(p->resamplers[i]); + } + free(p->resamplers); + free(p->channel_ptrs); + free(p->shared); + + memset(p, 0, sizeof(*p)); +} + + + +double soxr_delay(soxr_t p) +{ + return + (p && !p->error && p->resamplers)? resampler_delay(p->resamplers[0]) : 0; +} + + + +static soxr_error_t fatal_error(soxr_t p, soxr_error_t error) +{ + soxr_delete0(p); + return p->error = error; +} + + + +static soxr_error_t initialise(soxr_t p) +{ + unsigned i; + size_t shared_size, channel_size; + + resampler_sizes(&shared_size, &channel_size); + p->channel_ptrs = calloc(sizeof(*p->channel_ptrs), p->num_channels); + p->shared = calloc(shared_size, 1); + p->resamplers = calloc(sizeof(*p->resamplers), p->num_channels); + if (!p->shared || !p->channel_ptrs || !p->resamplers) + return fatal_error(p, "malloc failed"); + + for (i = 0; i < p->num_channels; ++i) { + soxr_error_t error; + if (!(p->resamplers[i] = calloc(channel_size, 1))) + return fatal_error(p, "malloc failed"); + error = resampler_create( + p->resamplers[i], + p->shared, + p->io_ratio, + &p->q_spec, + &p->runtime_spec, + p->io_spec.scale); + if (error) + return fatal_error(p, error); + } + return 0; +} + + + +soxr_error_t soxr_set_num_channels(soxr_t p, unsigned num_channels) +{ + if (!p) return "invalid soxr_t pointer"; + if (num_channels == p->num_channels) return p->error; + if (!num_channels) return "invalid # of channels"; + if (p->resamplers) return "# of channels can't be changed"; + p->num_channels = num_channels; + return soxr_set_io_ratio(p, p->io_ratio, 0); +} + + + +soxr_error_t soxr_set_io_ratio(soxr_t p, double io_ratio, size_t slew_len) +{ + unsigned i; + soxr_error_t error; + if (!p) return "invalid soxr_t pointer"; + if ((error = p->error)) return error; + if (!p->num_channels) return "must set # channels before O/I ratio"; + if (io_ratio <= 0) return "I/O ratio out-of-range"; + if (!p->channel_ptrs) { + p->io_ratio = io_ratio; + return initialise(p); + } + if (p->control_block[8]) { + for (i = 0; !error && i < p->num_channels; ++i) + resampler_set_io_ratio(p->resamplers[i], io_ratio, slew_len); + return error; + } + return fabs(p->io_ratio - io_ratio) < 1e-15? 0 : + "varying O/I ratio is not supported with this quality level"; +} + + + +void soxr_delete(soxr_t p) +{ + if (p) + soxr_delete0(p), free(p); +} + + + +soxr_error_t soxr_clear(soxr_t p) /* TODO: this, properly. */ +{ + if (p) { + struct soxr tmp = *p; + soxr_delete0(p); + memset(p, 0, sizeof(*p)); + p->input_fn = tmp.input_fn; + p->runtime_spec = tmp.runtime_spec; + p->q_spec = tmp.q_spec; + p->io_spec = tmp.io_spec; + p->num_channels = tmp.num_channels; + p->input_fn_state = tmp.input_fn_state; + memcpy(p->control_block, tmp.control_block, sizeof(p->control_block)); + p->deinterleave = tmp.deinterleave; + p->interleave = tmp.interleave; + return (p->q_spec.flags & RESET_ON_CLEAR)? + soxr_set_io_ratio(p, tmp.io_ratio, 0) : 0; + } + return "invalid soxr_t pointer"; +} + + + +static void soxr_input_1ch(soxr_t p, unsigned i, soxr_cbuf_t src, size_t len) +{ + sample_t * dest = resampler_input(p->resamplers[i], NULL, len); + (*p->deinterleave)(&dest, p->io_spec.itype, &src, len, 1); +} + + + +static size_t soxr_input(soxr_t p, void const * in, size_t len) +{ + bool separated = !!(p->io_spec.itype & SOXR_SPLIT); + unsigned i; + if (!p || p->error) return 0; + if (!in && len) {p->error = "null input buffer pointer"; return 0;} + if (!len) { + p->flushing = true; + return 0; + } + if (separated) + for (i = 0; i < p->num_channels; ++i) + soxr_input_1ch(p, i, ((soxr_cbufs_t)in)[i], len); + else { + for (i = 0; i < p->num_channels; ++i) + p->channel_ptrs[i] = resampler_input(p->resamplers[i], NULL, len); + (*p->deinterleave)( + (sample_t **)p->channel_ptrs, p->io_spec.itype, &in, len, p->num_channels); + } + return len; +} + + + +static size_t soxr_output_1ch(soxr_t p, unsigned i, soxr_buf_t dest, size_t len, bool separated) +{ + sample_t const * src; + if (p->flushing) + resampler_flush(p->resamplers[i]); + resampler_process(p->resamplers[i], len); + src = resampler_output(p->resamplers[i], NULL, &len); + if (separated) + p->clips += (p->interleave)(p->io_spec.otype, &dest, &src, + len, 1, (p->io_spec.flags & SOXR_NO_DITHER)? 0 : &p->seed); + else p->channel_ptrs[i] = (void /* const */ *)src; + return len; +} + + + +static size_t soxr_output_no_callback(soxr_t p, soxr_buf_t out, size_t len) +{ + unsigned u; + size_t done = 0; + bool separated = !!(p->io_spec.otype & SOXR_SPLIT); +#if defined _OPENMP + int i; + if (!p->runtime_spec.num_threads && p->num_channels > 1) +#pragma omp parallel for + for (i = 0; i < (int)p->num_channels; ++i) { + size_t done1; + done1 = soxr_output_1ch(p, (unsigned)i, ((soxr_bufs_t)out)[i], len, separated); + if (!i) + done = done1; + } else +#endif + for (u = 0; u < p->num_channels; ++u) + done = soxr_output_1ch(p, u, ((soxr_bufs_t)out)[u], len, separated); + + if (!separated) + p->clips += (p->interleave)(p->io_spec.otype, &out, (sample_t const * const *)p->channel_ptrs, + done, p->num_channels, (p->io_spec.flags & SOXR_NO_DITHER)? 0 : &p->seed); + return done; +} + + + +size_t soxr_output(soxr_t p, void * out, size_t len0) +{ + size_t odone, odone0 = 0, olen = len0, osize, idone; + size_t ilen = min(p->max_ilen, (size_t)ceil((double)olen *p->io_ratio)); + void const * in = out; /* Set to !=0, so that caller may leave unset. */ + bool was_flushing; + + if (!p || p->error) return 0; + if (!out && len0) {p->error = "null output buffer pointer"; return 0;} + + do { + odone = soxr_output_no_callback(p, out, olen); + odone0 += odone; + if (odone0 == len0 || !p->input_fn || p->flushing) + break; + + osize = soxr_datatype_size(p->io_spec.otype) * p->num_channels; + out = (char *)out + osize * odone; + olen -= odone; + idone = p->input_fn(p->input_fn_state, &in, ilen); + was_flushing = p->flushing; + if (!in) + p->error = "input function reported failure"; + else soxr_input(p, in, idone); + } while (odone || idone || (!was_flushing && p->flushing)); + return odone0; +} + + + +static size_t soxr_i_for_o(soxr_t p, size_t olen, size_t ilen) +{ + size_t result; +#if 0 + if (p->runtime_spec.flags & SOXR_STRICT_BUFFERING) + result = rate_i_for_o(p->resamplers[0], olen); + else +#endif + result = (size_t)ceil((double)olen * p->io_ratio); + return min(result, ilen); +} + + + +#if 0 +static size_t soxr_o_for_i(soxr_t p, size_t ilen, size_t olen) +{ + size_t result = (size_t)ceil((double)ilen / p->io_ratio); + return min(result, olen); +} +#endif + + + +soxr_error_t soxr_process(soxr_t p, + void const * in , size_t ilen0, size_t * idone0, + void * out, size_t olen , size_t * odone0) +{ + size_t ilen, idone, odone = 0; + unsigned u; + bool flush_requested = false; + + if (!p) return "null pointer"; + + if (!in) + flush_requested = true, ilen = ilen0 = 0; + else { + if ((ptrdiff_t)ilen0 < 0) + flush_requested = true, ilen0 = ~ilen0; + if (idone0 && (1 || flush_requested)) + ilen = soxr_i_for_o(p, olen, ilen0); + else + ilen = ilen0/*, olen = soxr_o_for_i(p, ilen, olen)*/; + } + p->flushing |= ilen == ilen0 && flush_requested; + + if (!out && !in) + idone = ilen; + else if (p->io_spec.itype & p->io_spec.otype & SOXR_SPLIT) { /* Both i & o */ +#if defined _OPENMP + int i; + if (!p->runtime_spec.num_threads && p->num_channels > 1) +#pragma omp parallel for + for (i = 0; i < (int)p->num_channels; ++i) { + size_t done; + if (in) + soxr_input_1ch(p, (unsigned)i, ((soxr_cbufs_t)in)[i], ilen); + done = soxr_output_1ch(p, (unsigned)i, ((soxr_bufs_t)out)[i], olen, true); + if (!i) + odone = done; + } else +#endif + for (u = 0; u < p->num_channels; ++u) { + if (in) + soxr_input_1ch(p, u, ((soxr_cbufs_t)in)[u], ilen); + odone = soxr_output_1ch(p, u, ((soxr_bufs_t)out)[u], olen, true); + } + idone = ilen; + } + else { + idone = ilen? soxr_input (p, in , ilen) : 0; + odone = soxr_output(p, out, olen); + } + if (idone0) *idone0 = idone; + if (odone0) *odone0 = odone; + return p->error; +} + + + +soxr_error_t soxr_oneshot( + double irate, double orate, + unsigned num_channels, + void const * in , size_t ilen, size_t * idone, + void * out, size_t olen, size_t * odone, + soxr_io_spec_t const * io_spec, + soxr_quality_spec_t const * q_spec, + soxr_runtime_spec_t const * runtime_spec) +{ + soxr_t resampler; + soxr_error_t error = q_spec? q_spec->e : 0; + if (!error) { + soxr_quality_spec_t q_spec1; + if (!q_spec) + q_spec1 = soxr_quality_spec(SOXR_LQ, 0), q_spec = &q_spec1; + resampler = soxr_create(irate, orate, num_channels, + &error, io_spec, q_spec, runtime_spec); + } + if (!error) { + error = soxr_process(resampler, in, ~ilen, idone, out, olen, odone); + soxr_delete(resampler); + } + return error; +} + + + +soxr_error_t soxr_set_error(soxr_t p, soxr_error_t error) +{ + if (!p) return "null pointer"; + if (!p->error && p->error != error) return p->error; + p->error = error; + return 0; +} diff --git a/soxr-sys/src/soxr.h b/soxr-sys/src/soxr.h new file mode 100644 index 000000000..09ec7c466 --- /dev/null +++ b/soxr-sys/src/soxr.h @@ -0,0 +1,344 @@ +/* SoX Resampler Library Copyright (c) 2007-18 robs@users.sourceforge.net + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + + +/* -------------------------------- Gubbins --------------------------------- */ + +#if !defined soxr_included +#define soxr_included + + +#if defined __cplusplus + #include + extern "C" { +#else + #include +#endif + +#if defined SOXR_DLL + #if defined soxr_EXPORTS + #define SOXR __declspec(dllexport) + #else + #define SOXR __declspec(dllimport) + #endif +#elif defined SOXR_VISIBILITY && defined __GNUC__ && (__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 1) + #define SOXR __attribute__ ((visibility("default"))) +#else + #define SOXR +#endif + +typedef struct soxr_io_spec soxr_io_spec_t; +typedef struct soxr_quality_spec soxr_quality_spec_t; +typedef struct soxr_runtime_spec soxr_runtime_spec_t; + + + +/* ---------------------------- API conventions -------------------------------- + +Buffer lengths (and occupancies) are expressed as the number of contained +samples per channel. + +Parameter names for buffer lengths have the suffix `len'. + +A single-character `i' or 'o' is often used in names to give context as +input or output (e.g. ilen, olen). */ + + + +/* --------------------------- Version management --------------------------- */ + +/* E.g. #if SOXR_THIS_VERSION >= SOXR_VERSION(0,1,1) ... */ + +#define SOXR_VERSION(x,y,z) (((x)<<16)|((y)<<8)|(z)) +#define SOXR_THIS_VERSION SOXR_VERSION(0,1,3) +#define SOXR_THIS_VERSION_STR "0.1.3" + + + +/* --------------------------- Type declarations ---------------------------- */ + +typedef struct soxr * soxr_t; /* A resampler for 1 or more channels. */ +typedef char const * soxr_error_t; /* 0:no-error; non-0:error. */ + +typedef void * soxr_buf_t; /* 1 buffer of channel-interleaved samples. */ +typedef void const * soxr_cbuf_t; /* Ditto; read-only. */ + +typedef soxr_buf_t const * soxr_bufs_t;/* Or, a separate buffer for each ch. */ +typedef soxr_cbuf_t const * soxr_cbufs_t; /* Ditto; read-only. */ + +typedef void const * soxr_in_t; /* Either a soxr_cbuf_t or soxr_cbufs_t, + depending on itype in soxr_io_spec_t. */ +typedef void * soxr_out_t; /* Either a soxr_buf_t or soxr_bufs_t, + depending on otype in soxr_io_spec_t. */ + + + +/* --------------------------- API main functions --------------------------- */ + +SOXR char const * soxr_version(void); /* Query library version: "libsoxr-x.y.z" */ + +#define soxr_strerror(e) /* Soxr counterpart to strerror. */ \ + ((e)?(e):"no error") + + +/* Create a stream resampler: */ + +SOXR soxr_t soxr_create( + double input_rate, /* Input sample-rate. */ + double output_rate, /* Output sample-rate. */ + unsigned num_channels, /* Number of channels to be used. */ + /* All following arguments are optional (may be set to NULL). */ + soxr_error_t *, /* To report any error during creation. */ + soxr_io_spec_t const *, /* To specify non-default I/O formats. */ + soxr_quality_spec_t const *, /* To specify non-default resampling quality.*/ + soxr_runtime_spec_t const *);/* To specify non-default runtime resources. + + Default io_spec is per soxr_io_spec(SOXR_FLOAT32_I, SOXR_FLOAT32_I) + Default quality_spec is per soxr_quality_spec(SOXR_HQ, 0) + Default runtime_spec is per soxr_runtime_spec(1) */ + + + +/* If not using an app-supplied input function, after creating a stream + * resampler, repeatedly call: */ + +SOXR soxr_error_t soxr_process( + soxr_t resampler, /* As returned by soxr_create. */ + /* Input (to be resampled): */ + soxr_in_t in, /* Input buffer(s); may be NULL (see below). */ + size_t ilen, /* Input buf. length (samples per channel). */ + size_t * idone, /* To return actual # samples used (<= ilen). */ + /* Output (resampled): */ + soxr_out_t out, /* Output buffer(s).*/ + size_t olen, /* Output buf. length (samples per channel). */ + size_t * odone); /* To return actual # samples out (<= olen). + + Note that no special meaning is associated with ilen or olen equal to + zero. End-of-input (i.e. no data is available nor shall be available) + may be indicated by seting `in' to NULL. */ + + + +/* If using an app-supplied input function, it must look and behave like this:*/ + +typedef size_t /* data_len */ + (* soxr_input_fn_t)( /* Supply data to be resampled. */ + void * input_fn_state, /* As given to soxr_set_input_fn (below). */ + soxr_in_t * data, /* Returned data; see below. N.B. ptr to ptr(s)*/ + size_t requested_len); /* Samples per channel, >= returned data_len. + + data_len *data Indicates Meaning + ------- ------- ------------ ------------------------- + !=0 !=0 Success *data contains data to be + input to the resampler. + 0 !=0 (or End-of-input No data is available nor + not set) shall be available. + 0 0 Failure An error occurred whilst trying to + source data to be input to the resampler. */ + +/* and be registered with a previously created stream resampler using: */ + +SOXR soxr_error_t soxr_set_input_fn(/* Set (or reset) an input function.*/ + soxr_t resampler, /* As returned by soxr_create. */ + soxr_input_fn_t, /* Function to supply data to be resampled.*/ + void * input_fn_state, /* If needed by the input function. */ + size_t max_ilen); /* Maximum value for input fn. requested_len.*/ + +/* then repeatedly call: */ + +SOXR size_t /*odone*/ soxr_output(/* Resample and output a block of data.*/ + soxr_t resampler, /* As returned by soxr_create. */ + soxr_out_t data, /* App-supplied buffer(s) for resampled data.*/ + size_t olen); /* Amount of data to output; >= odone. */ + + + +/* Common stream resampler operations: */ + +SOXR soxr_error_t soxr_error(soxr_t); /* Query error status. */ +SOXR size_t * soxr_num_clips(soxr_t); /* Query int. clip counter (for R/W). */ +SOXR double soxr_delay(soxr_t); /* Query current delay in output samples.*/ +SOXR char const * soxr_engine(soxr_t); /* Query resampling engine name. */ + +SOXR soxr_error_t soxr_clear(soxr_t); /* Ready for fresh signal, same config. */ +SOXR void soxr_delete(soxr_t); /* Free resources. */ + + + +/* `Short-cut', single call to resample a (probably short) signal held entirely + * in memory. See soxr_create and soxr_process above for parameter details. + * Note that unlike soxr_create however, the default quality spec. for + * soxr_oneshot is per soxr_quality_spec(SOXR_LQ, 0). */ + +SOXR soxr_error_t soxr_oneshot( + double input_rate, + double output_rate, + unsigned num_channels, + soxr_in_t in , size_t ilen, size_t * idone, + soxr_out_t out, size_t olen, size_t * odone, + soxr_io_spec_t const *, + soxr_quality_spec_t const *, + soxr_runtime_spec_t const *); + + + +/* For variable-rate resampling. See example # 5 for how to create a + * variable-rate resampler and how to use this function. */ + +SOXR soxr_error_t soxr_set_io_ratio(soxr_t, double io_ratio, size_t slew_len); + + + +/* -------------------------- API type definitions -------------------------- */ + +typedef enum { /* Datatypes supported for I/O to/from the resampler: */ + /* Internal; do not use: */ + SOXR_FLOAT32, SOXR_FLOAT64, SOXR_INT32, SOXR_INT16, SOXR_SPLIT = 4, + + /* Use for interleaved channels: */ + SOXR_FLOAT32_I = SOXR_FLOAT32, SOXR_FLOAT64_I, SOXR_INT32_I, SOXR_INT16_I, + + /* Use for split channels: */ + SOXR_FLOAT32_S = SOXR_SPLIT , SOXR_FLOAT64_S, SOXR_INT32_S, SOXR_INT16_S + +} soxr_datatype_t; + +#define soxr_datatype_size(x) /* Returns `sizeof' a soxr_datatype_t sample. */\ + ((unsigned char *)"\4\10\4\2")[(x)&3] + + + +struct soxr_io_spec { /* Typically */ + soxr_datatype_t itype; /* Input datatype. SOXR_FLOAT32_I */ + soxr_datatype_t otype; /* Output datatype. SOXR_FLOAT32_I */ + double scale; /* Linear gain to apply during resampling. 1 */ + void * e; /* Reserved for internal use 0 */ + unsigned long flags; /* Per the following #defines. 0 */ +}; + +#define SOXR_TPDF 0 /* Applicable only if otype is INT16. */ +#define SOXR_NO_DITHER 8u /* Disable the above. */ + + + +struct soxr_quality_spec { /* Typically */ + double precision; /* Conversion precision (in bits). 20 */ + double phase_response; /* 0=minimum, ... 50=linear, ... 100=maximum 50 */ + double passband_end; /* 0dB pt. bandwidth to preserve; nyquist=1 0.913*/ + double stopband_begin; /* Aliasing/imaging control; > passband_end 1 */ + void * e; /* Reserved for internal use. 0 */ + unsigned long flags; /* Per the following #defines. 0 */ +}; + +#define SOXR_ROLLOFF_SMALL 0u /* <= 0.01 dB */ +#define SOXR_ROLLOFF_MEDIUM 1u /* <= 0.35 dB */ +#define SOXR_ROLLOFF_NONE 2u /* For Chebyshev bandwidth. */ + +#define SOXR_HI_PREC_CLOCK 8u /* Increase `irrational' ratio accuracy. */ +#define SOXR_DOUBLE_PRECISION 16u /* Use D.P. calcs even if precision <= 20. */ +#define SOXR_VR 32u /* Variable-rate resampling. */ + + + +struct soxr_runtime_spec { /* Typically */ + unsigned log2_min_dft_size; /* For DFT efficiency. [8,15] 10 */ + unsigned log2_large_dft_size; /* For DFT efficiency. [8,20] 17 */ + unsigned coef_size_kbytes; /* For SOXR_COEF_INTERP_AUTO (below). 400 */ + unsigned num_threads; /* 0: per OMP_NUM_THREADS; 1: 1 thread. 1 */ + void * e; /* Reserved for internal use. 0 */ + unsigned long flags; /* Per the following #defines. 0 */ +}; + /* For `irrational' ratios only: */ +#define SOXR_COEF_INTERP_AUTO 0u /* Auto select coef. interpolation. */ +#define SOXR_COEF_INTERP_LOW 2u /* Man. select: less CPU, more memory. */ +#define SOXR_COEF_INTERP_HIGH 3u /* Man. select: more CPU, less memory. */ + + + +/* -------------------------- API type constructors ------------------------- */ + +/* These functions allow setting of the most commonly-used structure + * parameters, with other parameters being given default values. The default + * values may then be overridden, directly in the structure, if needed. */ + +SOXR soxr_quality_spec_t soxr_quality_spec( + unsigned long recipe, /* Per the #defines immediately below. */ + unsigned long flags); /* As soxr_quality_spec_t.flags. */ + + /* The 5 standard qualities found in SoX: */ +#define SOXR_QQ 0 /* 'Quick' cubic interpolation. */ +#define SOXR_LQ 1 /* 'Low' 16-bit with larger rolloff. */ +#define SOXR_MQ 2 /* 'Medium' 16-bit with medium rolloff. */ +#define SOXR_HQ SOXR_20_BITQ /* 'High quality'. */ +#define SOXR_VHQ SOXR_28_BITQ /* 'Very high quality'. */ + +#define SOXR_16_BITQ 3 +#define SOXR_20_BITQ 4 +#define SOXR_24_BITQ 5 +#define SOXR_28_BITQ 6 +#define SOXR_32_BITQ 7 + /* Reserved for internal use (to be removed): */ +#define SOXR_LSR0Q 8 /* 'Best sinc'. */ +#define SOXR_LSR1Q 9 /* 'Medium sinc'. */ +#define SOXR_LSR2Q 10 /* 'Fast sinc'. */ + +#define SOXR_LINEAR_PHASE 0x00 +#define SOXR_INTERMEDIATE_PHASE 0x10 +#define SOXR_MINIMUM_PHASE 0x30 + +#define SOXR_STEEP_FILTER 0x40 + + + +SOXR soxr_runtime_spec_t soxr_runtime_spec( + unsigned num_threads); + + + +SOXR soxr_io_spec_t soxr_io_spec( + soxr_datatype_t itype, + soxr_datatype_t otype); + + + +/* --------------------------- Advanced use only ---------------------------- */ + +/* For new designs, the following functions/usage will probably not be needed. + * They might be useful when adding soxr into an existing design where values + * for the resampling-rate and/or number-of-channels parameters to soxr_create + * are not available when that function will be called. In such cases, the + * relevant soxr_create parameter(s) can be given as 0, then one or both of the + * following (as appropriate) later invoked (but prior to calling soxr_process + * or soxr_output): + * + * soxr_set_error(soxr, soxr_set_io_ratio(soxr, io_ratio, 0)); + * soxr_set_error(soxr, soxr_set_num_channels(soxr, num_channels)); + */ + +SOXR soxr_error_t soxr_set_error(soxr_t, soxr_error_t); +SOXR soxr_error_t soxr_set_num_channels(soxr_t, unsigned); + + + +#undef SOXR + +#if defined __cplusplus +} +#endif + +#endif diff --git a/soxr-sys/src/soxr.rs b/soxr-sys/src/soxr.rs new file mode 100644 index 000000000..442dd03aa --- /dev/null +++ b/soxr-sys/src/soxr.rs @@ -0,0 +1,403 @@ +/* automatically generated by rust-bindgen 0.69.4 */ + +pub const SOXR_THIS_VERSION_STR: &[u8; 6] = b"0.1.3\0"; +pub const SOXR_TPDF: u32 = 0; +pub const SOXR_NO_DITHER: u32 = 8; +pub const SOXR_ROLLOFF_SMALL: u32 = 0; +pub const SOXR_ROLLOFF_MEDIUM: u32 = 1; +pub const SOXR_ROLLOFF_NONE: u32 = 2; +pub const SOXR_HI_PREC_CLOCK: u32 = 8; +pub const SOXR_DOUBLE_PRECISION: u32 = 16; +pub const SOXR_VR: u32 = 32; +pub const SOXR_COEF_INTERP_AUTO: u32 = 0; +pub const SOXR_COEF_INTERP_LOW: u32 = 2; +pub const SOXR_COEF_INTERP_HIGH: u32 = 3; +pub const SOXR_QQ: u32 = 0; +pub const SOXR_LQ: u32 = 1; +pub const SOXR_MQ: u32 = 2; +pub const SOXR_16_BITQ: u32 = 3; +pub const SOXR_20_BITQ: u32 = 4; +pub const SOXR_24_BITQ: u32 = 5; +pub const SOXR_28_BITQ: u32 = 6; +pub const SOXR_32_BITQ: u32 = 7; +pub const SOXR_LSR0Q: u32 = 8; +pub const SOXR_LSR1Q: u32 = 9; +pub const SOXR_LSR2Q: u32 = 10; +pub const SOXR_LINEAR_PHASE: u32 = 0; +pub const SOXR_INTERMEDIATE_PHASE: u32 = 16; +pub const SOXR_MINIMUM_PHASE: u32 = 48; +pub const SOXR_STEEP_FILTER: u32 = 64; +pub type wchar_t = ::std::os::raw::c_int; +pub type max_align_t = f64; +pub type soxr_io_spec_t = soxr_io_spec; +pub type soxr_quality_spec_t = soxr_quality_spec; +pub type soxr_runtime_spec_t = soxr_runtime_spec; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct soxr { + _unused: [u8; 0], +} +pub type soxr_t = *mut soxr; +pub type soxr_error_t = *const ::std::os::raw::c_char; +pub type soxr_buf_t = *mut ::std::os::raw::c_void; +pub type soxr_cbuf_t = *const ::std::os::raw::c_void; +pub type soxr_bufs_t = *const soxr_buf_t; +pub type soxr_cbufs_t = *const soxr_cbuf_t; +pub type soxr_in_t = *const ::std::os::raw::c_void; +pub type soxr_out_t = *mut ::std::os::raw::c_void; +extern "C" { + pub fn soxr_version() -> *const ::std::os::raw::c_char; +} +extern "C" { + pub fn soxr_create( + input_rate: f64, + output_rate: f64, + num_channels: ::std::os::raw::c_uint, + arg1: *mut soxr_error_t, + arg2: *const soxr_io_spec_t, + arg3: *const soxr_quality_spec_t, + arg4: *const soxr_runtime_spec_t, + ) -> soxr_t; +} +extern "C" { + pub fn soxr_process( + resampler: soxr_t, + in_: soxr_in_t, + ilen: usize, + idone: *mut usize, + out: soxr_out_t, + olen: usize, + odone: *mut usize, + ) -> soxr_error_t; +} +pub type soxr_input_fn_t = ::std::option::Option< + unsafe extern "C" fn( + input_fn_state: *mut ::std::os::raw::c_void, + data: *mut soxr_in_t, + requested_len: usize, + ) -> usize, +>; +extern "C" { + pub fn soxr_set_input_fn( + resampler: soxr_t, + arg1: soxr_input_fn_t, + input_fn_state: *mut ::std::os::raw::c_void, + max_ilen: usize, + ) -> soxr_error_t; +} +extern "C" { + pub fn soxr_output(resampler: soxr_t, data: soxr_out_t, olen: usize) -> usize; +} +extern "C" { + pub fn soxr_error(arg1: soxr_t) -> soxr_error_t; +} +extern "C" { + pub fn soxr_num_clips(arg1: soxr_t) -> *mut usize; +} +extern "C" { + pub fn soxr_delay(arg1: soxr_t) -> f64; +} +extern "C" { + pub fn soxr_engine(arg1: soxr_t) -> *const ::std::os::raw::c_char; +} +extern "C" { + pub fn soxr_clear(arg1: soxr_t) -> soxr_error_t; +} +extern "C" { + pub fn soxr_delete(arg1: soxr_t); +} +extern "C" { + pub fn soxr_oneshot( + input_rate: f64, + output_rate: f64, + num_channels: ::std::os::raw::c_uint, + in_: soxr_in_t, + ilen: usize, + idone: *mut usize, + out: soxr_out_t, + olen: usize, + odone: *mut usize, + arg1: *const soxr_io_spec_t, + arg2: *const soxr_quality_spec_t, + arg3: *const soxr_runtime_spec_t, + ) -> soxr_error_t; +} +extern "C" { + pub fn soxr_set_io_ratio(arg1: soxr_t, io_ratio: f64, slew_len: usize) -> soxr_error_t; +} +pub const SOXR_FLOAT32: soxr_datatype_t = 0; +pub const SOXR_FLOAT64: soxr_datatype_t = 1; +pub const SOXR_INT32: soxr_datatype_t = 2; +pub const SOXR_INT16: soxr_datatype_t = 3; +pub const SOXR_SPLIT: soxr_datatype_t = 4; +pub const SOXR_FLOAT32_I: soxr_datatype_t = 0; +pub const SOXR_FLOAT64_I: soxr_datatype_t = 1; +pub const SOXR_INT32_I: soxr_datatype_t = 2; +pub const SOXR_INT16_I: soxr_datatype_t = 3; +pub const SOXR_FLOAT32_S: soxr_datatype_t = 4; +pub const SOXR_FLOAT64_S: soxr_datatype_t = 5; +pub const SOXR_INT32_S: soxr_datatype_t = 6; +pub const SOXR_INT16_S: soxr_datatype_t = 7; +pub type soxr_datatype_t = ::std::os::raw::c_uint; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct soxr_io_spec { + pub itype: soxr_datatype_t, + pub otype: soxr_datatype_t, + pub scale: f64, + pub e: *mut ::std::os::raw::c_void, + pub flags: ::std::os::raw::c_ulong, +} +#[test] +fn bindgen_test_layout_soxr_io_spec() { + const UNINIT: ::std::mem::MaybeUninit = ::std::mem::MaybeUninit::uninit(); + let ptr = UNINIT.as_ptr(); + assert_eq!( + ::std::mem::size_of::(), + 32usize, + concat!("Size of: ", stringify!(soxr_io_spec)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(soxr_io_spec)) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).itype) as usize - ptr as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(soxr_io_spec), + "::", + stringify!(itype) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).otype) as usize - ptr as usize }, + 4usize, + concat!( + "Offset of field: ", + stringify!(soxr_io_spec), + "::", + stringify!(otype) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).scale) as usize - ptr as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(soxr_io_spec), + "::", + stringify!(scale) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).e) as usize - ptr as usize }, + 16usize, + concat!( + "Offset of field: ", + stringify!(soxr_io_spec), + "::", + stringify!(e) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).flags) as usize - ptr as usize }, + 24usize, + concat!( + "Offset of field: ", + stringify!(soxr_io_spec), + "::", + stringify!(flags) + ) + ); +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct soxr_quality_spec { + pub precision: f64, + pub phase_response: f64, + pub passband_end: f64, + pub stopband_begin: f64, + pub e: *mut ::std::os::raw::c_void, + pub flags: ::std::os::raw::c_ulong, +} +#[test] +fn bindgen_test_layout_soxr_quality_spec() { + const UNINIT: ::std::mem::MaybeUninit = ::std::mem::MaybeUninit::uninit(); + let ptr = UNINIT.as_ptr(); + assert_eq!( + ::std::mem::size_of::(), + 48usize, + concat!("Size of: ", stringify!(soxr_quality_spec)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(soxr_quality_spec)) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).precision) as usize - ptr as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(soxr_quality_spec), + "::", + stringify!(precision) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).phase_response) as usize - ptr as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(soxr_quality_spec), + "::", + stringify!(phase_response) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).passband_end) as usize - ptr as usize }, + 16usize, + concat!( + "Offset of field: ", + stringify!(soxr_quality_spec), + "::", + stringify!(passband_end) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).stopband_begin) as usize - ptr as usize }, + 24usize, + concat!( + "Offset of field: ", + stringify!(soxr_quality_spec), + "::", + stringify!(stopband_begin) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).e) as usize - ptr as usize }, + 32usize, + concat!( + "Offset of field: ", + stringify!(soxr_quality_spec), + "::", + stringify!(e) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).flags) as usize - ptr as usize }, + 40usize, + concat!( + "Offset of field: ", + stringify!(soxr_quality_spec), + "::", + stringify!(flags) + ) + ); +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct soxr_runtime_spec { + pub log2_min_dft_size: ::std::os::raw::c_uint, + pub log2_large_dft_size: ::std::os::raw::c_uint, + pub coef_size_kbytes: ::std::os::raw::c_uint, + pub num_threads: ::std::os::raw::c_uint, + pub e: *mut ::std::os::raw::c_void, + pub flags: ::std::os::raw::c_ulong, +} +#[test] +fn bindgen_test_layout_soxr_runtime_spec() { + const UNINIT: ::std::mem::MaybeUninit = ::std::mem::MaybeUninit::uninit(); + let ptr = UNINIT.as_ptr(); + assert_eq!( + ::std::mem::size_of::(), + 32usize, + concat!("Size of: ", stringify!(soxr_runtime_spec)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(soxr_runtime_spec)) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).log2_min_dft_size) as usize - ptr as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(soxr_runtime_spec), + "::", + stringify!(log2_min_dft_size) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).log2_large_dft_size) as usize - ptr as usize }, + 4usize, + concat!( + "Offset of field: ", + stringify!(soxr_runtime_spec), + "::", + stringify!(log2_large_dft_size) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).coef_size_kbytes) as usize - ptr as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(soxr_runtime_spec), + "::", + stringify!(coef_size_kbytes) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).num_threads) as usize - ptr as usize }, + 12usize, + concat!( + "Offset of field: ", + stringify!(soxr_runtime_spec), + "::", + stringify!(num_threads) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).e) as usize - ptr as usize }, + 16usize, + concat!( + "Offset of field: ", + stringify!(soxr_runtime_spec), + "::", + stringify!(e) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).flags) as usize - ptr as usize }, + 24usize, + concat!( + "Offset of field: ", + stringify!(soxr_runtime_spec), + "::", + stringify!(flags) + ) + ); +} +extern "C" { + pub fn soxr_quality_spec( + recipe: ::std::os::raw::c_ulong, + flags: ::std::os::raw::c_ulong, + ) -> soxr_quality_spec_t; +} +extern "C" { + pub fn soxr_runtime_spec(num_threads: ::std::os::raw::c_uint) -> soxr_runtime_spec_t; +} +extern "C" { + pub fn soxr_io_spec(itype: soxr_datatype_t, otype: soxr_datatype_t) -> soxr_io_spec_t; +} +extern "C" { + pub fn soxr_set_error(arg1: soxr_t, arg2: soxr_error_t) -> soxr_error_t; +} +extern "C" { + pub fn soxr_set_num_channels(arg1: soxr_t, arg2: ::std::os::raw::c_uint) -> soxr_error_t; +} diff --git a/soxr-sys/src/std-types.h b/soxr-sys/src/std-types.h new file mode 100644 index 000000000..c5e8636ac --- /dev/null +++ b/soxr-sys/src/std-types.h @@ -0,0 +1,48 @@ +/* SoX Resampler Library Copyright (c) 2007-16 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +#if !defined soxr_std_types_included +#define soxr_std_types_included + +#include "soxr-config.h" + +#include + +#if HAVE_STDBOOL_H + #include +#else + #undef bool + #undef false + #undef true + #define bool int + #define false 0 + #define true 1 +#endif + +#if HAVE_STDINT_H + #include +#else + #undef int16_t + #undef int32_t + #undef int64_t + #undef uint32_t + #undef uint64_t + #define int16_t short + #if LONG_MAX > 2147483647L + #define int32_t int + #define int64_t long + #elif LONG_MAX < 2147483647L + #error this library requires that 'long int' has at least 32-bits + #else + #define int32_t long + #if defined _MSC_VER + #define int64_t __int64 + #else + #define int64_t long long + #endif + #endif + #define uint32_t unsigned int32_t + #define uint64_t unsigned int64_t +#endif + +#endif diff --git a/soxr-sys/src/util-simd.c b/soxr-sys/src/util-simd.c new file mode 100644 index 000000000..ec548fdee --- /dev/null +++ b/soxr-sys/src/util-simd.c @@ -0,0 +1,89 @@ +/* SoX Resampler Library Copyright (c) 2007-16 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +#include +#include +#include + +#include "soxr-config.h" + +#define SIMD_ALIGNMENT (sizeof(float) * (1 + (PFFFT_DOUBLE|AVCODEC_FOUND)) * 4) + +void * SIMD_ALIGNED_MALLOC(size_t size) +{ + char * p1 = 0, * p = malloc(size + SIMD_ALIGNMENT); + if (p) { + p1 = (char *)((size_t)(p + SIMD_ALIGNMENT) & ~(SIMD_ALIGNMENT - 1)); + *((void * *)p1 - 1) = p; + } + return p1; +} + + + +void * SIMD_ALIGNED_CALLOC(size_t nmemb, size_t size) +{ + void * p = SIMD_ALIGNED_MALLOC(nmemb * size); + if (p) + memset(p, 0, nmemb * size); + return p; +} + + + +void SIMD_ALIGNED_FREE(void * p1) +{ + if (p1) + free(*((void * *)p1 - 1)); +} + + + +#define PFFT_MACROS_ONLY +#include "pffft.c" + + + +void ORDERED_CONVOLVE_SIMD(int n, void * not_used, float * a, float const * b) +{ + int i; + float ab0, ab1; + v4sf * RESTRICT va = (v4sf *)a; + v4sf const * RESTRICT vb = (v4sf const *)b; + assert(VALIGNED(a) && VALIGNED(b)); + ab0 = a[0] * b[0], ab1 = a[1] * b[1]; + for (i = 0; i < n / 4; i += 2) { + v4sf a1r = va[i+0], a1i = va[i+1]; + v4sf b1r = vb[i+0], b1i = vb[i+1]; + UNINTERLEAVE2(a1r, a1i, a1r, a1i); + UNINTERLEAVE2(b1r, b1i, b1r, b1i); + VCPLXMUL(a1r, a1i, b1r, b1i); + INTERLEAVE2(a1r, a1i, a1r, a1i); + va[i+0] = a1r, va[i+1] = a1i; + } + a[0] = ab0, a[1] = ab1; + (void)not_used; +} + + + +void ORDERED_PARTIAL_CONVOLVE_SIMD(int n, float * a, float const * b) +{ + int i; + float ab0; + v4sf * RESTRICT va = (v4sf *)a; + v4sf const * RESTRICT vb = (v4sf const *)b; + assert(VALIGNED(a) && VALIGNED(b)); + ab0 = a[0] * b[0]; + for (i = 0; i < n / 4; i += 2) { + v4sf a1r = va[i+0], a1i = va[i+1]; + v4sf b1r = vb[i+0], b1i = vb[i+1]; + UNINTERLEAVE2(a1r, a1i, a1r, a1i); + UNINTERLEAVE2(b1r, b1i, b1r, b1i); + VCPLXMUL(a1r, a1i, b1r, b1i); + INTERLEAVE2(a1r, a1i, a1r, a1i); + va[i+0] = a1r, va[i+1] = a1i; + } + a[0] = ab0; + a[1] = b[n] * a[n] - b[n+1] * a[n+1]; +} diff --git a/soxr-sys/src/util32s.c b/soxr-sys/src/util32s.c new file mode 100644 index 000000000..b9c9e08bd --- /dev/null +++ b/soxr-sys/src/util32s.c @@ -0,0 +1,8 @@ +/* SoX Resampler Library Copyright (c) 2007-16 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +#define PFFFT_DOUBLE 0 + +#include "util32s.h" + +#include "util-simd.c" diff --git a/soxr-sys/src/util32s.h b/soxr-sys/src/util32s.h new file mode 100644 index 000000000..12226e501 --- /dev/null +++ b/soxr-sys/src/util32s.h @@ -0,0 +1,23 @@ +/* SoX Resampler Library Copyright (c) 2007-16 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +#if !defined soxr_util32s_included +#define soxr_util32s_included + +#include + +void * _soxr_simd32_aligned_malloc(size_t); +void * _soxr_simd32_aligned_calloc(size_t, size_t); +void _soxr_simd32_aligned_free(void *); + +#define SIMD_ALIGNED_MALLOC _soxr_simd32_aligned_malloc +#define SIMD_ALIGNED_CALLOC _soxr_simd32_aligned_calloc +#define SIMD_ALIGNED_FREE _soxr_simd32_aligned_free + +void _soxr_ordered_convolve_simd32(int n, void * not_used, float * a, float const * b); +void _soxr_ordered_partial_convolve_simd32(int n, float * a, float const * b); + +#define ORDERED_CONVOLVE_SIMD _soxr_ordered_convolve_simd32 +#define ORDERED_PARTIAL_CONVOLVE_SIMD _soxr_ordered_partial_convolve_simd32 + +#endif diff --git a/soxr-sys/src/util64s.c b/soxr-sys/src/util64s.c new file mode 100644 index 000000000..0faa9e9ef --- /dev/null +++ b/soxr-sys/src/util64s.c @@ -0,0 +1,8 @@ +/* SoX Resampler Library Copyright (c) 2007-16 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +#define PFFFT_DOUBLE 1 + +#include "util64s.h" + +#include "util-simd.c" diff --git a/soxr-sys/src/util64s.h b/soxr-sys/src/util64s.h new file mode 100644 index 000000000..7beeb8991 --- /dev/null +++ b/soxr-sys/src/util64s.h @@ -0,0 +1,23 @@ +/* SoX Resampler Library Copyright (c) 2007-16 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +#if !defined soxr_util64s_included +#define soxr_util64s_included + +#include + +void * _soxr_simd64_aligned_malloc(size_t); +void * _soxr_simd64_aligned_calloc(size_t, size_t); +void _soxr_simd64_aligned_free(void *); + +#define SIMD_ALIGNED_MALLOC _soxr_simd64_aligned_malloc +#define SIMD_ALIGNED_CALLOC _soxr_simd64_aligned_calloc +#define SIMD_ALIGNED_FREE _soxr_simd64_aligned_free + +void _soxr_ordered_convolve_simd64(int n, void * not_used, double * a, double const * b); +void _soxr_ordered_partial_convolve_simd64(int n, double * a, double const * b); + +#define ORDERED_CONVOLVE_SIMD _soxr_ordered_convolve_simd64 +#define ORDERED_PARTIAL_CONVOLVE_SIMD _soxr_ordered_partial_convolve_simd64 + +#endif diff --git a/soxr-sys/src/vr-coefs.c b/soxr-sys/src/vr-coefs.c new file mode 100644 index 000000000..a57bec8c2 --- /dev/null +++ b/soxr-sys/src/vr-coefs.c @@ -0,0 +1,115 @@ +/* SoX Resampler Library Copyright (c) 2013 robs@users.sourceforge.net + * Licence for this file: LGPL v2.1 See LICENCE for details. */ + +/* Generate the filter coefficients for variable-rate resampling. */ + +#include +#include +#include +#define PI 3.14159265358979323846 /* Since M_PI can't be relied on */ + +static void print(double * h, int m, double l, char const * name) +{ /* Print out a filter: */ + int i, N = l? (int)(l*m)-(l>1) : m, R=(N+1)/2; + int a = !l||l>1? 0:N-R, b = l>1? R:N; + printf("static float const %s[] = {\n", name); + if (l>1) printf(" 0.f,"); else if (!l) l=1; + for (i=a; h && i 0 && x E[i] >= x E[i z 1]) +#define PEAK do {if (k0)-(E[i]<0);} while (0) + +typedef struct {double x, beta, gamma;} coef_t; + +static double amp_response(coef_t * coef, int R, double f, int i) +{ + double n = 0, d = 0, x = cos(PI*f), t; + for (; i < R; d += t = coef[i].beta / t, n += coef[i].gamma * t, ++i) + if (fabs(t = x - coef[i].x) < 1e-9) return coef[i].gamma; + return n/d; +} + +static void fir(int m, double l, double Fp0, double Fs0, + double weight0, int density, char const * name) +{ + double Fp=Fp0/l, Fs=Fs0/l, weight=1/weight0, inc[2], Ws=1-Fs; + int N = (int)(l*m)-(l>1), R=(N+1)/2, NP=R+1, grid_size=1+density*R+1, pass=0; + int n1 = Ws>=(2*R-1)*Fp? 1:(int)(R*Fp/(Fp+Ws)+.5), n2=NP-n1, _1, i, j, k; + int * peak = calloc(sizeof(*peak), (size_t)(NP+1)), * P=peak, end[2]; + coef_t * coef = calloc(sizeof(*coef), (size_t)(NP)); + float * E = calloc(sizeof(*E ), (size_t)(grid_size)); + double d, n, e, f, mult, delta, sum, hi, lo, * A = (double*)E, *h=0; + + if (!P || !coef || !E) goto END; + end[0] = n1 * density, end[1] = grid_size-1; /* Create prototype peaks: */ + inc[0] = Fp/end[0], inc[1] = n2==1? 0 : Ws / ((n2-1)*density); + for (i=0; iE[i+1]) || (EE(-,-) && E[i] 1) goto END; /* Too many/few? */ + P = peak + k * (fabs(E[peak[0]]) < fabs(E[peak[NP]])); /* rm 1st? */ + + for (lo = hi = fabs(E[P[0]]), i=1; ihi? e:hi; + } while ((hi-lo)/hi > .001 && ++pass < 20); + /* Create impulse response from final amp. resp. coefs: */ + if (!(h = malloc(sizeof(*h)*(size_t)N))) goto END; + for (i = 0; i < R; f = 2.*i/N, A[i++] = amp_response(coef,R,f,0)*even_adj(f)); + for (i = 0; i < R; h[N-1-i] = h[i] = sum/N, ++i) + for (sum=*A, j=1; j +#include "math-wrap.h" +#include +#include +#include "internal.h" +#define FIFO_SIZE_T int +#define FIFO_MIN 0x8000 +#include "fifo.h" +#include "vr-coefs.h" + +#define FADE_LEN_BITS 9 +#define PHASE_BITS_D 10 +#define PHASE_BITS_U 9 + +#define PHASES0_D 12 +#define POLY_FIR_LEN_D 20 +#define PHASES0_U 6 +#define POLY_FIR_LEN_U 12 + +#define MULT32 (65536. * 65536.) +#define PHASES_D (1 << PHASE_BITS_D) +#define PHASES_U (1 << PHASE_BITS_U) + +#define CONVOLVE \ + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \ + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \ + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + +#define HALF_FIR_LEN_2 (iAL(half_fir_coefs) - 1) +#define HALF_FIR_LEN_4 (HALF_FIR_LEN_2 / 2) + +#define _ sum += (input[-i] + input[i]) * half_fir_coefs[i], ++i; +static float half_fir(float const * input) +{ + long i = 1; + float sum = input[0] * half_fir_coefs[0]; + CONVOLVE CONVOLVE + assert(i == HALF_FIR_LEN_2 + 1); + return (float)sum; +} +#undef _ + +#define _ sum += (input[-i] + input[i]) * half_fir_coefs[2*i], ++i; +static float double_fir0(float const * input) +{ + int i = 1; + float sum = input[0] * half_fir_coefs[0]; + CONVOLVE + assert(i == HALF_FIR_LEN_4 + 1); + return (float)(sum * 2); +} +#undef _ + +#define _ sum += (input[-i] + input[1+i]) * half_fir_coefs[2*i+1], ++i; +static float double_fir1(float const * input) +{ + int i = 0; + float sum = 0; + CONVOLVE + assert(i == HALF_FIR_LEN_4 + 0); + return (float)(sum * 2); +} +#undef _ + +static float fast_half_fir(float const * input) +{ + int i = 0; + float sum = input[0] * .5f; +#define _ sum += (input[-(2*i+1)] + input[2*i+1]) * fast_half_fir_coefs[i], ++i; + _ _ _ _ _ _ +#undef _ + return (float)sum; +} + +#define IIR_FILTER _ _ _ _ _ _ _ +#define _ in1=(in1-p->y[i])*iir_coefs[i]+tmp1;tmp1=p->y[i],p->y[i]=in1;++i;\ + in0=(in0-p->y[i])*iir_coefs[i]+tmp0;tmp0=p->y[i],p->y[i]=in0;++i; + +typedef struct {float x[2], y[AL(iir_coefs)];} half_iir_t; + +static float half_iir1(half_iir_t * p, float in0, float in1) +{ + int i = 0; + float tmp0, tmp1; + tmp0 = p->x[0], p->x[0] = in0; + tmp1 = p->x[1], p->x[1] = in1; + IIR_FILTER + p->y[i] = in1 = (in1 - p->y[i]) * iir_coefs[i] + tmp1; + return in1 + in0; +} +#undef _ + +static void half_iir(half_iir_t * p, float * obuf, float const * ibuf, int olen) +{ + int i; + for (i=0; i < olen; obuf[i] = (float)half_iir1(p, ibuf[i*2], ibuf[i*2+1]),++i); +} + +static void half_phase(half_iir_t * p, float * buf, int len) +{ + float const small_normal = 1/MULT32/MULT32; /* To quash denormals on path 0.*/ + int i; + for (i = 0; i < len; buf[i] = (float)half_iir1(p, buf[i], 0), ++i); +#define _ p->y[i] += small_normal, i += 2; + i = 0, _ IIR_FILTER +#undef _ +#define _ p->y[i] -= small_normal, i += 2; + i = 0, _ IIR_FILTER +#undef _ +} + +#define coef(coef_p, interp_order, fir_len, phase_num, coef_interp_num, \ + fir_coef_num) coef_p[(fir_len) * ((interp_order) + 1) * (phase_num) + \ + ((interp_order) + 1) * (fir_coef_num) + (interp_order - coef_interp_num)] + +#define COEF(h,l,i) ((i)<0||(i)>=(l)?0:(h)[(i)>(l)/2?(l)-(i):(i)]) +static void prepare_coefs(float * coefs, int n, int phases0, int phases, + float const * coefs0, double multiplier) +{ + double k[6]; + int length0 = n * phases0, length = n * phases, K0 = iAL(k)/2 - 1, i, j, pos; + float * coefs1 = malloc(((size_t)length / 2 + 1) * sizeof(*coefs1)); + float * p = coefs1, f0, f1 = 0; + + for (j = 0; j < iAL(k); k[j] = COEF(coefs0, length0, j - K0), ++j); + for (pos = i = 0; i < length0 / 2; ++i) { + double b=(1/24.)*(k[0]+k[4]+6*k[2]-4*(k[1]+k[3])),d=.5*(k[1]+k[3])-k[2]-b; + double a=(1/120.)*(k[5]-k[2]-9*(9*b+d)+2.5*(k[3]-k[1])-2*(k[4]-k[0])); + double c=(1/12.)*(k[4]-k[0]-2*(k[3]-k[1])-60*a),e=.5*(k[3]-k[1])-a-c; + for (; pos / phases == i; pos += phases0) { + double x = (double)(pos % phases) / phases; + *p++ = (float)(k[K0] + ((((a*x + b)*x + c)*x + d)*x + e)*x); + } + for (j = 0; j < iAL(k) - 1; k[j] = k[j + 1], ++j); + k[j] = COEF(coefs0, length0, i + iAL(k) / 2 + 1); + } + if (!(length & 1)) + *p++ = (float)k[K0]; + assert(p - coefs1 == length / 2 + 1); + + for (i = 0; i < n; ++i) for (j = phases - 1; j >= 0; --j, f1 = f0) { + pos = (n - 1 - i) * phases + j; + f0 = COEF(coefs1, length, pos) * (float)multiplier; + coef(coefs, 1, n, j, 0, i) = (float)f0; + coef(coefs, 1, n, j, 1, i) = (float)(f1 - f0); + } + free(coefs1); +} + +#define _ sum += (b *x + a)*input[i], ++i; +#define a (coef(poly_fir_coefs_d, 1, POLY_FIR_LEN_D, phase, 0,i)) +#define b (coef(poly_fir_coefs_d, 1, POLY_FIR_LEN_D, phase, 1,i)) +static float poly_fir_coefs_d[POLY_FIR_LEN_D * PHASES_D * 2]; + +static float poly_fir1_d(float const * input, uint32_t frac) +{ + int i = 0, phase = (int)(frac >> (32 - PHASE_BITS_D)); + float sum = 0, x = (float)(frac << PHASE_BITS_D) * (float)(1 / MULT32); + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + assert(i == POLY_FIR_LEN_D); + return (float)sum; +} +#undef a +#undef b +#define a (coef(poly_fir_coefs_u, 1, POLY_FIR_LEN_U, phase, 0,i)) +#define b (coef(poly_fir_coefs_u, 1, POLY_FIR_LEN_U, phase, 1,i)) +static float poly_fir_coefs_u[POLY_FIR_LEN_U * PHASES_U * 2]; + +static float poly_fir1_u(float const * input, uint32_t frac) +{ + int i = 0, phase = (int)(frac >> (32 - PHASE_BITS_U)); + float sum = 0, x = (float)(frac << PHASE_BITS_U) * (float)(1 / MULT32); + _ _ _ _ _ _ _ _ _ _ _ _ + assert(i == POLY_FIR_LEN_U); + return (float)sum; +} +#undef a +#undef b +#undef _ + +#define ADD_TO(x,y) x.all += y.all +#define SUBTRACT_FROM(x,y) x.all -= y.all +#define FRAC(x) x.part.frac +#define INT(x) x.part.integer + +typedef struct { + union { + int64_t all; +#if HAVE_BIGENDIAN + struct {int32_t integer; uint32_t frac;} part; +#else + struct {uint32_t frac; int32_t integer;} part; +#endif + } at, step, step_step; + float const * input; + int len, stage_num; + bool is_d; /* true: downsampling at x2 rate; false: upsampling at 1x rate. */ + double step_mult; +} stream_t; + +static int poly_fir_d(stream_t * s, float * output, int olen) +{ + int i; + float const * input = s->input - POLY_FIR_LEN_D / 2 + 1; + for (i = 0; i < olen && INT(s->at) < s->len; ++i) { + output[i] = poly_fir1_d(input + INT(s->at), FRAC(s->at)); + ADD_TO(s->at, s->step); + if (!(INT(s->at) < s->len)) { + SUBTRACT_FROM(s->at, s->step); + break; + } + output[++i] = poly_fir1_d(input + INT(s->at), FRAC(s->at)); + ADD_TO(s->at, s->step); + ADD_TO(s->step, s->step_step); + } + return i; +} + +static int poly_fir_fade_d( + stream_t * s, float const * vol, int step, float * output, int olen) +{ + int i; + float const * input = s->input - POLY_FIR_LEN_D / 2 + 1; + for (i = 0; i < olen && INT(s->at) < s->len; ++i, vol += step) { + output[i] += *vol * poly_fir1_d(input + INT(s->at), FRAC(s->at)); + ADD_TO(s->at, s->step); + if (!(INT(s->at) < s->len)) { + SUBTRACT_FROM(s->at, s->step); + break; + } + output[++i] += *(vol += step) * poly_fir1_d(input + INT(s->at),FRAC(s->at)); + ADD_TO(s->at, s->step); + ADD_TO(s->step, s->step_step); + } + return i; +} + +static int poly_fir_u(stream_t * s, float * output, int olen) +{ + int i; + float const * input = s->input - POLY_FIR_LEN_U / 2 + 1; + for (i = 0; i < olen && INT(s->at) < s->len; ++i) { + output[i] = poly_fir1_u(input + INT(s->at), FRAC(s->at)); + ADD_TO(s->at, s->step); + ADD_TO(s->step, s->step_step); + } + return i; +} + +static int poly_fir_fade_u( + stream_t * s, float const * vol, int step, float * output, int olen) +{ + int i; + float const * input = s->input - POLY_FIR_LEN_U / 2 + 1; + for (i = 0; i < olen && INT(s->at) < s->len; i += 2, vol += step) { + output[i] += *vol * poly_fir1_u(input + INT(s->at), FRAC(s->at)); + ADD_TO(s->at, s->step); + ADD_TO(s->step, s->step_step); + } + return i; +} + +#define shiftr(x,by) ((by) < 0? (x) << (-(by)) : (x) >> (by)) +#define shiftl(x,by) shiftr(x,-(by)) +#define stage_occupancy(s) (fifo_occupancy(&(s)->fifo) - 4*HALF_FIR_LEN_2) +#define stage_read_p(s) ((float *)fifo_read_ptr(&(s)->fifo) + 2*HALF_FIR_LEN_2) +#define stage_preload(s) memset(fifo_reserve(&(s)->fifo, (s)->preload), \ + 0, sizeof(float) * (size_t)(s)->preload); + +typedef struct { + fifo_t fifo; + double step_mult; + int is_fast, x_fade_len, preload; +} stage_t; + +typedef struct { + int num_stages0, num_stages, flushing; + int fade_len, slew_len, xfade, stage_inc, switch_stage_num; + double new_io_ratio, default_io_ratio; + stage_t * stages; + fifo_t output_fifo; + half_iir_t halfer; + stream_t current, fadeout; /* Current/fade-in, fadeout streams. */ +} rate_t; + +static float fade_coefs[(2 << FADE_LEN_BITS) + 1]; + +static void vr_init(rate_t * p, double default_io_ratio, int num_stages, double mult) +{ + int i; + assert(num_stages >= 0); + memset(p, 0, sizeof(*p)); + + p->num_stages0 = num_stages; + p->num_stages = num_stages = max(num_stages, 1); + p->stages = (stage_t *)calloc((unsigned)num_stages + 1, sizeof(*p->stages)) + 1; + for (i = -1; i < p->num_stages; ++i) { + stage_t * s = &p->stages[i]; + fifo_create(&s->fifo, sizeof(float)); + s->step_mult = 2 * MULT32 / shiftl(2, i); + s->preload = i < 0? 0 : i == 0? 2 * HALF_FIR_LEN_2 : 3 * HALF_FIR_LEN_2 / 2; + stage_preload(s); + s->is_fast = true; + lsx_debug("%-3i preload=%i", i, s->preload); + } + fifo_create(&p->output_fifo, sizeof(float)); + p->default_io_ratio = default_io_ratio; + if (fade_coefs[0]==0) { + for (i = 0; i < iAL(fade_coefs); ++i) + fade_coefs[i] = (float)(.5 * (1 + cos(M_PI * i / (AL(fade_coefs) - 1)))); + prepare_coefs(poly_fir_coefs_u, POLY_FIR_LEN_U, PHASES0_U, PHASES_U, coefs0_u, mult); + prepare_coefs(poly_fir_coefs_d, POLY_FIR_LEN_D, PHASES0_D, PHASES_D, coefs0_d, mult *.5); + } + assert(fade_coefs[0]); +} + +static void enter_new_stage(rate_t * p, int occupancy0) +{ + p->current.len = shiftr(occupancy0, p->current.stage_num); + p->current.input = stage_read_p(&p->stages[p->current.stage_num]); + + p->current.step_mult = p->stages[p->current.stage_num].step_mult; + p->current.is_d = p->current.stage_num >= 0; + if (p->current.is_d) + p->current.step_mult *= .5; +} + +static void set_step(stream_t * p, double io_ratio) +{ + p->step.all = (int64_t)(io_ratio * p->step_mult + .5); +} + +static bool set_step_step(stream_t * p, double io_ratio, int slew_len) +{ + int64_t dif; + int difi; + stream_t tmp = *p; + set_step(&tmp, io_ratio); + dif = tmp.step.all - p->step.all; + dif = dif < 0? dif - (slew_len >> 1) : dif + (slew_len >> 1); + difi = (int)dif; /* Try to avoid int64_t div. */ + p->step_step.all = difi == dif? difi / slew_len : dif / slew_len; + return p->step_step.all != 0; +} + +static void vr_set_io_ratio(rate_t * p, double io_ratio, size_t slew_len) +{ + assert(io_ratio > 0); + if (slew_len) { + if (!set_step_step(&p->current, io_ratio, p->slew_len = (int)slew_len)) + p->slew_len = 0, p->new_io_ratio = 0, p->fadeout.step_step.all = 0; + else { + p->new_io_ratio = io_ratio; + if (p->fade_len) + set_step_step(&p->fadeout, io_ratio, p->slew_len); + } + } + else { + if (p->default_io_ratio!=0) { /* Then this is the first call to this fn. */ + int octave = (int)floor(log(io_ratio) / M_LN2); + p->current.stage_num = octave < 0? -1 : min(octave, p->num_stages0-1); + enter_new_stage(p, 0); + } + else if (p->fade_len) + set_step(&p->fadeout, io_ratio); + set_step(&p->current, io_ratio); + if (p->default_io_ratio!=0) FRAC(p->current.at) = FRAC(p->current.step) >> 1; + p->default_io_ratio = 0; + } +} + +static bool do_input_stage(rate_t * p, int stage_num, int sign, int min_stage_num) +{ + int i = 0; + float * dest; + stage_t * s = &p->stages[stage_num]; + stage_t * s1 = &p->stages[stage_num - sign]; + float const * src = (float *)fifo_read_ptr(&s1->fifo) + HALF_FIR_LEN_2; + int len = shiftr(fifo_occupancy(&s1->fifo) - HALF_FIR_LEN_2 * 2, sign); + int already_done = fifo_occupancy(&s->fifo) - s->preload; + if ((len -= already_done) <= 0) + return false; + src += shiftl(already_done, sign); + + dest = fifo_reserve(&s->fifo, len); + if (stage_num < 0) for (; i < len; ++src) + dest[i++] = double_fir0(src), dest[i++] = double_fir1(src); + else { + bool should_be_fast = p->stage_inc; + if (!s->x_fade_len && stage_num == p->switch_stage_num) { + p->switch_stage_num = 0; + if (s->is_fast != should_be_fast) { + s->x_fade_len = 1 << FADE_LEN_BITS, s->is_fast = should_be_fast, ++p->xfade; + lsx_debug("xfade level %i, inc?=%i", stage_num, p->stage_inc); + } + } + if (s->x_fade_len) { + float const * vol1 = fade_coefs + (s->x_fade_len << 1); + float const * vol2 = fade_coefs + (((1 << FADE_LEN_BITS) - s->x_fade_len) << 1); + int n = min(len, s->x_fade_len); + /*lsx_debug("xfade level %i, inc?=%i len=%i n=%i", stage_num, p->stage_inc, s->x_fade_len, n);*/ + if (should_be_fast) + for (; i < n; vol2 += 2, vol1 -= 2, src += 2) + dest[i++] = *vol1 * fast_half_fir(src) + *vol2 * half_fir(src); + else for (; i < n; vol2 += 2, vol1 -= 2, src += 2) + dest[i++] = *vol2 * fast_half_fir(src) + *vol1 * half_fir(src); + s->x_fade_len -= n; + p->xfade -= !s->x_fade_len; + } + if (stage_num < min_stage_num) + for (; i < len; dest[i++] = fast_half_fir(src), src += 2); + else for (; i < len; dest[i++] = half_fir(src), src += 2); + } + if (p->flushing > 0) + stage_preload(s); + return true; +} + +static int vr_process(rate_t * p, int olen0) +{ + assert(p->num_stages > 0); + if (p->default_io_ratio!=0) + vr_set_io_ratio(p, p->default_io_ratio, 0); + { + float * output = fifo_reserve(&p->output_fifo, olen0); + int j, odone0 = 0, min_stage_num = p->current.stage_num; + int occupancy0, max_stage_num = min_stage_num; + if (p->fade_len) { + min_stage_num = min(min_stage_num, p->fadeout.stage_num); + max_stage_num = max(max_stage_num, p->fadeout.stage_num); + } + + for (j = min(min_stage_num, 0); j <= max_stage_num; ++j) + if (j && !do_input_stage(p, j, j < 0? -1 : 1, min_stage_num)) + break; + if (p->flushing > 0) + p->flushing = -1; + + occupancy0 = shiftl(max(0,stage_occupancy(&p->stages[max_stage_num])), max_stage_num); + p->current.len = shiftr(occupancy0, p->current.stage_num); + p->current.input = stage_read_p(&p->stages[p->current.stage_num]); + if (p->fade_len) { + p->fadeout.len = shiftr(occupancy0, p->fadeout.stage_num); + p->fadeout.input = stage_read_p(&p->stages[p->fadeout.stage_num]); + } + + while (odone0 < olen0) { + int odone, odone2, olen = olen0 - odone0, stage_dif = 0, shift; + float buf[64 << 1]; + + olen = min(olen, (int)(AL(buf) >> 1)); + if (p->slew_len) + olen = min(olen, p->slew_len); + else if (p->new_io_ratio!=0) { + set_step(&p->current, p->new_io_ratio); + set_step(&p->fadeout, p->new_io_ratio); + p->fadeout.step_step.all = p->current.step_step.all = 0; + p->new_io_ratio = 0; + } + if (!p->flushing && !p->fade_len && !p->xfade) { + if (p->current.is_d) { + if (INT(p->current.step) && FRAC(p->current.step)) + stage_dif = 1, ++max_stage_num; + else if (!INT(p->current.step) && FRAC(p->current.step) < (1u << 31)) + stage_dif = -1, --min_stage_num; + } else if (INT(p->current.step) > 1 && FRAC(p->current.step)) + stage_dif = 1, ++max_stage_num; + } + if (stage_dif) { + int n = p->current.stage_num + stage_dif; + if (n >= p->num_stages) + --max_stage_num; + else { + p->stage_inc = stage_dif > 0; + p->fadeout = p->current; + p->current.stage_num += stage_dif; + if (!p->stage_inc) + p->switch_stage_num = p->current.stage_num; + if ((p->current.stage_num < 0 && stage_dif < 0) || + (p->current.stage_num > 0 && stage_dif > 0)) { + stage_t * s = &p->stages[p->current.stage_num]; + fifo_clear(&s->fifo); + stage_preload(s); + s->is_fast = false; + do_input_stage(p, p->current.stage_num, stage_dif, p->current.stage_num); + } + if (p->current.stage_num > 0 && stage_dif < 0) { + int idone = INT(p->current.at); + stage_t * s = &p->stages[p->current.stage_num]; + fifo_trim_to(&s->fifo, 2 * HALF_FIR_LEN_2 + idone + (POLY_FIR_LEN_D >> 1)); + do_input_stage(p, p->current.stage_num, 1, p->current.stage_num); + } + enter_new_stage(p, occupancy0); + shift = -stage_dif; +#define lshift(x,by) (x)=(by)>0?(x)<<(by):(x)>>-(by) + lshift(p->current.at.all, shift); + shift += p->fadeout.is_d - p->current.is_d; + lshift(p->current.step.all, shift); + lshift(p->current.step_step.all, shift); + p->fade_len = AL(fade_coefs) - 1; + lsx_debug("switch from stage %i to %i, x2 from %i to %i", p->fadeout.stage_num, p->current.stage_num, p->fadeout.is_d, p->current.is_d); + } + } + + if (p->fade_len) { + float const * vol1 = fade_coefs + p->fade_len; + float const * vol2 = fade_coefs + (iAL(fade_coefs) - 1 - p->fade_len); + int olen2 = (olen = min(olen, p->fade_len >> 1)) << 1; + + /* x2 is more fine-grained so may fail to produce a pair of samples + * where x1 would not (the x1 second sample is a zero so is always + * available). So do x2 first, then feed odone to the second one. */ + memset(buf, 0, sizeof(*buf) * (size_t)olen2); + if (p->current.is_d && p->fadeout.is_d) { + odone = poly_fir_fade_d(&p->current, vol1,-1, buf, olen2); + odone2 = poly_fir_fade_d(&p->fadeout, vol2, 1, buf, odone); + } else if (p->current.is_d) { + odone = poly_fir_fade_d(&p->current, vol1,-1, buf, olen2); + odone2 = poly_fir_fade_u(&p->fadeout, vol2, 2, buf, odone); + } else { + assert(p->fadeout.is_d); + odone = poly_fir_fade_d(&p->fadeout, vol2, 1, buf, olen2); + odone2 = poly_fir_fade_u(&p->current, vol1,-2, buf, odone); + } + assert(odone == odone2); + (void)odone2; + p->fade_len -= odone; + if (!p->fade_len) { + if (p->stage_inc) + p->switch_stage_num = min_stage_num++; + else + --max_stage_num; + } + half_iir(&p->halfer, &output[odone0], buf, odone >>= 1); + } + else if (p->current.is_d) { + odone = poly_fir_d(&p->current, buf, olen << 1) >> 1; + half_iir(&p->halfer, &output[odone0], buf, odone); + } + else { + odone = poly_fir_u(&p->current, &output[odone0], olen); + if (p->num_stages0) + half_phase(&p->halfer, &output[odone0], odone); + } + odone0 += odone; + if (p->slew_len) + p->slew_len -= odone; + if (odone != olen) + break; /* Need more input. */ + } { + int from = max(0, max_stage_num), to = min(0, min_stage_num); + int i, idone = shiftr(INT(p->current.at), from - p->current.stage_num); + INT(p->current.at) -= shiftl(idone, from - p->current.stage_num); + if (p->fade_len) + INT(p->fadeout.at) -= shiftl(idone, from - p->fadeout.stage_num); + for (i = from; i >= to; --i, idone <<= 1) + fifo_read(&p->stages[i].fifo, idone, NULL); + } + fifo_trim_by(&p->output_fifo, olen0 - odone0); + return odone0; + } +} + +static float * vr_input(rate_t * p, float const * input, size_t n) +{ + return fifo_write(&p->stages[0].fifo, (int)n, input); +} + +static float const * vr_output(rate_t * p, float * output, size_t * n) +{ + fifo_t * fifo = &p->output_fifo; + if (1 || !p->num_stages0) + return fifo_read(fifo, (int)(*n = min(*n, (size_t)fifo_occupancy(fifo))), output); + else { /* Ignore this complication for now. */ + int const IIR_DELAY = 2; + float * ptr = fifo_read_ptr(fifo); + int olen = min((int)*n, max(0, fifo_occupancy(fifo) - IIR_DELAY)); + *n = (size_t)olen; + if (output) + memcpy(output, ptr + IIR_DELAY, *n * sizeof(*output)); + fifo_read(fifo, olen, NULL); + return ptr + IIR_DELAY; + } +} + +static void vr_flush(rate_t * p) +{ + if (!p->flushing) { + stage_preload(&p->stages[0]); + ++p->flushing; + } +} + +static void vr_close(rate_t * p) +{ + int i; + + fifo_delete(&p->output_fifo); + for (i = -1; i < p->num_stages; ++i) { + stage_t * s = &p->stages[i]; + fifo_delete(&s->fifo); + } + free(p->stages - 1); +} + +static double vr_delay(rate_t * p) +{ + return 100; /* TODO */ + (void)p; +} + +static void vr_sizes(size_t * shared, size_t * channel) +{ + *shared = 0; + *channel = sizeof(rate_t); +} + +static char const * vr_create(void * channel, void * shared,double max_io_ratio, + void * q_spec, void * r_spec, double scale) +{ + double x = max_io_ratio; + int n; + for (n = 0; x > 1; x *= .5, ++n); + vr_init(channel, max_io_ratio, n, scale); + return 0; + (void)shared, (void)q_spec, (void)r_spec; +} + +static char const * vr_id(void) +{ + return "vr32"; +} + +typedef void (* fn_t)(void); +fn_t _soxr_vr32_cb[] = { + (fn_t)vr_input, + (fn_t)vr_process, + (fn_t)vr_output, + (fn_t)vr_flush, + (fn_t)vr_close, + (fn_t)vr_delay, + (fn_t)vr_sizes, + (fn_t)vr_create, + (fn_t)vr_set_io_ratio, + (fn_t)vr_id, +}; From 8fa30111109f03f17a3e18fbb68c476a91199dc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Mon, 23 Sep 2024 15:55:08 -0700 Subject: [PATCH 040/274] fix: resume/reconnection not working! (#440) --- livekit-protocol/src/livekit.rs | 320 +++++++++++++------------- livekit/src/rtc_engine/mod.rs | 4 +- livekit/src/rtc_engine/rtc_session.rs | 2 +- 3 files changed, 163 insertions(+), 163 deletions(-) diff --git a/livekit-protocol/src/livekit.rs b/livekit-protocol/src/livekit.rs index e7cc3d0cf..b2a1de945 100644 --- a/livekit-protocol/src/livekit.rs +++ b/livekit-protocol/src/livekit.rs @@ -131,10 +131,10 @@ pub mod participant_info { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - State::Joining => "JOINING", - State::Joined => "JOINED", - State::Active => "ACTIVE", - State::Disconnected => "DISCONNECTED", + Self::Joining => "JOINING", + Self::Joined => "JOINED", + Self::Active => "ACTIVE", + Self::Disconnected => "DISCONNECTED", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -169,11 +169,11 @@ pub mod participant_info { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Kind::Standard => "STANDARD", - Kind::Ingress => "INGRESS", - Kind::Egress => "EGRESS", - Kind::Sip => "SIP", - Kind::Agent => "AGENT", + Self::Standard => "STANDARD", + Self::Ingress => "INGRESS", + Self::Egress => "EGRESS", + Self::Sip => "SIP", + Self::Agent => "AGENT", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -208,9 +208,9 @@ pub mod encryption { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Type::None => "NONE", - Type::Gcm => "GCM", - Type::Custom => "CUSTOM", + Self::None => "NONE", + Self::Gcm => "GCM", + Self::Custom => "CUSTOM", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -330,8 +330,8 @@ pub mod data_packet { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Kind::Reliable => "RELIABLE", - Kind::Lossy => "LOSSY", + Self::Reliable => "RELIABLE", + Self::Lossy => "LOSSY", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -477,8 +477,8 @@ pub mod server_info { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Edition::Standard => "Standard", - Edition::Cloud => "Cloud", + Self::Standard => "Standard", + Self::Cloud => "Cloud", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -540,17 +540,17 @@ pub mod client_info { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Sdk::Unknown => "UNKNOWN", - Sdk::Js => "JS", - Sdk::Swift => "SWIFT", - Sdk::Android => "ANDROID", - Sdk::Flutter => "FLUTTER", - Sdk::Go => "GO", - Sdk::Unity => "UNITY", - Sdk::ReactNative => "REACT_NATIVE", - Sdk::Rust => "RUST", - Sdk::Python => "PYTHON", - Sdk::Cpp => "CPP", + Self::Unknown => "UNKNOWN", + Self::Js => "JS", + Self::Swift => "SWIFT", + Self::Android => "ANDROID", + Self::Flutter => "FLUTTER", + Self::Go => "GO", + Self::Unity => "UNITY", + Self::ReactNative => "REACT_NATIVE", + Self::Rust => "RUST", + Self::Python => "PYTHON", + Self::Cpp => "CPP", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -791,9 +791,9 @@ impl AudioCodec { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - AudioCodec::DefaultAc => "DEFAULT_AC", - AudioCodec::Opus => "OPUS", - AudioCodec::Aac => "AAC", + Self::DefaultAc => "DEFAULT_AC", + Self::Opus => "OPUS", + Self::Aac => "AAC", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -822,11 +822,11 @@ impl VideoCodec { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - VideoCodec::DefaultVc => "DEFAULT_VC", - VideoCodec::H264Baseline => "H264_BASELINE", - VideoCodec::H264Main => "H264_MAIN", - VideoCodec::H264High => "H264_HIGH", - VideoCodec::Vp8 => "VP8", + Self::DefaultVc => "DEFAULT_VC", + Self::H264Baseline => "H264_BASELINE", + Self::H264Main => "H264_MAIN", + Self::H264High => "H264_HIGH", + Self::Vp8 => "VP8", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -854,8 +854,8 @@ impl ImageCodec { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - ImageCodec::IcDefault => "IC_DEFAULT", - ImageCodec::IcJpeg => "IC_JPEG", + Self::IcDefault => "IC_DEFAULT", + Self::IcJpeg => "IC_JPEG", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -881,9 +881,9 @@ impl TrackType { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - TrackType::Audio => "AUDIO", - TrackType::Video => "VIDEO", - TrackType::Data => "DATA", + Self::Audio => "AUDIO", + Self::Video => "VIDEO", + Self::Data => "DATA", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -912,11 +912,11 @@ impl TrackSource { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - TrackSource::Unknown => "UNKNOWN", - TrackSource::Camera => "CAMERA", - TrackSource::Microphone => "MICROPHONE", - TrackSource::ScreenShare => "SCREEN_SHARE", - TrackSource::ScreenShareAudio => "SCREEN_SHARE_AUDIO", + Self::Unknown => "UNKNOWN", + Self::Camera => "CAMERA", + Self::Microphone => "MICROPHONE", + Self::ScreenShare => "SCREEN_SHARE", + Self::ScreenShareAudio => "SCREEN_SHARE_AUDIO", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -946,10 +946,10 @@ impl VideoQuality { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - VideoQuality::Low => "LOW", - VideoQuality::Medium => "MEDIUM", - VideoQuality::High => "HIGH", - VideoQuality::Off => "OFF", + Self::Low => "LOW", + Self::Medium => "MEDIUM", + Self::High => "HIGH", + Self::Off => "OFF", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -978,10 +978,10 @@ impl ConnectionQuality { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - ConnectionQuality::Poor => "POOR", - ConnectionQuality::Good => "GOOD", - ConnectionQuality::Excellent => "EXCELLENT", - ConnectionQuality::Lost => "LOST", + Self::Poor => "POOR", + Self::Good => "GOOD", + Self::Excellent => "EXCELLENT", + Self::Lost => "LOST", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1009,9 +1009,9 @@ impl ClientConfigSetting { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - ClientConfigSetting::Unset => "UNSET", - ClientConfigSetting::Disabled => "DISABLED", - ClientConfigSetting::Enabled => "ENABLED", + Self::Unset => "UNSET", + Self::Disabled => "DISABLED", + Self::Enabled => "ENABLED", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1056,17 +1056,17 @@ impl DisconnectReason { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - DisconnectReason::UnknownReason => "UNKNOWN_REASON", - DisconnectReason::ClientInitiated => "CLIENT_INITIATED", - DisconnectReason::DuplicateIdentity => "DUPLICATE_IDENTITY", - DisconnectReason::ServerShutdown => "SERVER_SHUTDOWN", - DisconnectReason::ParticipantRemoved => "PARTICIPANT_REMOVED", - DisconnectReason::RoomDeleted => "ROOM_DELETED", - DisconnectReason::StateMismatch => "STATE_MISMATCH", - DisconnectReason::JoinFailure => "JOIN_FAILURE", - DisconnectReason::Migration => "MIGRATION", - DisconnectReason::SignalClose => "SIGNAL_CLOSE", - DisconnectReason::RoomClosed => "ROOM_CLOSED", + Self::UnknownReason => "UNKNOWN_REASON", + Self::ClientInitiated => "CLIENT_INITIATED", + Self::DuplicateIdentity => "DUPLICATE_IDENTITY", + Self::ServerShutdown => "SERVER_SHUTDOWN", + Self::ParticipantRemoved => "PARTICIPANT_REMOVED", + Self::RoomDeleted => "ROOM_DELETED", + Self::StateMismatch => "STATE_MISMATCH", + Self::JoinFailure => "JOIN_FAILURE", + Self::Migration => "MIGRATION", + Self::SignalClose => "SIGNAL_CLOSE", + Self::RoomClosed => "ROOM_CLOSED", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1103,11 +1103,11 @@ impl ReconnectReason { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - ReconnectReason::RrUnknown => "RR_UNKNOWN", - ReconnectReason::RrSignalDisconnected => "RR_SIGNAL_DISCONNECTED", - ReconnectReason::RrPublisherFailed => "RR_PUBLISHER_FAILED", - ReconnectReason::RrSubscriberFailed => "RR_SUBSCRIBER_FAILED", - ReconnectReason::RrSwitchCandidate => "RR_SWITCH_CANDIDATE", + Self::RrUnknown => "RR_UNKNOWN", + Self::RrSignalDisconnected => "RR_SIGNAL_DISCONNECTED", + Self::RrPublisherFailed => "RR_PUBLISHER_FAILED", + Self::RrSubscriberFailed => "RR_SUBSCRIBER_FAILED", + Self::RrSwitchCandidate => "RR_SWITCH_CANDIDATE", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1136,9 +1136,9 @@ impl SubscriptionError { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - SubscriptionError::SeUnknown => "SE_UNKNOWN", - SubscriptionError::SeCodecUnsupported => "SE_CODEC_UNSUPPORTED", - SubscriptionError::SeTrackNotfound => "SE_TRACK_NOTFOUND", + Self::SeUnknown => "SE_UNKNOWN", + Self::SeCodecUnsupported => "SE_CODEC_UNSUPPORTED", + Self::SeTrackNotfound => "SE_TRACK_NOTFOUND", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1168,12 +1168,12 @@ impl AudioTrackFeature { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - AudioTrackFeature::TfStereo => "TF_STEREO", - AudioTrackFeature::TfNoDtx => "TF_NO_DTX", - AudioTrackFeature::TfAutoGainControl => "TF_AUTO_GAIN_CONTROL", - AudioTrackFeature::TfEchoCancellation => "TF_ECHO_CANCELLATION", - AudioTrackFeature::TfNoiseSuppression => "TF_NOISE_SUPPRESSION", - AudioTrackFeature::TfEnhancedNoiseCancellation => "TF_ENHANCED_NOISE_CANCELLATION", + Self::TfStereo => "TF_STEREO", + Self::TfNoDtx => "TF_NO_DTX", + Self::TfAutoGainControl => "TF_AUTO_GAIN_CONTROL", + Self::TfEchoCancellation => "TF_ECHO_CANCELLATION", + Self::TfNoiseSuppression => "TF_NOISE_SUPPRESSION", + Self::TfEnhancedNoiseCancellation => "TF_ENHANCED_NOISE_CANCELLATION", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1784,9 +1784,9 @@ pub mod stream_info { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Status::Active => "ACTIVE", - Status::Finished => "FINISHED", - Status::Failed => "FAILED", + Self::Active => "ACTIVE", + Self::Finished => "FINISHED", + Self::Failed => "FAILED", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1908,9 +1908,9 @@ impl EncodedFileType { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - EncodedFileType::DefaultFiletype => "DEFAULT_FILETYPE", - EncodedFileType::Mp4 => "MP4", - EncodedFileType::Ogg => "OGG", + Self::DefaultFiletype => "DEFAULT_FILETYPE", + Self::Mp4 => "MP4", + Self::Ogg => "OGG", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1936,8 +1936,8 @@ impl SegmentedFileProtocol { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - SegmentedFileProtocol::DefaultSegmentedFileProtocol => "DEFAULT_SEGMENTED_FILE_PROTOCOL", - SegmentedFileProtocol::HlsProtocol => "HLS_PROTOCOL", + Self::DefaultSegmentedFileProtocol => "DEFAULT_SEGMENTED_FILE_PROTOCOL", + Self::HlsProtocol => "HLS_PROTOCOL", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1962,8 +1962,8 @@ impl SegmentedFileSuffix { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - SegmentedFileSuffix::Index => "INDEX", - SegmentedFileSuffix::Timestamp => "TIMESTAMP", + Self::Index => "INDEX", + Self::Timestamp => "TIMESTAMP", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1988,8 +1988,8 @@ impl ImageFileSuffix { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - ImageFileSuffix::ImageSuffixIndex => "IMAGE_SUFFIX_INDEX", - ImageFileSuffix::ImageSuffixTimestamp => "IMAGE_SUFFIX_TIMESTAMP", + Self::ImageSuffixIndex => "IMAGE_SUFFIX_INDEX", + Self::ImageSuffixTimestamp => "IMAGE_SUFFIX_TIMESTAMP", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -2016,9 +2016,9 @@ impl StreamProtocol { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - StreamProtocol::DefaultProtocol => "DEFAULT_PROTOCOL", - StreamProtocol::Rtmp => "RTMP", - StreamProtocol::Srt => "SRT", + Self::DefaultProtocol => "DEFAULT_PROTOCOL", + Self::Rtmp => "RTMP", + Self::Srt => "SRT", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -2058,14 +2058,14 @@ impl EncodingOptionsPreset { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - EncodingOptionsPreset::H264720p30 => "H264_720P_30", - EncodingOptionsPreset::H264720p60 => "H264_720P_60", - EncodingOptionsPreset::H2641080p30 => "H264_1080P_30", - EncodingOptionsPreset::H2641080p60 => "H264_1080P_60", - EncodingOptionsPreset::PortraitH264720p30 => "PORTRAIT_H264_720P_30", - EncodingOptionsPreset::PortraitH264720p60 => "PORTRAIT_H264_720P_60", - EncodingOptionsPreset::PortraitH2641080p30 => "PORTRAIT_H264_1080P_30", - EncodingOptionsPreset::PortraitH2641080p60 => "PORTRAIT_H264_1080P_60", + Self::H264720p30 => "H264_720P_30", + Self::H264720p60 => "H264_720P_60", + Self::H2641080p30 => "H264_1080P_30", + Self::H2641080p60 => "H264_1080P_60", + Self::PortraitH264720p30 => "PORTRAIT_H264_720P_30", + Self::PortraitH264720p60 => "PORTRAIT_H264_720P_60", + Self::PortraitH2641080p30 => "PORTRAIT_H264_1080P_30", + Self::PortraitH2641080p60 => "PORTRAIT_H264_1080P_60", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -2101,13 +2101,13 @@ impl EgressStatus { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - EgressStatus::EgressStarting => "EGRESS_STARTING", - EgressStatus::EgressActive => "EGRESS_ACTIVE", - EgressStatus::EgressEnding => "EGRESS_ENDING", - EgressStatus::EgressComplete => "EGRESS_COMPLETE", - EgressStatus::EgressFailed => "EGRESS_FAILED", - EgressStatus::EgressAborted => "EGRESS_ABORTED", - EgressStatus::EgressLimitReached => "EGRESS_LIMIT_REACHED", + Self::EgressStarting => "EGRESS_STARTING", + Self::EgressActive => "EGRESS_ACTIVE", + Self::EgressEnding => "EGRESS_ENDING", + Self::EgressComplete => "EGRESS_COMPLETE", + Self::EgressFailed => "EGRESS_FAILED", + Self::EgressAborted => "EGRESS_ABORTED", + Self::EgressLimitReached => "EGRESS_LIMIT_REACHED", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -2484,9 +2484,9 @@ pub mod leave_request { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Action::Disconnect => "DISCONNECT", - Action::Resume => "RESUME", - Action::Reconnect => "RECONNECT", + Self::Disconnect => "DISCONNECT", + Self::Resume => "RESUME", + Self::Reconnect => "RECONNECT", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -2747,10 +2747,10 @@ pub mod request_response { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Reason::Ok => "OK", - Reason::NotFound => "NOT_FOUND", - Reason::NotAllowed => "NOT_ALLOWED", - Reason::LimitExceeded => "LIMIT_EXCEEDED", + Self::Ok => "OK", + Self::NotFound => "NOT_FOUND", + Self::NotAllowed => "NOT_ALLOWED", + Self::LimitExceeded => "LIMIT_EXCEEDED", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -2783,8 +2783,8 @@ impl SignalTarget { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - SignalTarget::Publisher => "PUBLISHER", - SignalTarget::Subscriber => "SUBSCRIBER", + Self::Publisher => "PUBLISHER", + Self::Subscriber => "SUBSCRIBER", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -2809,8 +2809,8 @@ impl StreamState { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - StreamState::Active => "ACTIVE", - StreamState::Paused => "PAUSED", + Self::Active => "ACTIVE", + Self::Paused => "PAUSED", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -2836,9 +2836,9 @@ impl CandidateProtocol { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - CandidateProtocol::Udp => "UDP", - CandidateProtocol::Tcp => "TCP", - CandidateProtocol::Tls => "TLS", + Self::Udp => "UDP", + Self::Tcp => "TCP", + Self::Tls => "TLS", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -3067,8 +3067,8 @@ impl JobType { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - JobType::JtRoom => "JT_ROOM", - JobType::JtPublisher => "JT_PUBLISHER", + Self::JtRoom => "JT_ROOM", + Self::JtPublisher => "JT_PUBLISHER", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -3093,8 +3093,8 @@ impl WorkerStatus { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - WorkerStatus::WsAvailable => "WS_AVAILABLE", - WorkerStatus::WsFull => "WS_FULL", + Self::WsAvailable => "WS_AVAILABLE", + Self::WsFull => "WS_FULL", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -3121,10 +3121,10 @@ impl JobStatus { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - JobStatus::JsPending => "JS_PENDING", - JobStatus::JsRunning => "JS_RUNNING", - JobStatus::JsSuccess => "JS_SUCCESS", - JobStatus::JsFailed => "JS_FAILED", + Self::JsPending => "JS_PENDING", + Self::JsRunning => "JS_RUNNING", + Self::JsSuccess => "JS_SUCCESS", + Self::JsFailed => "JS_FAILED", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -3596,11 +3596,11 @@ pub mod ingress_state { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Status::EndpointInactive => "ENDPOINT_INACTIVE", - Status::EndpointBuffering => "ENDPOINT_BUFFERING", - Status::EndpointPublishing => "ENDPOINT_PUBLISHING", - Status::EndpointError => "ENDPOINT_ERROR", - Status::EndpointComplete => "ENDPOINT_COMPLETE", + Self::EndpointInactive => "ENDPOINT_INACTIVE", + Self::EndpointBuffering => "ENDPOINT_BUFFERING", + Self::EndpointPublishing => "ENDPOINT_PUBLISHING", + Self::EndpointError => "ENDPOINT_ERROR", + Self::EndpointComplete => "ENDPOINT_COMPLETE", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -3700,9 +3700,9 @@ impl IngressInput { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - IngressInput::RtmpInput => "RTMP_INPUT", - IngressInput::WhipInput => "WHIP_INPUT", - IngressInput::UrlInput => "URL_INPUT", + Self::RtmpInput => "RTMP_INPUT", + Self::WhipInput => "WHIP_INPUT", + Self::UrlInput => "URL_INPUT", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -3730,8 +3730,8 @@ impl IngressAudioEncodingPreset { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - IngressAudioEncodingPreset::OpusStereo96kbps => "OPUS_STEREO_96KBPS", - IngressAudioEncodingPreset::OpusMono64kbs => "OPUS_MONO_64KBS", + Self::OpusStereo96kbps => "OPUS_STEREO_96KBPS", + Self::OpusMono64kbs => "OPUS_MONO_64KBS", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -3774,16 +3774,16 @@ impl IngressVideoEncodingPreset { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - IngressVideoEncodingPreset::H264720p30fps3Layers => "H264_720P_30FPS_3_LAYERS", - IngressVideoEncodingPreset::H2641080p30fps3Layers => "H264_1080P_30FPS_3_LAYERS", - IngressVideoEncodingPreset::H264540p25fps2Layers => "H264_540P_25FPS_2_LAYERS", - IngressVideoEncodingPreset::H264720p30fps1Layer => "H264_720P_30FPS_1_LAYER", - IngressVideoEncodingPreset::H2641080p30fps1Layer => "H264_1080P_30FPS_1_LAYER", - IngressVideoEncodingPreset::H264720p30fps3LayersHighMotion => "H264_720P_30FPS_3_LAYERS_HIGH_MOTION", - IngressVideoEncodingPreset::H2641080p30fps3LayersHighMotion => "H264_1080P_30FPS_3_LAYERS_HIGH_MOTION", - IngressVideoEncodingPreset::H264540p25fps2LayersHighMotion => "H264_540P_25FPS_2_LAYERS_HIGH_MOTION", - IngressVideoEncodingPreset::H264720p30fps1LayerHighMotion => "H264_720P_30FPS_1_LAYER_HIGH_MOTION", - IngressVideoEncodingPreset::H2641080p30fps1LayerHighMotion => "H264_1080P_30FPS_1_LAYER_HIGH_MOTION", + Self::H264720p30fps3Layers => "H264_720P_30FPS_3_LAYERS", + Self::H2641080p30fps3Layers => "H264_1080P_30FPS_3_LAYERS", + Self::H264540p25fps2Layers => "H264_540P_25FPS_2_LAYERS", + Self::H264720p30fps1Layer => "H264_720P_30FPS_1_LAYER", + Self::H2641080p30fps1Layer => "H264_1080P_30FPS_1_LAYER", + Self::H264720p30fps3LayersHighMotion => "H264_720P_30FPS_3_LAYERS_HIGH_MOTION", + Self::H2641080p30fps3LayersHighMotion => "H264_1080P_30FPS_3_LAYERS_HIGH_MOTION", + Self::H264540p25fps2LayersHighMotion => "H264_540P_25FPS_2_LAYERS_HIGH_MOTION", + Self::H264720p30fps1LayerHighMotion => "H264_720P_30FPS_1_LAYER_HIGH_MOTION", + Self::H2641080p30fps1LayerHighMotion => "H264_1080P_30FPS_1_LAYER_HIGH_MOTION", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -3930,9 +3930,9 @@ pub mod sip_trunk_info { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - TrunkKind::TrunkLegacy => "TRUNK_LEGACY", - TrunkKind::TrunkInbound => "TRUNK_INBOUND", - TrunkKind::TrunkOutbound => "TRUNK_OUTBOUND", + Self::TrunkLegacy => "TRUNK_LEGACY", + Self::TrunkInbound => "TRUNK_INBOUND", + Self::TrunkOutbound => "TRUNK_OUTBOUND", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -4208,10 +4208,10 @@ impl SipTransport { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - SipTransport::Auto => "SIP_TRANSPORT_AUTO", - SipTransport::Udp => "SIP_TRANSPORT_UDP", - SipTransport::Tcp => "SIP_TRANSPORT_TCP", - SipTransport::Tls => "SIP_TRANSPORT_TLS", + Self::Auto => "SIP_TRANSPORT_AUTO", + Self::Udp => "SIP_TRANSPORT_UDP", + Self::Tcp => "SIP_TRANSPORT_TCP", + Self::Tls => "SIP_TRANSPORT_TLS", } } /// Creates an enum from field names used in the ProtoBuf definition. diff --git a/livekit/src/rtc_engine/mod.rs b/livekit/src/rtc_engine/mod.rs index fb49b689b..f718585b8 100644 --- a/livekit/src/rtc_engine/mod.rs +++ b/livekit/src/rtc_engine/mod.rs @@ -402,7 +402,7 @@ impl EngineInner { async fn on_session_event(self: &Arc, event: SessionEvent) -> EngineResult<()> { match event { SessionEvent::Close { source, reason, action, retry_now } => { - log::debug!("received session close: {}, {:?}", source, reason); + log::warn!("received session close: {:?} {:?} {:?}", source, reason, action); match action { proto::leave_request::Action::Resume => { self.reconnection_needed(retry_now, false) @@ -611,7 +611,7 @@ impl EngineInner { log::error!("resuming connection... attempt: {}", i); if let Err(err) = self.try_resume_connection().await { log::error!("resuming connection failed: {}", err); - if let EngineError::Signal(_) = err { + if !matches!(err, EngineError::Signal(_)) { let mut running_handle = self.running_handle.write(); running_handle.full_reconnect = true; } diff --git a/livekit/src/rtc_engine/rtc_session.rs b/livekit/src/rtc_engine/rtc_session.rs index 7fb4c46d1..e9f5619a6 100644 --- a/livekit/src/rtc_engine/rtc_session.rs +++ b/livekit/src/rtc_engine/rtc_session.rs @@ -420,7 +420,7 @@ impl SessionInner { self.on_session_disconnected( format!("signal client closed: {:?}", reason).as_str(), DisconnectReason::UnknownReason, - proto::leave_request::Action::Disconnect, + proto::leave_request::Action::Resume, false, ); } From c66f715eb56322fc0a0158181fafc14e02a2618b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Tue, 24 Sep 2024 12:36:44 -0700 Subject: [PATCH 041/274] fix: avoid wrong reconnection logs (#441) --- livekit-api/src/signal_client/mod.rs | 10 +++++----- .../src/signal_client/signal_stream.rs | 6 ++++-- livekit/src/rtc_engine/rtc_session.rs | 19 +++++++++++-------- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/livekit-api/src/signal_client/mod.rs b/livekit-api/src/signal_client/mod.rs index 73ac1f916..711ba7ed2 100644 --- a/livekit-api/src/signal_client/mod.rs +++ b/livekit-api/src/signal_client/mod.rs @@ -152,7 +152,7 @@ impl SignalClient { /// Close the connection to the server pub async fn close(&self) { - self.inner.close().await; + self.inner.close(true).await; let handle = self.handle.lock().take(); if let Some(signal_task) = handle { @@ -254,7 +254,7 @@ impl SignalInner { proto::ReconnectResponse, mpsc::UnboundedReceiver>, )> { - self.close().await; + self.close(false).await; // Lock while we are reconnecting let mut stream = self.stream.write().await; @@ -278,9 +278,9 @@ impl SignalInner { } /// Close the connection - pub async fn close(&self) { + pub async fn close(&self, notify_close: bool) { if let Some(stream) = self.stream.write().await.take() { - stream.close().await; + stream.close(notify_close).await; } } @@ -392,7 +392,7 @@ async fn signal_task( } } - inner.close().await; // Make sure to always close the ws connection when the loop is terminated + inner.close(true).await; // Make sure to always close the ws connection when the loop is terminated } /// Check if the signal is queuable diff --git a/livekit-api/src/signal_client/signal_stream.rs b/livekit-api/src/signal_client/signal_stream.rs index 835ca7e8f..c5ab27951 100644 --- a/livekit-api/src/signal_client/signal_stream.rs +++ b/livekit-api/src/signal_client/signal_stream.rs @@ -110,8 +110,10 @@ impl SignalStream { /// Close the websocket /// It sends a CloseFrame to the server before closing - pub async fn close(self) { - let _ = self.internal_tx.send(InternalMessage::Close).await; + pub async fn close(self, notify_close: bool) { + if notify_close { + let _ = self.internal_tx.send(InternalMessage::Close).await; + } let _ = self.write_handle.await; let _ = self.read_handle.await; } diff --git a/livekit/src/rtc_engine/rtc_session.rs b/livekit/src/rtc_engine/rtc_session.rs index e9f5619a6..cadbd8ff7 100644 --- a/livekit/src/rtc_engine/rtc_session.rs +++ b/livekit/src/rtc_engine/rtc_session.rs @@ -416,13 +416,15 @@ impl SessionInner { task.await; } SignalEvent::Close(reason) => { - // SignalClient has been closed - self.on_session_disconnected( - format!("signal client closed: {:?}", reason).as_str(), - DisconnectReason::UnknownReason, - proto::leave_request::Action::Resume, - false, - ); + if !self.closed.load(Ordering::Acquire) { + // SignalClient has been closed unexpectedly + self.on_session_disconnected( + format!("signal client closed: {:?}", reason).as_str(), + DisconnectReason::UnknownReason, + proto::leave_request::Action::Resume, + false, + ); + } } } }, @@ -776,6 +778,8 @@ impl SessionInner { } async fn close(&self) { + self.closed.store(true, Ordering::Release); + self.signal_client .send(proto::signal_request::Message::Leave(proto::LeaveRequest { action: proto::leave_request::Action::Disconnect.into(), @@ -784,7 +788,6 @@ impl SessionInner { })) .await; - self.closed.store(true, Ordering::Release); self.signal_client.close().await; self.publisher_pc.close(); self.subscriber_pc.close(); From f675ef8cb1d9148137bfce6cff6306f22e1ee638 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Tue, 24 Sep 2024 20:21:25 -0700 Subject: [PATCH 042/274] fix livekit-protocol generation (#442) * wip * add comment --- .github/workflows/gen-protocol.yaml | 2 +- livekit-protocol/generate_proto.sh | 3 + livekit-protocol/src/livekit.rs | 629 ++++++++++++++++++---------- 3 files changed, 421 insertions(+), 213 deletions(-) diff --git a/.github/workflows/gen-protocol.yaml b/.github/workflows/gen-protocol.yaml index 7941cd182..16818170d 100644 --- a/.github/workflows/gen-protocol.yaml +++ b/.github/workflows/gen-protocol.yaml @@ -50,7 +50,7 @@ jobs: repo-token: ${{ secrets.GITHUB_TOKEN }} - name: Install prost generators - run: cargo install protoc-gen-prost protoc-gen-prost-serde + run: cargo install protoc-gen-prost@0.3.1 protoc-gen-prost-serde@0.3.1 - name: generate python stubs run: ./generate_proto.sh diff --git a/livekit-protocol/generate_proto.sh b/livekit-protocol/generate_proto.sh index 4ef8efaad..d8b5c19c7 100755 --- a/livekit-protocol/generate_proto.sh +++ b/livekit-protocol/generate_proto.sh @@ -14,6 +14,9 @@ # limitations under the License. +# dependencies: cargo install protoc-gen-prost@0.3.1 protoc-gen-prost-serde@0.3.1 + + PROTOCOL=protocol/protobufs OUT_RUST=src diff --git a/livekit-protocol/src/livekit.rs b/livekit-protocol/src/livekit.rs index b2a1de945..39426bdef 100644 --- a/livekit-protocol/src/livekit.rs +++ b/livekit-protocol/src/livekit.rs @@ -1,5 +1,6 @@ // @generated // This file is @generated by prost-build. +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct Room { #[prost(string, tag="1")] @@ -29,6 +30,7 @@ pub struct Room { #[prost(message, optional, tag="13")] pub version: ::core::option::Option, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct Codec { #[prost(string, tag="1")] @@ -36,7 +38,8 @@ pub struct Codec { #[prost(string, tag="2")] pub fmtp_line: ::prost::alloc::string::String, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct PlayoutDelay { #[prost(bool, tag="1")] pub enabled: bool, @@ -45,6 +48,7 @@ pub struct PlayoutDelay { #[prost(uint32, tag="3")] pub max: u32, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ParticipantPermission { /// allow participant to subscribe to other tracks in the room @@ -76,6 +80,7 @@ pub struct ParticipantPermission { #[prost(bool, tag="11")] pub agent: bool, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ParticipantInfo { #[prost(string, tag="1")] @@ -131,10 +136,10 @@ pub mod participant_info { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::Joining => "JOINING", - Self::Joined => "JOINED", - Self::Active => "ACTIVE", - Self::Disconnected => "DISCONNECTED", + State::Joining => "JOINING", + State::Joined => "JOINED", + State::Active => "ACTIVE", + State::Disconnected => "DISCONNECTED", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -169,11 +174,11 @@ pub mod participant_info { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::Standard => "STANDARD", - Self::Ingress => "INGRESS", - Self::Egress => "EGRESS", - Self::Sip => "SIP", - Self::Agent => "AGENT", + Kind::Standard => "STANDARD", + Kind::Ingress => "INGRESS", + Kind::Egress => "EGRESS", + Kind::Sip => "SIP", + Kind::Agent => "AGENT", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -189,7 +194,8 @@ pub mod participant_info { } } } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct Encryption { } /// Nested message and enum types in `Encryption`. @@ -208,9 +214,9 @@ pub mod encryption { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::None => "NONE", - Self::Gcm => "GCM", - Self::Custom => "CUSTOM", + Type::None => "NONE", + Type::Gcm => "GCM", + Type::Custom => "CUSTOM", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -224,6 +230,7 @@ pub mod encryption { } } } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SimulcastCodecInfo { #[prost(string, tag="1")] @@ -235,6 +242,7 @@ pub struct SimulcastCodecInfo { #[prost(message, repeated, tag="4")] pub layers: ::prost::alloc::vec::Vec, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackInfo { #[prost(string, tag="1")] @@ -285,7 +293,8 @@ pub struct TrackInfo { pub audio_features: ::prost::alloc::vec::Vec, } /// provide information about available spatial layers -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct VideoLayer { /// for tracks with a single layer, this should be HIGH #[prost(enumeration="VideoQuality", tag="1")] @@ -301,6 +310,7 @@ pub struct VideoLayer { pub ssrc: u32, } /// new DataPacket API +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct DataPacket { #[deprecated] @@ -330,8 +340,8 @@ pub mod data_packet { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::Reliable => "RELIABLE", - Self::Lossy => "LOSSY", + Kind::Reliable => "RELIABLE", + Kind::Lossy => "LOSSY", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -343,7 +353,8 @@ pub mod data_packet { } } } - #[derive(Clone, PartialEq, ::prost::Oneof)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Value { #[prost(message, tag="2")] User(super::UserPacket), @@ -355,11 +366,13 @@ pub mod data_packet { Transcription(super::Transcription), } } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ActiveSpeakerUpdate { #[prost(message, repeated, tag="1")] pub speakers: ::prost::alloc::vec::Vec, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SpeakerInfo { #[prost(string, tag="1")] @@ -371,6 +384,7 @@ pub struct SpeakerInfo { #[prost(bool, tag="3")] pub active: bool, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct UserPacket { /// participant ID of user that sent the message @@ -403,6 +417,7 @@ pub struct UserPacket { #[prost(uint64, optional, tag="10")] pub end_time: ::core::option::Option, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SipDtmf { #[prost(uint32, tag="3")] @@ -410,6 +425,7 @@ pub struct SipDtmf { #[prost(string, tag="4")] pub digit: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct Transcription { /// Participant that got its speech transcribed @@ -420,6 +436,7 @@ pub struct Transcription { #[prost(message, repeated, tag="4")] pub segments: ::prost::alloc::vec::Vec, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TranscriptionSegment { #[prost(string, tag="1")] @@ -435,6 +452,7 @@ pub struct TranscriptionSegment { #[prost(string, tag="6")] pub language: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ParticipantTracks { /// participant ID of participant to whom the tracks belong @@ -444,6 +462,7 @@ pub struct ParticipantTracks { pub track_sids: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, } /// details about the server +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ServerInfo { #[prost(enumeration="server_info::Edition", tag="1")] @@ -477,8 +496,8 @@ pub mod server_info { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::Standard => "Standard", - Self::Cloud => "Cloud", + Edition::Standard => "Standard", + Edition::Cloud => "Cloud", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -492,6 +511,7 @@ pub mod server_info { } } /// details about the client +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ClientInfo { #[prost(enumeration="client_info::Sdk", tag="1")] @@ -540,17 +560,17 @@ pub mod client_info { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::Unknown => "UNKNOWN", - Self::Js => "JS", - Self::Swift => "SWIFT", - Self::Android => "ANDROID", - Self::Flutter => "FLUTTER", - Self::Go => "GO", - Self::Unity => "UNITY", - Self::ReactNative => "REACT_NATIVE", - Self::Rust => "RUST", - Self::Python => "PYTHON", - Self::Cpp => "CPP", + Sdk::Unknown => "UNKNOWN", + Sdk::Js => "JS", + Sdk::Swift => "SWIFT", + Sdk::Android => "ANDROID", + Sdk::Flutter => "FLUTTER", + Sdk::Go => "GO", + Sdk::Unity => "UNITY", + Sdk::ReactNative => "REACT_NATIVE", + Sdk::Rust => "RUST", + Sdk::Python => "PYTHON", + Sdk::Cpp => "CPP", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -573,6 +593,7 @@ pub mod client_info { } } /// server provided client configuration +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ClientConfiguration { #[prost(message, optional, tag="1")] @@ -586,11 +607,13 @@ pub struct ClientConfiguration { #[prost(enumeration="ClientConfigSetting", tag="5")] pub force_relay: i32, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct VideoConfiguration { #[prost(enumeration="ClientConfigSetting", tag="1")] pub hardware_encoder: i32, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct DisabledCodecs { /// disabled for both publish and subscribe @@ -600,7 +623,8 @@ pub struct DisabledCodecs { #[prost(message, repeated, tag="2")] pub publish: ::prost::alloc::vec::Vec, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct RtpDrift { #[prost(message, optional, tag="1")] pub start_time: ::core::option::Option<::pbjson_types::Timestamp>, @@ -621,6 +645,7 @@ pub struct RtpDrift { #[prost(double, tag="9")] pub clock_rate: f64, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RtpStats { #[prost(message, optional, tag="1")] @@ -713,7 +738,8 @@ pub struct RtpStats { #[prost(message, optional, tag="46")] pub rebased_report_drift: ::core::option::Option, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct RtpForwarderState { #[prost(bool, tag="1")] pub started: bool, @@ -732,13 +758,15 @@ pub struct RtpForwarderState { } /// Nested message and enum types in `RTPForwarderState`. pub mod rtp_forwarder_state { - #[derive(Clone, Copy, PartialEq, ::prost::Oneof)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] pub enum CodecMunger { #[prost(message, tag="7")] Vp8Munger(super::Vp8MungerState), } } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct RtpMungerState { #[prost(uint64, tag="1")] pub ext_last_sequence_number: u64, @@ -753,7 +781,8 @@ pub struct RtpMungerState { #[prost(bool, tag="6")] pub second_last_marker: bool, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct Vp8MungerState { #[prost(int32, tag="1")] pub ext_last_picture_id: i32, @@ -770,7 +799,8 @@ pub struct Vp8MungerState { #[prost(bool, tag="7")] pub key_idx_used: bool, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct TimedVersion { #[prost(int64, tag="1")] pub unix_micro: i64, @@ -791,9 +821,9 @@ impl AudioCodec { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::DefaultAc => "DEFAULT_AC", - Self::Opus => "OPUS", - Self::Aac => "AAC", + AudioCodec::DefaultAc => "DEFAULT_AC", + AudioCodec::Opus => "OPUS", + AudioCodec::Aac => "AAC", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -822,11 +852,11 @@ impl VideoCodec { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::DefaultVc => "DEFAULT_VC", - Self::H264Baseline => "H264_BASELINE", - Self::H264Main => "H264_MAIN", - Self::H264High => "H264_HIGH", - Self::Vp8 => "VP8", + VideoCodec::DefaultVc => "DEFAULT_VC", + VideoCodec::H264Baseline => "H264_BASELINE", + VideoCodec::H264Main => "H264_MAIN", + VideoCodec::H264High => "H264_HIGH", + VideoCodec::Vp8 => "VP8", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -854,8 +884,8 @@ impl ImageCodec { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::IcDefault => "IC_DEFAULT", - Self::IcJpeg => "IC_JPEG", + ImageCodec::IcDefault => "IC_DEFAULT", + ImageCodec::IcJpeg => "IC_JPEG", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -881,9 +911,9 @@ impl TrackType { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::Audio => "AUDIO", - Self::Video => "VIDEO", - Self::Data => "DATA", + TrackType::Audio => "AUDIO", + TrackType::Video => "VIDEO", + TrackType::Data => "DATA", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -912,11 +942,11 @@ impl TrackSource { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::Unknown => "UNKNOWN", - Self::Camera => "CAMERA", - Self::Microphone => "MICROPHONE", - Self::ScreenShare => "SCREEN_SHARE", - Self::ScreenShareAudio => "SCREEN_SHARE_AUDIO", + TrackSource::Unknown => "UNKNOWN", + TrackSource::Camera => "CAMERA", + TrackSource::Microphone => "MICROPHONE", + TrackSource::ScreenShare => "SCREEN_SHARE", + TrackSource::ScreenShareAudio => "SCREEN_SHARE_AUDIO", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -946,10 +976,10 @@ impl VideoQuality { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::Low => "LOW", - Self::Medium => "MEDIUM", - Self::High => "HIGH", - Self::Off => "OFF", + VideoQuality::Low => "LOW", + VideoQuality::Medium => "MEDIUM", + VideoQuality::High => "HIGH", + VideoQuality::Off => "OFF", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -978,10 +1008,10 @@ impl ConnectionQuality { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::Poor => "POOR", - Self::Good => "GOOD", - Self::Excellent => "EXCELLENT", - Self::Lost => "LOST", + ConnectionQuality::Poor => "POOR", + ConnectionQuality::Good => "GOOD", + ConnectionQuality::Excellent => "EXCELLENT", + ConnectionQuality::Lost => "LOST", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1009,9 +1039,9 @@ impl ClientConfigSetting { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::Unset => "UNSET", - Self::Disabled => "DISABLED", - Self::Enabled => "ENABLED", + ClientConfigSetting::Unset => "UNSET", + ClientConfigSetting::Disabled => "DISABLED", + ClientConfigSetting::Enabled => "ENABLED", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1056,17 +1086,17 @@ impl DisconnectReason { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::UnknownReason => "UNKNOWN_REASON", - Self::ClientInitiated => "CLIENT_INITIATED", - Self::DuplicateIdentity => "DUPLICATE_IDENTITY", - Self::ServerShutdown => "SERVER_SHUTDOWN", - Self::ParticipantRemoved => "PARTICIPANT_REMOVED", - Self::RoomDeleted => "ROOM_DELETED", - Self::StateMismatch => "STATE_MISMATCH", - Self::JoinFailure => "JOIN_FAILURE", - Self::Migration => "MIGRATION", - Self::SignalClose => "SIGNAL_CLOSE", - Self::RoomClosed => "ROOM_CLOSED", + DisconnectReason::UnknownReason => "UNKNOWN_REASON", + DisconnectReason::ClientInitiated => "CLIENT_INITIATED", + DisconnectReason::DuplicateIdentity => "DUPLICATE_IDENTITY", + DisconnectReason::ServerShutdown => "SERVER_SHUTDOWN", + DisconnectReason::ParticipantRemoved => "PARTICIPANT_REMOVED", + DisconnectReason::RoomDeleted => "ROOM_DELETED", + DisconnectReason::StateMismatch => "STATE_MISMATCH", + DisconnectReason::JoinFailure => "JOIN_FAILURE", + DisconnectReason::Migration => "MIGRATION", + DisconnectReason::SignalClose => "SIGNAL_CLOSE", + DisconnectReason::RoomClosed => "ROOM_CLOSED", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1103,11 +1133,11 @@ impl ReconnectReason { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::RrUnknown => "RR_UNKNOWN", - Self::RrSignalDisconnected => "RR_SIGNAL_DISCONNECTED", - Self::RrPublisherFailed => "RR_PUBLISHER_FAILED", - Self::RrSubscriberFailed => "RR_SUBSCRIBER_FAILED", - Self::RrSwitchCandidate => "RR_SWITCH_CANDIDATE", + ReconnectReason::RrUnknown => "RR_UNKNOWN", + ReconnectReason::RrSignalDisconnected => "RR_SIGNAL_DISCONNECTED", + ReconnectReason::RrPublisherFailed => "RR_PUBLISHER_FAILED", + ReconnectReason::RrSubscriberFailed => "RR_SUBSCRIBER_FAILED", + ReconnectReason::RrSwitchCandidate => "RR_SWITCH_CANDIDATE", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1136,9 +1166,9 @@ impl SubscriptionError { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::SeUnknown => "SE_UNKNOWN", - Self::SeCodecUnsupported => "SE_CODEC_UNSUPPORTED", - Self::SeTrackNotfound => "SE_TRACK_NOTFOUND", + SubscriptionError::SeUnknown => "SE_UNKNOWN", + SubscriptionError::SeCodecUnsupported => "SE_CODEC_UNSUPPORTED", + SubscriptionError::SeTrackNotfound => "SE_TRACK_NOTFOUND", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1168,12 +1198,12 @@ impl AudioTrackFeature { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::TfStereo => "TF_STEREO", - Self::TfNoDtx => "TF_NO_DTX", - Self::TfAutoGainControl => "TF_AUTO_GAIN_CONTROL", - Self::TfEchoCancellation => "TF_ECHO_CANCELLATION", - Self::TfNoiseSuppression => "TF_NOISE_SUPPRESSION", - Self::TfEnhancedNoiseCancellation => "TF_ENHANCED_NOISE_CANCELLATION", + AudioTrackFeature::TfStereo => "TF_STEREO", + AudioTrackFeature::TfNoDtx => "TF_NO_DTX", + AudioTrackFeature::TfAutoGainControl => "TF_AUTO_GAIN_CONTROL", + AudioTrackFeature::TfEchoCancellation => "TF_ECHO_CANCELLATION", + AudioTrackFeature::TfNoiseSuppression => "TF_NOISE_SUPPRESSION", + AudioTrackFeature::TfEnhancedNoiseCancellation => "TF_ENHANCED_NOISE_CANCELLATION", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1190,6 +1220,7 @@ impl AudioTrackFeature { } } /// composite using a web browser +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RoomCompositeEgressRequest { /// required @@ -1224,7 +1255,8 @@ pub struct RoomCompositeEgressRequest { /// Nested message and enum types in `RoomCompositeEgressRequest`. pub mod room_composite_egress_request { /// deprecated (use _output fields) - #[derive(Clone, PartialEq, ::prost::Oneof)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Output { #[prost(message, tag="6")] File(super::EncodedFileOutput), @@ -1233,7 +1265,8 @@ pub mod room_composite_egress_request { #[prost(message, tag="10")] Segments(super::SegmentedFileOutput), } - #[derive(Clone, Copy, PartialEq, ::prost::Oneof)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Options { /// (default H264_720P_30) #[prost(enumeration="super::EncodingOptionsPreset", tag="8")] @@ -1244,6 +1277,7 @@ pub mod room_composite_egress_request { } } /// record any website +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct WebEgressRequest { #[prost(string, tag="1")] @@ -1271,7 +1305,8 @@ pub struct WebEgressRequest { /// Nested message and enum types in `WebEgressRequest`. pub mod web_egress_request { /// deprecated (use _output fields) - #[derive(Clone, PartialEq, ::prost::Oneof)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Output { #[prost(message, tag="4")] File(super::EncodedFileOutput), @@ -1280,7 +1315,8 @@ pub mod web_egress_request { #[prost(message, tag="6")] Segments(super::SegmentedFileOutput), } - #[derive(Clone, Copy, PartialEq, ::prost::Oneof)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Options { #[prost(enumeration="super::EncodingOptionsPreset", tag="7")] Preset(i32), @@ -1289,6 +1325,7 @@ pub mod web_egress_request { } } /// record audio and video from a single participant +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ParticipantEgressRequest { /// required @@ -1313,7 +1350,8 @@ pub struct ParticipantEgressRequest { } /// Nested message and enum types in `ParticipantEgressRequest`. pub mod participant_egress_request { - #[derive(Clone, Copy, PartialEq, ::prost::Oneof)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Options { /// (default H264_720P_30) #[prost(enumeration="super::EncodingOptionsPreset", tag="4")] @@ -1324,6 +1362,7 @@ pub mod participant_egress_request { } } /// containerize up to one audio and one video track +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackCompositeEgressRequest { /// required @@ -1352,7 +1391,8 @@ pub struct TrackCompositeEgressRequest { /// Nested message and enum types in `TrackCompositeEgressRequest`. pub mod track_composite_egress_request { /// deprecated (use _output fields) - #[derive(Clone, PartialEq, ::prost::Oneof)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Output { #[prost(message, tag="4")] File(super::EncodedFileOutput), @@ -1361,7 +1401,8 @@ pub mod track_composite_egress_request { #[prost(message, tag="8")] Segments(super::SegmentedFileOutput), } - #[derive(Clone, Copy, PartialEq, ::prost::Oneof)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Options { /// (default H264_720P_30) #[prost(enumeration="super::EncodingOptionsPreset", tag="6")] @@ -1372,6 +1413,7 @@ pub mod track_composite_egress_request { } } /// record tracks individually, without transcoding +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackEgressRequest { /// required @@ -1387,7 +1429,8 @@ pub struct TrackEgressRequest { /// Nested message and enum types in `TrackEgressRequest`. pub mod track_egress_request { /// required - #[derive(Clone, PartialEq, ::prost::Oneof)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Output { #[prost(message, tag="3")] File(super::DirectFileOutput), @@ -1395,6 +1438,7 @@ pub mod track_egress_request { WebsocketUrl(::prost::alloc::string::String), } } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct EncodedFileOutput { /// (optional) @@ -1411,7 +1455,8 @@ pub struct EncodedFileOutput { } /// Nested message and enum types in `EncodedFileOutput`. pub mod encoded_file_output { - #[derive(Clone, PartialEq, ::prost::Oneof)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Output { #[prost(message, tag="3")] S3(super::S3Upload), @@ -1424,6 +1469,7 @@ pub mod encoded_file_output { } } /// Used to generate HLS segments or other kind of segmented output +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SegmentedFileOutput { /// (optional) @@ -1454,7 +1500,8 @@ pub struct SegmentedFileOutput { /// Nested message and enum types in `SegmentedFileOutput`. pub mod segmented_file_output { /// required - #[derive(Clone, PartialEq, ::prost::Oneof)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Output { #[prost(message, tag="5")] S3(super::S3Upload), @@ -1466,6 +1513,7 @@ pub mod segmented_file_output { AliOss(super::AliOssUpload), } } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct DirectFileOutput { /// see egress docs for templating (default {track_id}-{time}) @@ -1479,7 +1527,8 @@ pub struct DirectFileOutput { } /// Nested message and enum types in `DirectFileOutput`. pub mod direct_file_output { - #[derive(Clone, PartialEq, ::prost::Oneof)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Output { #[prost(message, tag="2")] S3(super::S3Upload), @@ -1491,6 +1540,7 @@ pub mod direct_file_output { AliOss(super::AliOssUpload), } } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ImageOutput { /// in seconds (required) @@ -1521,7 +1571,8 @@ pub struct ImageOutput { /// Nested message and enum types in `ImageOutput`. pub mod image_output { /// required - #[derive(Clone, PartialEq, ::prost::Oneof)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Output { #[prost(message, tag="8")] S3(super::S3Upload), @@ -1533,6 +1584,7 @@ pub mod image_output { AliOss(super::AliOssUpload), } } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct S3Upload { #[prost(string, tag="1")] @@ -1559,6 +1611,7 @@ pub struct S3Upload { #[prost(message, optional, tag="10")] pub proxy: ::core::option::Option, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GcpUpload { /// service account credentials serialized in JSON "credentials.json" @@ -1569,6 +1622,7 @@ pub struct GcpUpload { #[prost(message, optional, tag="3")] pub proxy: ::core::option::Option, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct AzureBlobUpload { #[prost(string, tag="1")] @@ -1578,6 +1632,7 @@ pub struct AzureBlobUpload { #[prost(string, tag="3")] pub container_name: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct AliOssUpload { #[prost(string, tag="1")] @@ -1591,6 +1646,7 @@ pub struct AliOssUpload { #[prost(string, tag="5")] pub bucket: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ProxyConfig { #[prost(string, tag="1")] @@ -1600,6 +1656,7 @@ pub struct ProxyConfig { #[prost(string, tag="3")] pub password: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct StreamOutput { /// required @@ -1609,7 +1666,8 @@ pub struct StreamOutput { #[prost(string, repeated, tag="2")] pub urls: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct EncodingOptions { /// (default 1920) #[prost(int32, tag="1")] @@ -1648,6 +1706,7 @@ pub struct EncodingOptions { #[prost(double, tag="10")] pub key_frame_interval: f64, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct UpdateLayoutRequest { #[prost(string, tag="1")] @@ -1655,6 +1714,7 @@ pub struct UpdateLayoutRequest { #[prost(string, tag="2")] pub layout: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct UpdateStreamRequest { #[prost(string, tag="1")] @@ -1664,6 +1724,7 @@ pub struct UpdateStreamRequest { #[prost(string, repeated, tag="3")] pub remove_output_urls: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ListEgressRequest { /// (optional, filter by room name) @@ -1676,16 +1737,19 @@ pub struct ListEgressRequest { #[prost(bool, tag="3")] pub active: bool, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ListEgressResponse { #[prost(message, repeated, tag="1")] pub items: ::prost::alloc::vec::Vec, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct StopEgressRequest { #[prost(string, tag="1")] pub egress_id: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct EgressInfo { #[prost(string, tag="1")] @@ -1724,7 +1788,8 @@ pub struct EgressInfo { } /// Nested message and enum types in `EgressInfo`. pub mod egress_info { - #[derive(Clone, PartialEq, ::prost::Oneof)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Request { #[prost(message, tag="4")] RoomComposite(super::RoomCompositeEgressRequest), @@ -1738,7 +1803,8 @@ pub mod egress_info { Track(super::TrackEgressRequest), } /// deprecated (use _result fields) - #[derive(Clone, PartialEq, ::prost::Oneof)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Result { #[prost(message, tag="7")] Stream(super::StreamInfoList), @@ -1748,11 +1814,13 @@ pub mod egress_info { Segments(super::SegmentsInfo), } } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct StreamInfoList { #[prost(message, repeated, tag="1")] pub info: ::prost::alloc::vec::Vec, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct StreamInfo { #[prost(string, tag="1")] @@ -1784,9 +1852,9 @@ pub mod stream_info { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::Active => "ACTIVE", - Self::Finished => "FINISHED", - Self::Failed => "FAILED", + Status::Active => "ACTIVE", + Status::Finished => "FINISHED", + Status::Failed => "FAILED", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1800,6 +1868,7 @@ pub mod stream_info { } } } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FileInfo { #[prost(string, tag="1")] @@ -1815,6 +1884,7 @@ pub struct FileInfo { #[prost(string, tag="5")] pub location: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SegmentsInfo { #[prost(string, tag="1")] @@ -1836,6 +1906,7 @@ pub struct SegmentsInfo { #[prost(int64, tag="7")] pub ended_at: i64, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ImagesInfo { #[prost(string, tag="4")] @@ -1847,6 +1918,7 @@ pub struct ImagesInfo { #[prost(int64, tag="3")] pub ended_at: i64, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct AutoParticipantEgress { #[prost(message, repeated, tag="3")] @@ -1858,7 +1930,8 @@ pub struct AutoParticipantEgress { } /// Nested message and enum types in `AutoParticipantEgress`. pub mod auto_participant_egress { - #[derive(Clone, Copy, PartialEq, ::prost::Oneof)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Options { /// (default H264_720P_30) #[prost(enumeration="super::EncodingOptionsPreset", tag="1")] @@ -1868,6 +1941,7 @@ pub mod auto_participant_egress { Advanced(super::EncodingOptions), } } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct AutoTrackEgress { /// see docs for templating (default {track_id}-{time}) @@ -1881,7 +1955,8 @@ pub struct AutoTrackEgress { } /// Nested message and enum types in `AutoTrackEgress`. pub mod auto_track_egress { - #[derive(Clone, PartialEq, ::prost::Oneof)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Output { #[prost(message, tag="2")] S3(super::S3Upload), @@ -1908,9 +1983,9 @@ impl EncodedFileType { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::DefaultFiletype => "DEFAULT_FILETYPE", - Self::Mp4 => "MP4", - Self::Ogg => "OGG", + EncodedFileType::DefaultFiletype => "DEFAULT_FILETYPE", + EncodedFileType::Mp4 => "MP4", + EncodedFileType::Ogg => "OGG", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1936,8 +2011,8 @@ impl SegmentedFileProtocol { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::DefaultSegmentedFileProtocol => "DEFAULT_SEGMENTED_FILE_PROTOCOL", - Self::HlsProtocol => "HLS_PROTOCOL", + SegmentedFileProtocol::DefaultSegmentedFileProtocol => "DEFAULT_SEGMENTED_FILE_PROTOCOL", + SegmentedFileProtocol::HlsProtocol => "HLS_PROTOCOL", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1962,8 +2037,8 @@ impl SegmentedFileSuffix { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::Index => "INDEX", - Self::Timestamp => "TIMESTAMP", + SegmentedFileSuffix::Index => "INDEX", + SegmentedFileSuffix::Timestamp => "TIMESTAMP", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1988,8 +2063,8 @@ impl ImageFileSuffix { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::ImageSuffixIndex => "IMAGE_SUFFIX_INDEX", - Self::ImageSuffixTimestamp => "IMAGE_SUFFIX_TIMESTAMP", + ImageFileSuffix::ImageSuffixIndex => "IMAGE_SUFFIX_INDEX", + ImageFileSuffix::ImageSuffixTimestamp => "IMAGE_SUFFIX_TIMESTAMP", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -2016,9 +2091,9 @@ impl StreamProtocol { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::DefaultProtocol => "DEFAULT_PROTOCOL", - Self::Rtmp => "RTMP", - Self::Srt => "SRT", + StreamProtocol::DefaultProtocol => "DEFAULT_PROTOCOL", + StreamProtocol::Rtmp => "RTMP", + StreamProtocol::Srt => "SRT", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -2058,14 +2133,14 @@ impl EncodingOptionsPreset { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::H264720p30 => "H264_720P_30", - Self::H264720p60 => "H264_720P_60", - Self::H2641080p30 => "H264_1080P_30", - Self::H2641080p60 => "H264_1080P_60", - Self::PortraitH264720p30 => "PORTRAIT_H264_720P_30", - Self::PortraitH264720p60 => "PORTRAIT_H264_720P_60", - Self::PortraitH2641080p30 => "PORTRAIT_H264_1080P_30", - Self::PortraitH2641080p60 => "PORTRAIT_H264_1080P_60", + EncodingOptionsPreset::H264720p30 => "H264_720P_30", + EncodingOptionsPreset::H264720p60 => "H264_720P_60", + EncodingOptionsPreset::H2641080p30 => "H264_1080P_30", + EncodingOptionsPreset::H2641080p60 => "H264_1080P_60", + EncodingOptionsPreset::PortraitH264720p30 => "PORTRAIT_H264_720P_30", + EncodingOptionsPreset::PortraitH264720p60 => "PORTRAIT_H264_720P_60", + EncodingOptionsPreset::PortraitH2641080p30 => "PORTRAIT_H264_1080P_30", + EncodingOptionsPreset::PortraitH2641080p60 => "PORTRAIT_H264_1080P_60", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -2101,13 +2176,13 @@ impl EgressStatus { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::EgressStarting => "EGRESS_STARTING", - Self::EgressActive => "EGRESS_ACTIVE", - Self::EgressEnding => "EGRESS_ENDING", - Self::EgressComplete => "EGRESS_COMPLETE", - Self::EgressFailed => "EGRESS_FAILED", - Self::EgressAborted => "EGRESS_ABORTED", - Self::EgressLimitReached => "EGRESS_LIMIT_REACHED", + EgressStatus::EgressStarting => "EGRESS_STARTING", + EgressStatus::EgressActive => "EGRESS_ACTIVE", + EgressStatus::EgressEnding => "EGRESS_ENDING", + EgressStatus::EgressComplete => "EGRESS_COMPLETE", + EgressStatus::EgressFailed => "EGRESS_FAILED", + EgressStatus::EgressAborted => "EGRESS_ABORTED", + EgressStatus::EgressLimitReached => "EGRESS_LIMIT_REACHED", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -2124,6 +2199,7 @@ impl EgressStatus { } } } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SignalRequest { #[prost(oneof="signal_request::Message", tags="1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17, 18")] @@ -2131,7 +2207,8 @@ pub struct SignalRequest { } /// Nested message and enum types in `SignalRequest`. pub mod signal_request { - #[derive(Clone, PartialEq, ::prost::Oneof)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Message { /// initial join exchange, for publisher #[prost(message, tag="1")] @@ -2186,6 +2263,7 @@ pub mod signal_request { UpdateVideoTrack(super::UpdateLocalVideoTrack), } } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SignalResponse { #[prost(oneof="signal_response::Message", tags="1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23")] @@ -2193,7 +2271,8 @@ pub struct SignalResponse { } /// Nested message and enum types in `SignalResponse`. pub mod signal_response { - #[derive(Clone, PartialEq, ::prost::Oneof)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Message { /// sent when join is accepted #[prost(message, tag="1")] @@ -2266,6 +2345,7 @@ pub mod signal_response { TrackSubscribed(super::TrackSubscribed), } } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SimulcastCodec { #[prost(string, tag="1")] @@ -2273,6 +2353,7 @@ pub struct SimulcastCodec { #[prost(string, tag="2")] pub cid: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct AddTrackRequest { /// client ID of track, to match it when RTC track is received @@ -2314,6 +2395,7 @@ pub struct AddTrackRequest { #[prost(string, tag="15")] pub stream: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrickleRequest { #[prost(string, tag="1")] @@ -2323,6 +2405,7 @@ pub struct TrickleRequest { #[prost(bool, tag="3")] pub r#final: bool, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct MuteTrackRequest { #[prost(string, tag="1")] @@ -2330,6 +2413,7 @@ pub struct MuteTrackRequest { #[prost(bool, tag="2")] pub muted: bool, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct JoinResponse { #[prost(message, optional, tag="1")] @@ -2365,6 +2449,7 @@ pub struct JoinResponse { #[prost(bytes="vec", tag="13")] pub sif_trailer: ::prost::alloc::vec::Vec, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ReconnectResponse { #[prost(message, repeated, tag="1")] @@ -2372,6 +2457,7 @@ pub struct ReconnectResponse { #[prost(message, optional, tag="2")] pub client_configuration: ::core::option::Option, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackPublishedResponse { #[prost(string, tag="1")] @@ -2379,11 +2465,13 @@ pub struct TrackPublishedResponse { #[prost(message, optional, tag="2")] pub track: ::core::option::Option, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackUnpublishedResponse { #[prost(string, tag="1")] pub track_sid: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SessionDescription { /// "answer" | "offer" | "pranswer" | "rollback" @@ -2392,11 +2480,13 @@ pub struct SessionDescription { #[prost(string, tag="2")] pub sdp: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ParticipantUpdate { #[prost(message, repeated, tag="1")] pub participants: ::prost::alloc::vec::Vec, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct UpdateSubscription { #[prost(string, repeated, tag="1")] @@ -2406,6 +2496,7 @@ pub struct UpdateSubscription { #[prost(message, repeated, tag="3")] pub participant_tracks: ::prost::alloc::vec::Vec, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct UpdateTrackSettings { #[prost(string, repeated, tag="1")] @@ -2434,6 +2525,7 @@ pub struct UpdateTrackSettings { #[prost(uint32, tag="8")] pub priority: u32, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct UpdateLocalAudioTrack { #[prost(string, tag="1")] @@ -2441,6 +2533,7 @@ pub struct UpdateLocalAudioTrack { #[prost(enumeration="AudioTrackFeature", repeated, tag="2")] pub features: ::prost::alloc::vec::Vec, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct UpdateLocalVideoTrack { #[prost(string, tag="1")] @@ -2450,6 +2543,7 @@ pub struct UpdateLocalVideoTrack { #[prost(uint32, tag="3")] pub height: u32, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct LeaveRequest { /// sent when server initiates the disconnect due to server-restart @@ -2484,9 +2578,9 @@ pub mod leave_request { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::Disconnect => "DISCONNECT", - Self::Resume => "RESUME", - Self::Reconnect => "RECONNECT", + Action::Disconnect => "DISCONNECT", + Action::Resume => "RESUME", + Action::Reconnect => "RECONNECT", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -2501,6 +2595,7 @@ pub mod leave_request { } } /// message to indicate published video track dimensions are changing +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct UpdateVideoLayers { #[prost(string, tag="1")] @@ -2508,6 +2603,7 @@ pub struct UpdateVideoLayers { #[prost(message, repeated, tag="2")] pub layers: ::prost::alloc::vec::Vec, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct UpdateParticipantMetadata { #[prost(string, tag="1")] @@ -2521,6 +2617,7 @@ pub struct UpdateParticipantMetadata { #[prost(uint32, tag="4")] pub request_id: u32, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct IceServer { #[prost(string, repeated, tag="1")] @@ -2530,16 +2627,19 @@ pub struct IceServer { #[prost(string, tag="3")] pub credential: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SpeakersChanged { #[prost(message, repeated, tag="1")] pub speakers: ::prost::alloc::vec::Vec, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RoomUpdate { #[prost(message, optional, tag="1")] pub room: ::core::option::Option, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ConnectionQualityInfo { #[prost(string, tag="1")] @@ -2549,11 +2649,13 @@ pub struct ConnectionQualityInfo { #[prost(float, tag="3")] pub score: f32, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ConnectionQualityUpdate { #[prost(message, repeated, tag="1")] pub updates: ::prost::alloc::vec::Vec, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct StreamStateInfo { #[prost(string, tag="1")] @@ -2563,18 +2665,21 @@ pub struct StreamStateInfo { #[prost(enumeration="StreamState", tag="3")] pub state: i32, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct StreamStateUpdate { #[prost(message, repeated, tag="1")] pub stream_states: ::prost::alloc::vec::Vec, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct SubscribedQuality { #[prost(enumeration="VideoQuality", tag="1")] pub quality: i32, #[prost(bool, tag="2")] pub enabled: bool, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SubscribedCodec { #[prost(string, tag="1")] @@ -2582,6 +2687,7 @@ pub struct SubscribedCodec { #[prost(message, repeated, tag="2")] pub qualities: ::prost::alloc::vec::Vec, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SubscribedQualityUpdate { #[prost(string, tag="1")] @@ -2591,6 +2697,7 @@ pub struct SubscribedQualityUpdate { #[prost(message, repeated, tag="3")] pub subscribed_codecs: ::prost::alloc::vec::Vec, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackPermission { /// permission could be granted either by participant sid or identity @@ -2603,6 +2710,7 @@ pub struct TrackPermission { #[prost(string, tag="4")] pub participant_identity: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SubscriptionPermission { #[prost(bool, tag="1")] @@ -2610,6 +2718,7 @@ pub struct SubscriptionPermission { #[prost(message, repeated, tag="2")] pub track_permissions: ::prost::alloc::vec::Vec, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SubscriptionPermissionUpdate { #[prost(string, tag="1")] @@ -2619,6 +2728,7 @@ pub struct SubscriptionPermissionUpdate { #[prost(bool, tag="3")] pub allowed: bool, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SyncState { /// last subscribe answer before reconnecting @@ -2636,6 +2746,7 @@ pub struct SyncState { #[prost(string, repeated, tag="6")] pub track_sids_disabled: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct DataChannelInfo { #[prost(string, tag="1")] @@ -2645,14 +2756,16 @@ pub struct DataChannelInfo { #[prost(enumeration="SignalTarget", tag="3")] pub target: i32, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct SimulateScenario { #[prost(oneof="simulate_scenario::Scenario", tags="1, 2, 3, 4, 5, 6, 7, 8, 9")] pub scenario: ::core::option::Option, } /// Nested message and enum types in `SimulateScenario`. pub mod simulate_scenario { - #[derive(Clone, Copy, PartialEq, ::prost::Oneof)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Scenario { /// simulate N seconds of speaker activity #[prost(int32, tag="1")] @@ -2684,7 +2797,8 @@ pub mod simulate_scenario { LeaveRequestFullReconnect(bool), } } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct Ping { #[prost(int64, tag="1")] pub timestamp: i64, @@ -2692,7 +2806,8 @@ pub struct Ping { #[prost(int64, tag="2")] pub rtt: i64, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct Pong { /// timestamp field of last received ping request #[prost(int64, tag="1")] @@ -2700,11 +2815,13 @@ pub struct Pong { #[prost(int64, tag="2")] pub timestamp: i64, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RegionSettings { #[prost(message, repeated, tag="1")] pub regions: ::prost::alloc::vec::Vec, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RegionInfo { #[prost(string, tag="1")] @@ -2714,6 +2831,7 @@ pub struct RegionInfo { #[prost(int64, tag="3")] pub distance: i64, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SubscriptionResponse { #[prost(string, tag="1")] @@ -2721,6 +2839,7 @@ pub struct SubscriptionResponse { #[prost(enumeration="SubscriptionError", tag="2")] pub err: i32, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RequestResponse { #[prost(uint32, tag="1")] @@ -2747,10 +2866,10 @@ pub mod request_response { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::Ok => "OK", - Self::NotFound => "NOT_FOUND", - Self::NotAllowed => "NOT_ALLOWED", - Self::LimitExceeded => "LIMIT_EXCEEDED", + Reason::Ok => "OK", + Reason::NotFound => "NOT_FOUND", + Reason::NotAllowed => "NOT_ALLOWED", + Reason::LimitExceeded => "LIMIT_EXCEEDED", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -2765,6 +2884,7 @@ pub mod request_response { } } } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackSubscribed { #[prost(string, tag="1")] @@ -2783,8 +2903,8 @@ impl SignalTarget { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::Publisher => "PUBLISHER", - Self::Subscriber => "SUBSCRIBER", + SignalTarget::Publisher => "PUBLISHER", + SignalTarget::Subscriber => "SUBSCRIBER", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -2809,8 +2929,8 @@ impl StreamState { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::Active => "ACTIVE", - Self::Paused => "PAUSED", + StreamState::Active => "ACTIVE", + StreamState::Paused => "PAUSED", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -2836,9 +2956,9 @@ impl CandidateProtocol { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::Udp => "UDP", - Self::Tcp => "TCP", - Self::Tls => "TLS", + CandidateProtocol::Udp => "UDP", + CandidateProtocol::Tcp => "TCP", + CandidateProtocol::Tls => "TLS", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -2851,6 +2971,7 @@ impl CandidateProtocol { } } } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct Job { #[prost(string, tag="1")] @@ -2873,6 +2994,7 @@ pub struct Job { #[prost(message, optional, tag="8")] pub state: ::core::option::Option, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct JobState { #[prost(enumeration="JobStatus", tag="1")] @@ -2889,6 +3011,7 @@ pub struct JobState { pub participant_identity: ::prost::alloc::string::String, } /// from Worker to Server +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct WorkerMessage { #[prost(oneof="worker_message::Message", tags="1, 2, 3, 4, 5, 6, 7")] @@ -2896,7 +3019,8 @@ pub struct WorkerMessage { } /// Nested message and enum types in `WorkerMessage`. pub mod worker_message { - #[derive(Clone, PartialEq, ::prost::Oneof)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Message { /// agent workers need to register themselves with the server first #[prost(message, tag="1")] @@ -2919,6 +3043,7 @@ pub mod worker_message { } } /// from Server to Worker +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ServerMessage { #[prost(oneof="server_message::Message", tags="1, 2, 3, 5, 4")] @@ -2926,7 +3051,8 @@ pub struct ServerMessage { } /// Nested message and enum types in `ServerMessage`. pub mod server_message { - #[derive(Clone, PartialEq, ::prost::Oneof)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Message { /// server confirms the registration, from this moment on, the worker is considered active #[prost(message, tag="1")] @@ -2942,6 +3068,7 @@ pub mod server_message { Pong(super::WorkerPong), } } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SimulateJobRequest { #[prost(enumeration="JobType", tag="1")] @@ -2951,18 +3078,21 @@ pub struct SimulateJobRequest { #[prost(message, optional, tag="3")] pub participant: ::core::option::Option, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct WorkerPing { #[prost(int64, tag="1")] pub timestamp: i64, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct WorkerPong { #[prost(int64, tag="1")] pub last_timestamp: i64, #[prost(int64, tag="2")] pub timestamp: i64, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RegisterWorkerRequest { #[prost(enumeration="JobType", tag="1")] @@ -2980,6 +3110,7 @@ pub struct RegisterWorkerRequest { #[prost(message, optional, tag="7")] pub allowed_permissions: ::core::option::Option, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RegisterWorkerResponse { #[prost(string, tag="1")] @@ -2987,12 +3118,14 @@ pub struct RegisterWorkerResponse { #[prost(message, optional, tag="3")] pub server_info: ::core::option::Option, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct MigrateJobRequest { /// string job_id = 1 \[deprecated = true\]; #[prost(string, repeated, tag="2")] pub job_ids: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct AvailabilityRequest { #[prost(message, optional, tag="1")] @@ -3002,6 +3135,7 @@ pub struct AvailabilityRequest { #[prost(bool, tag="2")] pub resuming: bool, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct AvailabilityResponse { #[prost(string, tag="1")] @@ -3019,6 +3153,7 @@ pub struct AvailabilityResponse { #[prost(map="string, string", tag="7")] pub participant_attributes: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct UpdateJobStatus { #[prost(string, tag="1")] @@ -3030,7 +3165,8 @@ pub struct UpdateJobStatus { #[prost(string, tag="3")] pub error: ::prost::alloc::string::String, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct UpdateWorkerStatus { #[prost(enumeration="WorkerStatus", optional, tag="1")] pub status: ::core::option::Option, @@ -3040,6 +3176,7 @@ pub struct UpdateWorkerStatus { #[prost(int32, tag="4")] pub job_count: i32, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct JobAssignment { #[prost(message, optional, tag="1")] @@ -3049,6 +3186,7 @@ pub struct JobAssignment { #[prost(string, tag="3")] pub token: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct JobTermination { #[prost(string, tag="1")] @@ -3067,8 +3205,8 @@ impl JobType { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::JtRoom => "JT_ROOM", - Self::JtPublisher => "JT_PUBLISHER", + JobType::JtRoom => "JT_ROOM", + JobType::JtPublisher => "JT_PUBLISHER", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -3093,8 +3231,8 @@ impl WorkerStatus { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::WsAvailable => "WS_AVAILABLE", - Self::WsFull => "WS_FULL", + WorkerStatus::WsAvailable => "WS_AVAILABLE", + WorkerStatus::WsFull => "WS_FULL", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -3121,10 +3259,10 @@ impl JobStatus { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::JsPending => "JS_PENDING", - Self::JsRunning => "JS_RUNNING", - Self::JsSuccess => "JS_SUCCESS", - Self::JsFailed => "JS_FAILED", + JobStatus::JsPending => "JS_PENDING", + JobStatus::JsRunning => "JS_RUNNING", + JobStatus::JsSuccess => "JS_SUCCESS", + JobStatus::JsFailed => "JS_FAILED", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -3138,6 +3276,7 @@ impl JobStatus { } } } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CreateAgentDispatchRequest { #[prost(string, tag="1")] @@ -3147,6 +3286,7 @@ pub struct CreateAgentDispatchRequest { #[prost(string, tag="3")] pub metadata: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RoomAgentDispatch { #[prost(string, tag="1")] @@ -3154,6 +3294,7 @@ pub struct RoomAgentDispatch { #[prost(string, tag="2")] pub metadata: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct DeleteAgentDispatchRequest { #[prost(string, tag="1")] @@ -3161,6 +3302,7 @@ pub struct DeleteAgentDispatchRequest { #[prost(string, tag="2")] pub room: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ListAgentDispatchRequest { /// if set, only the dispatch whose id is given will be returned @@ -3170,11 +3312,13 @@ pub struct ListAgentDispatchRequest { #[prost(string, tag="2")] pub room: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ListAgentDispatchResponse { #[prost(message, repeated, tag="1")] pub agent_dispatches: ::prost::alloc::vec::Vec, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct AgentDispatch { #[prost(string, tag="1")] @@ -3188,6 +3332,7 @@ pub struct AgentDispatch { #[prost(message, optional, tag="5")] pub state: ::core::option::Option, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct AgentDispatchState { /// For dispatches of tyoe JT_ROOM, there will be at most 1 job. @@ -3199,6 +3344,7 @@ pub struct AgentDispatchState { #[prost(int64, tag="3")] pub deleted_at: i64, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CreateRoomRequest { /// name of the room @@ -3243,6 +3389,7 @@ pub struct CreateRoomRequest { #[prost(bool, tag="13")] pub replay_enabled: bool, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RoomEgress { #[prost(message, optional, tag="1")] @@ -3252,42 +3399,50 @@ pub struct RoomEgress { #[prost(message, optional, tag="2")] pub tracks: ::core::option::Option, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RoomAgent { #[prost(message, repeated, tag="1")] pub dispatches: ::prost::alloc::vec::Vec, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ListRoomsRequest { /// when set, will only return rooms with name match #[prost(string, repeated, tag="1")] pub names: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ListRoomsResponse { #[prost(message, repeated, tag="1")] pub rooms: ::prost::alloc::vec::Vec, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct DeleteRoomRequest { /// name of the room #[prost(string, tag="1")] pub room: ::prost::alloc::string::String, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct DeleteRoomResponse { } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ListParticipantsRequest { /// name of the room #[prost(string, tag="1")] pub room: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ListParticipantsResponse { #[prost(message, repeated, tag="1")] pub participants: ::prost::alloc::vec::Vec, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RoomParticipantIdentity { /// name of the room @@ -3297,9 +3452,11 @@ pub struct RoomParticipantIdentity { #[prost(string, tag="2")] pub identity: ::prost::alloc::string::String, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct RemoveParticipantResponse { } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct MuteRoomTrackRequest { /// name of the room @@ -3314,11 +3471,13 @@ pub struct MuteRoomTrackRequest { #[prost(bool, tag="4")] pub muted: bool, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct MuteRoomTrackResponse { #[prost(message, optional, tag="1")] pub track: ::core::option::Option, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct UpdateParticipantRequest { #[prost(string, tag="1")] @@ -3339,6 +3498,7 @@ pub struct UpdateParticipantRequest { #[prost(map="string, string", tag="6")] pub attributes: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct UpdateSubscriptionsRequest { #[prost(string, tag="1")] @@ -3356,9 +3516,11 @@ pub struct UpdateSubscriptionsRequest { pub participant_tracks: ::prost::alloc::vec::Vec, } /// empty for now -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct UpdateSubscriptionsResponse { } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SendDataRequest { #[prost(string, tag="1")] @@ -3378,9 +3540,11 @@ pub struct SendDataRequest { pub topic: ::core::option::Option<::prost::alloc::string::String>, } /// -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct SendDataResponse { } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct UpdateRoomMetadataRequest { #[prost(string, tag="1")] @@ -3389,6 +3553,7 @@ pub struct UpdateRoomMetadataRequest { #[prost(string, tag="2")] pub metadata: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RoomConfiguration { /// Used as ID, must be unique @@ -3419,6 +3584,7 @@ pub struct RoomConfiguration { #[prost(bool, tag="9")] pub sync_streams: bool, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CreateIngressRequest { #[prost(enumeration="IngressInput", tag="1")] @@ -3453,6 +3619,7 @@ pub struct CreateIngressRequest { #[prost(message, optional, tag="7")] pub video: ::core::option::Option, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct IngressAudioOptions { #[prost(string, tag="1")] @@ -3464,7 +3631,8 @@ pub struct IngressAudioOptions { } /// Nested message and enum types in `IngressAudioOptions`. pub mod ingress_audio_options { - #[derive(Clone, Copy, PartialEq, ::prost::Oneof)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] pub enum EncodingOptions { #[prost(enumeration="super::IngressAudioEncodingPreset", tag="3")] Preset(i32), @@ -3472,6 +3640,7 @@ pub mod ingress_audio_options { Options(super::IngressAudioEncodingOptions), } } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct IngressVideoOptions { #[prost(string, tag="1")] @@ -3483,7 +3652,8 @@ pub struct IngressVideoOptions { } /// Nested message and enum types in `IngressVideoOptions`. pub mod ingress_video_options { - #[derive(Clone, PartialEq, ::prost::Oneof)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] pub enum EncodingOptions { #[prost(enumeration="super::IngressVideoEncodingPreset", tag="3")] Preset(i32), @@ -3491,7 +3661,8 @@ pub mod ingress_video_options { Options(super::IngressVideoEncodingOptions), } } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct IngressAudioEncodingOptions { /// desired audio codec to publish to room #[prost(enumeration="AudioCodec", tag="1")] @@ -3503,6 +3674,7 @@ pub struct IngressAudioEncodingOptions { #[prost(uint32, tag="4")] pub channels: u32, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct IngressVideoEncodingOptions { /// desired codec to publish to room @@ -3514,6 +3686,7 @@ pub struct IngressVideoEncodingOptions { #[prost(message, repeated, tag="3")] pub layers: ::prost::alloc::vec::Vec, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct IngressInfo { #[prost(string, tag="1")] @@ -3553,6 +3726,7 @@ pub struct IngressInfo { #[prost(message, optional, tag="12")] pub state: ::core::option::Option, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct IngressState { #[prost(enumeration="ingress_state::Status", tag="1")] @@ -3596,11 +3770,11 @@ pub mod ingress_state { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::EndpointInactive => "ENDPOINT_INACTIVE", - Self::EndpointBuffering => "ENDPOINT_BUFFERING", - Self::EndpointPublishing => "ENDPOINT_PUBLISHING", - Self::EndpointError => "ENDPOINT_ERROR", - Self::EndpointComplete => "ENDPOINT_COMPLETE", + Status::EndpointInactive => "ENDPOINT_INACTIVE", + Status::EndpointBuffering => "ENDPOINT_BUFFERING", + Status::EndpointPublishing => "ENDPOINT_PUBLISHING", + Status::EndpointError => "ENDPOINT_ERROR", + Status::EndpointComplete => "ENDPOINT_COMPLETE", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -3616,6 +3790,7 @@ pub mod ingress_state { } } } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct InputVideoState { #[prost(string, tag="1")] @@ -3629,6 +3804,7 @@ pub struct InputVideoState { #[prost(double, tag="5")] pub framerate: f64, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct InputAudioState { #[prost(string, tag="1")] @@ -3640,6 +3816,7 @@ pub struct InputAudioState { #[prost(uint32, tag="4")] pub sample_rate: u32, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct UpdateIngressRequest { #[prost(string, tag="1")] @@ -3664,6 +3841,7 @@ pub struct UpdateIngressRequest { #[prost(message, optional, tag="7")] pub video: ::core::option::Option, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ListIngressRequest { /// when blank, lists all ingress endpoints @@ -3675,11 +3853,13 @@ pub struct ListIngressRequest { #[prost(string, tag="2")] pub ingress_id: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ListIngressResponse { #[prost(message, repeated, tag="1")] pub items: ::prost::alloc::vec::Vec, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct DeleteIngressRequest { #[prost(string, tag="1")] @@ -3700,9 +3880,9 @@ impl IngressInput { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::RtmpInput => "RTMP_INPUT", - Self::WhipInput => "WHIP_INPUT", - Self::UrlInput => "URL_INPUT", + IngressInput::RtmpInput => "RTMP_INPUT", + IngressInput::WhipInput => "WHIP_INPUT", + IngressInput::UrlInput => "URL_INPUT", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -3730,8 +3910,8 @@ impl IngressAudioEncodingPreset { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::OpusStereo96kbps => "OPUS_STEREO_96KBPS", - Self::OpusMono64kbs => "OPUS_MONO_64KBS", + IngressAudioEncodingPreset::OpusStereo96kbps => "OPUS_STEREO_96KBPS", + IngressAudioEncodingPreset::OpusMono64kbs => "OPUS_MONO_64KBS", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -3774,16 +3954,16 @@ impl IngressVideoEncodingPreset { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::H264720p30fps3Layers => "H264_720P_30FPS_3_LAYERS", - Self::H2641080p30fps3Layers => "H264_1080P_30FPS_3_LAYERS", - Self::H264540p25fps2Layers => "H264_540P_25FPS_2_LAYERS", - Self::H264720p30fps1Layer => "H264_720P_30FPS_1_LAYER", - Self::H2641080p30fps1Layer => "H264_1080P_30FPS_1_LAYER", - Self::H264720p30fps3LayersHighMotion => "H264_720P_30FPS_3_LAYERS_HIGH_MOTION", - Self::H2641080p30fps3LayersHighMotion => "H264_1080P_30FPS_3_LAYERS_HIGH_MOTION", - Self::H264540p25fps2LayersHighMotion => "H264_540P_25FPS_2_LAYERS_HIGH_MOTION", - Self::H264720p30fps1LayerHighMotion => "H264_720P_30FPS_1_LAYER_HIGH_MOTION", - Self::H2641080p30fps1LayerHighMotion => "H264_1080P_30FPS_1_LAYER_HIGH_MOTION", + IngressVideoEncodingPreset::H264720p30fps3Layers => "H264_720P_30FPS_3_LAYERS", + IngressVideoEncodingPreset::H2641080p30fps3Layers => "H264_1080P_30FPS_3_LAYERS", + IngressVideoEncodingPreset::H264540p25fps2Layers => "H264_540P_25FPS_2_LAYERS", + IngressVideoEncodingPreset::H264720p30fps1Layer => "H264_720P_30FPS_1_LAYER", + IngressVideoEncodingPreset::H2641080p30fps1Layer => "H264_1080P_30FPS_1_LAYER", + IngressVideoEncodingPreset::H264720p30fps3LayersHighMotion => "H264_720P_30FPS_3_LAYERS_HIGH_MOTION", + IngressVideoEncodingPreset::H2641080p30fps3LayersHighMotion => "H264_1080P_30FPS_3_LAYERS_HIGH_MOTION", + IngressVideoEncodingPreset::H264540p25fps2LayersHighMotion => "H264_540P_25FPS_2_LAYERS_HIGH_MOTION", + IngressVideoEncodingPreset::H264720p30fps1LayerHighMotion => "H264_720P_30FPS_1_LAYER_HIGH_MOTION", + IngressVideoEncodingPreset::H2641080p30fps1LayerHighMotion => "H264_1080P_30FPS_1_LAYER_HIGH_MOTION", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -3803,6 +3983,7 @@ impl IngressVideoEncodingPreset { } } } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct WebhookEvent { /// one of room_started, room_finished, participant_joined, participant_left, @@ -3833,6 +4014,7 @@ pub struct WebhookEvent { #[prost(int32, tag="11")] pub num_dropped: i32, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CreateSipTrunkRequest { /// CIDR or IPs that traffic is accepted from @@ -3870,6 +4052,7 @@ pub struct CreateSipTrunkRequest { #[prost(string, tag="11")] pub metadata: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SipTrunkInfo { #[prost(string, tag="1")] @@ -3930,9 +4113,9 @@ pub mod sip_trunk_info { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::TrunkLegacy => "TRUNK_LEGACY", - Self::TrunkInbound => "TRUNK_INBOUND", - Self::TrunkOutbound => "TRUNK_OUTBOUND", + TrunkKind::TrunkLegacy => "TRUNK_LEGACY", + TrunkKind::TrunkInbound => "TRUNK_INBOUND", + TrunkKind::TrunkOutbound => "TRUNK_OUTBOUND", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -3946,12 +4129,14 @@ pub mod sip_trunk_info { } } } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CreateSipInboundTrunkRequest { /// Trunk ID is ignored #[prost(message, optional, tag="1")] pub trunk: ::core::option::Option, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SipInboundTrunkInfo { #[prost(string, tag="1")] @@ -3981,12 +4166,14 @@ pub struct SipInboundTrunkInfo { #[prost(string, tag="8")] pub auth_password: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CreateSipOutboundTrunkRequest { /// Trunk ID is ignored #[prost(message, optional, tag="1")] pub trunk: ::core::option::Option, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SipOutboundTrunkInfo { #[prost(string, tag="1")] @@ -4014,35 +4201,43 @@ pub struct SipOutboundTrunkInfo { #[prost(string, tag="8")] pub auth_password: ::prost::alloc::string::String, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct ListSipTrunkRequest { } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ListSipTrunkResponse { #[prost(message, repeated, tag="1")] pub items: ::prost::alloc::vec::Vec, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct ListSipInboundTrunkRequest { } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ListSipInboundTrunkResponse { #[prost(message, repeated, tag="1")] pub items: ::prost::alloc::vec::Vec, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct ListSipOutboundTrunkRequest { } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ListSipOutboundTrunkResponse { #[prost(message, repeated, tag="1")] pub items: ::prost::alloc::vec::Vec, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct DeleteSipTrunkRequest { #[prost(string, tag="1")] pub sip_trunk_id: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SipDispatchRuleDirect { /// What room should call be directed into @@ -4052,6 +4247,7 @@ pub struct SipDispatchRuleDirect { #[prost(string, tag="2")] pub pin: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SipDispatchRuleIndividual { /// Prefix used on new room name @@ -4061,6 +4257,7 @@ pub struct SipDispatchRuleIndividual { #[prost(string, tag="2")] pub pin: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SipDispatchRule { #[prost(oneof="sip_dispatch_rule::Rule", tags="1, 2")] @@ -4068,7 +4265,8 @@ pub struct SipDispatchRule { } /// Nested message and enum types in `SIPDispatchRule`. pub mod sip_dispatch_rule { - #[derive(Clone, PartialEq, ::prost::Oneof)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Rule { /// SIPDispatchRuleDirect is a `SIP Dispatch Rule` that puts a user directly into a room /// This places users into an existing room. Optionally you can require a pin before a user can @@ -4080,6 +4278,7 @@ pub mod sip_dispatch_rule { DispatchRuleIndividual(super::SipDispatchRuleIndividual), } } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CreateSipDispatchRuleRequest { #[prost(message, optional, tag="1")] @@ -4107,6 +4306,7 @@ pub struct CreateSipDispatchRuleRequest { #[prost(map="string, string", tag="7")] pub attributes: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SipDispatchRuleInfo { #[prost(string, tag="1")] @@ -4132,14 +4332,17 @@ pub struct SipDispatchRuleInfo { #[prost(map="string, string", tag="8")] pub attributes: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct ListSipDispatchRuleRequest { } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ListSipDispatchRuleResponse { #[prost(message, repeated, tag="1")] pub items: ::prost::alloc::vec::Vec, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct DeleteSipDispatchRuleRequest { #[prost(string, tag="1")] @@ -4147,6 +4350,7 @@ pub struct DeleteSipDispatchRuleRequest { } /// A SIP Participant is a singular SIP session connected to a LiveKit room via /// a SIP Trunk into a SIP DispatchRule +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CreateSipParticipantRequest { /// What SIP Trunk should be used to dial the user @@ -4182,6 +4386,7 @@ pub struct CreateSipParticipantRequest { #[prost(bool, tag="10")] pub hide_phone_number: bool, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SipParticipantInfo { #[prost(string, tag="1")] @@ -4208,10 +4413,10 @@ impl SipTransport { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::Auto => "SIP_TRANSPORT_AUTO", - Self::Udp => "SIP_TRANSPORT_UDP", - Self::Tcp => "SIP_TRANSPORT_TCP", - Self::Tls => "SIP_TRANSPORT_TLS", + SipTransport::Auto => "SIP_TRANSPORT_AUTO", + SipTransport::Udp => "SIP_TRANSPORT_UDP", + SipTransport::Tcp => "SIP_TRANSPORT_TCP", + SipTransport::Tls => "SIP_TRANSPORT_TLS", } } /// Creates an enum from field names used in the ProtoBuf definition. From fc9a382cc7c00f3267c8b8c57b87b471fffbd213 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Tue, 24 Sep 2024 20:26:15 -0700 Subject: [PATCH 043/274] ffi-v0.10.3 (#443) --- Cargo.lock | 2 +- livekit-ffi/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4cce07499..36351b458 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1591,7 +1591,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.10.2" +version = "0.10.3" dependencies = [ "console-subscriber", "dashmap", diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index b9deb78b0..42c5d36ae 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.10.2" +version = "0.10.3" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" From 3a04728110b981bb1753d1ec458a918a1eb64557 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Thu, 26 Sep 2024 14:04:05 -0700 Subject: [PATCH 044/274] expose soxr on the ffi (#444) --- Cargo.lock | 15 ++ Cargo.toml | 1 + livekit-ffi/Cargo.toml | 1 + livekit-ffi/protocol/audio_frame.proto | 78 +++++++++ livekit-ffi/protocol/ffi.proto | 8 +- livekit-ffi/src/conversion/mod.rs | 1 + livekit-ffi/src/conversion/resampler.rs | 1 + livekit-ffi/src/livekit.proto.rs | 209 +++++++++++++++++++++++- livekit-ffi/src/server/mod.rs | 2 + livekit-ffi/src/server/requests.rs | 110 ++++++++++++- livekit-ffi/src/server/resampler.rs | 139 ++++++++++++++++ soxr-sys/build.rs | 8 +- soxr-sys/generate_bindings.sh | 2 +- soxr-sys/src/lib.rs | 8 +- soxr-sys/src/soxr.rs | 110 +++---------- 15 files changed, 597 insertions(+), 96 deletions(-) create mode 100644 livekit-ffi/src/conversion/resampler.rs create mode 100644 livekit-ffi/src/server/resampler.rs diff --git a/Cargo.lock b/Cargo.lock index 36351b458..c082ba91d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1146,6 +1146,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "hound" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f" + [[package]] name = "http" version = "0.2.11" @@ -1608,6 +1614,7 @@ dependencies = [ "parking_lot", "prost 0.12.3", "prost-types 0.12.3", + "soxr-sys", "thiserror", "tokio", "webrtc-sys-build", @@ -2613,6 +2620,14 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "soxr-sys" +version = "0.1.0" +dependencies = [ + "cc", + "hound", +] + [[package]] name = "spin" version = "0.9.8" diff --git a/Cargo.toml b/Cargo.toml index 507af2249..47c7ce8f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "livekit-protocol", "livekit-ffi", "libwebrtc", + "soxr-sys", "webrtc-sys", "webrtc-sys/build", ] diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 42c5d36ae..13a78560c 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -19,6 +19,7 @@ tracing = ["tokio/tracing", "console-subscriber"] [dependencies] livekit = { path = "../livekit", version = "0.6.0" } +soxr-sys = { path = "../soxr-sys" } livekit-protocol = { path = "../livekit-protocol", version = "0.3.5" } tokio = { version = "1", features = ["full", "parking_lot"] } futures-util = { version = "0.3", default-features = false, features = ["sink"] } diff --git a/livekit-ffi/protocol/audio_frame.proto b/livekit-ffi/protocol/audio_frame.proto index 8307d265d..e5bccc27b 100644 --- a/livekit-ffi/protocol/audio_frame.proto +++ b/livekit-ffi/protocol/audio_frame.proto @@ -87,6 +87,70 @@ message RemixAndResampleResponse { OwnedAudioFrameBuffer buffer = 1; } + +// New resampler using SoX (much better quality) + +message NewSoxResamplerRequest { + double input_rate = 1; + double output_rate = 2; + uint32 num_channels = 3; + SoxResamplerDataType input_data_type = 4; + SoxResamplerDataType output_data_type = 5; + SoxQualityRecipe quality_recipe = 6; + uint32 flags = 7; +} +message NewSoxResamplerResponse { + OwnedSoxResampler resampler = 1; + optional string error = 2; +} + +message PushSoxResamplerRequest { + uint64 resampler_handle = 1; + uint64 data_ptr = 2; // *const i16 + uint32 size = 3; // in bytes +} + +message PushSoxResamplerResponse { + uint64 output_ptr = 1; // *const i16 (could be null) + uint32 size = 2; // in bytes + optional string error = 3; +} + +message FlushSoxResamplerRequest { + uint64 resampler_handle = 1; +} + +message FlushSoxResamplerResponse { + uint64 output_ptr = 1; // *const i16 (could be null) + uint32 size = 2; // in bytes + optional string error = 3; +} + +enum SoxResamplerDataType { + // TODO(theomonnom): support other datatypes (shouldn't really be needed) + SOXR_DATATYPE_INT16I = 0; + SOXR_DATATYPE_INT16S = 1; +} + +enum SoxQualityRecipe { + SOXR_QUALITY_QUICK = 0; + SOXR_QUALITY_LOW = 1; + SOXR_QUALITY_MEDIUM = 2; + SOXR_QUALITY_HIGH = 3; + SOXR_QUALITY_VERYHIGH = 4; +} + +enum SoxFlagBits { + SOXR_ROLLOFF_SMALL = 0; // 1 << 0 + SOXR_ROLLOFF_MEDIUM = 1; // 1 << 1 + SOXR_ROLLOFF_NONE = 2; // 1 << 2 + SOXR_HIGH_PREC_CLOCK = 3; // 1 << 3 + SOXR_DOUBLE_PRECISION = 4; // 1 << 4 + SOXR_VR = 5; // 1 << 5 +} + + + // // AudioFrame buffer // @@ -168,3 +232,17 @@ message OwnedAudioResampler { FfiOwnedHandle handle = 1; AudioResamplerInfo info = 2; } + + + +// +// Sox AudioResampler +// + + +message SoxResamplerInfo {} + +message OwnedSoxResampler { + FfiOwnedHandle handle = 1; + SoxResamplerInfo info = 2; +} diff --git a/livekit-ffi/protocol/ffi.proto b/livekit-ffi/protocol/ffi.proto index 173d36286..5fead41c6 100644 --- a/livekit-ffi/protocol/ffi.proto +++ b/livekit-ffi/protocol/ffi.proto @@ -77,7 +77,7 @@ message FfiRequest { EnableRemoteTrackRequest enable_remote_track = 18; GetStatsRequest get_stats = 19; - // Video +// Video NewVideoStreamRequest new_video_stream = 20; NewVideoSourceRequest new_video_source = 21; CaptureVideoFrameRequest capture_video_frame = 22; @@ -93,6 +93,9 @@ message FfiRequest { RemixAndResampleRequest remix_and_resample = 30; E2eeRequest e2ee = 31; AudioStreamFromParticipantRequest audio_stream_from_participant = 32; + NewSoxResamplerRequest new_sox_resampler = 33; + PushSoxResamplerRequest push_sox_resampler = 34; + FlushSoxResamplerRequest flush_sox_resampler = 35; } } @@ -138,6 +141,9 @@ message FfiResponse { RemixAndResampleResponse remix_and_resample = 30; AudioStreamFromParticipantResponse audio_stream_from_participant = 31; E2eeResponse e2ee = 32; + NewSoxResamplerResponse new_sox_resampler = 33; + PushSoxResamplerResponse push_sox_resampler = 34; + FlushSoxResamplerResponse flush_sox_resampler = 35; } } diff --git a/livekit-ffi/src/conversion/mod.rs b/livekit-ffi/src/conversion/mod.rs index 76025aac7..64820b3ad 100644 --- a/livekit-ffi/src/conversion/mod.rs +++ b/livekit-ffi/src/conversion/mod.rs @@ -14,6 +14,7 @@ pub mod audio_frame; pub mod participant; +pub mod resampler; pub mod room; pub mod stats; pub mod track; diff --git a/livekit-ffi/src/conversion/resampler.rs b/livekit-ffi/src/conversion/resampler.rs new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/livekit-ffi/src/conversion/resampler.rs @@ -0,0 +1 @@ + diff --git a/livekit-ffi/src/livekit.proto.rs b/livekit-ffi/src/livekit.proto.rs index 1cfce9572..6f003c09f 100644 --- a/livekit-ffi/src/livekit.proto.rs +++ b/livekit-ffi/src/livekit.proto.rs @@ -1,4 +1,5 @@ // @generated +// This file is @generated by prost-build. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FrameCryptor { @@ -3054,6 +3055,76 @@ pub struct RemixAndResampleResponse { #[prost(message, optional, tag="1")] pub buffer: ::core::option::Option, } +// New resampler using SoX (much better quality) + +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct NewSoxResamplerRequest { + #[prost(double, tag="1")] + pub input_rate: f64, + #[prost(double, tag="2")] + pub output_rate: f64, + #[prost(uint32, tag="3")] + pub num_channels: u32, + #[prost(enumeration="SoxResamplerDataType", tag="4")] + pub input_data_type: i32, + #[prost(enumeration="SoxResamplerDataType", tag="5")] + pub output_data_type: i32, + #[prost(enumeration="SoxQualityRecipe", tag="6")] + pub quality_recipe: i32, + #[prost(uint32, tag="7")] + pub flags: u32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct NewSoxResamplerResponse { + #[prost(message, optional, tag="1")] + pub resampler: ::core::option::Option, + #[prost(string, optional, tag="2")] + pub error: ::core::option::Option<::prost::alloc::string::String>, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PushSoxResamplerRequest { + #[prost(uint64, tag="1")] + pub resampler_handle: u64, + /// *const i16 + #[prost(uint64, tag="2")] + pub data_ptr: u64, + /// in bytes + #[prost(uint32, tag="3")] + pub size: u32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PushSoxResamplerResponse { + /// *const i16 (could be null) + #[prost(uint64, tag="1")] + pub output_ptr: u64, + /// in bytes + #[prost(uint32, tag="2")] + pub size: u32, + #[prost(string, optional, tag="3")] + pub error: ::core::option::Option<::prost::alloc::string::String>, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct FlushSoxResamplerRequest { + #[prost(uint64, tag="1")] + pub resampler_handle: u64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct FlushSoxResamplerResponse { + /// *const i16 (could be null) + #[prost(uint64, tag="1")] + pub output_ptr: u64, + /// in bytes + #[prost(uint32, tag="2")] + pub size: u32, + #[prost(string, optional, tag="3")] + pub error: ::core::option::Option<::prost::alloc::string::String>, +} // // AudioFrame buffer // @@ -3167,6 +3238,128 @@ pub struct OwnedAudioResampler { pub info: ::core::option::Option, } // +// Sox AudioResampler +// + +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SoxResamplerInfo { +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct OwnedSoxResampler { + #[prost(message, optional, tag="1")] + pub handle: ::core::option::Option, + #[prost(message, optional, tag="2")] + pub info: ::core::option::Option, +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum SoxResamplerDataType { + /// TODO(theomonnom): support other datatypes (shouldn't really be needed) + SoxrDatatypeInt16i = 0, + SoxrDatatypeInt16s = 1, +} +impl SoxResamplerDataType { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + SoxResamplerDataType::SoxrDatatypeInt16i => "SOXR_DATATYPE_INT16I", + SoxResamplerDataType::SoxrDatatypeInt16s => "SOXR_DATATYPE_INT16S", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "SOXR_DATATYPE_INT16I" => Some(Self::SoxrDatatypeInt16i), + "SOXR_DATATYPE_INT16S" => Some(Self::SoxrDatatypeInt16s), + _ => None, + } + } +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum SoxQualityRecipe { + SoxrQualityQuick = 0, + SoxrQualityLow = 1, + SoxrQualityMedium = 2, + SoxrQualityHigh = 3, + SoxrQualityVeryhigh = 4, +} +impl SoxQualityRecipe { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + SoxQualityRecipe::SoxrQualityQuick => "SOXR_QUALITY_QUICK", + SoxQualityRecipe::SoxrQualityLow => "SOXR_QUALITY_LOW", + SoxQualityRecipe::SoxrQualityMedium => "SOXR_QUALITY_MEDIUM", + SoxQualityRecipe::SoxrQualityHigh => "SOXR_QUALITY_HIGH", + SoxQualityRecipe::SoxrQualityVeryhigh => "SOXR_QUALITY_VERYHIGH", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "SOXR_QUALITY_QUICK" => Some(Self::SoxrQualityQuick), + "SOXR_QUALITY_LOW" => Some(Self::SoxrQualityLow), + "SOXR_QUALITY_MEDIUM" => Some(Self::SoxrQualityMedium), + "SOXR_QUALITY_HIGH" => Some(Self::SoxrQualityHigh), + "SOXR_QUALITY_VERYHIGH" => Some(Self::SoxrQualityVeryhigh), + _ => None, + } + } +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum SoxFlagBits { + /// 1 << 0 + SoxrRolloffSmall = 0, + /// 1 << 1 + SoxrRolloffMedium = 1, + /// 1 << 2 + SoxrRolloffNone = 2, + /// 1 << 3 + SoxrHighPrecClock = 3, + /// 1 << 4 + SoxrDoublePrecision = 4, + /// 1 << 5 + SoxrVr = 5, +} +impl SoxFlagBits { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + SoxFlagBits::SoxrRolloffSmall => "SOXR_ROLLOFF_SMALL", + SoxFlagBits::SoxrRolloffMedium => "SOXR_ROLLOFF_MEDIUM", + SoxFlagBits::SoxrRolloffNone => "SOXR_ROLLOFF_NONE", + SoxFlagBits::SoxrHighPrecClock => "SOXR_HIGH_PREC_CLOCK", + SoxFlagBits::SoxrDoublePrecision => "SOXR_DOUBLE_PRECISION", + SoxFlagBits::SoxrVr => "SOXR_VR", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "SOXR_ROLLOFF_SMALL" => Some(Self::SoxrRolloffSmall), + "SOXR_ROLLOFF_MEDIUM" => Some(Self::SoxrRolloffMedium), + "SOXR_ROLLOFF_NONE" => Some(Self::SoxrRolloffNone), + "SOXR_HIGH_PREC_CLOCK" => Some(Self::SoxrHighPrecClock), + "SOXR_DOUBLE_PRECISION" => Some(Self::SoxrDoublePrecision), + "SOXR_VR" => Some(Self::SoxrVr), + _ => None, + } + } +} +// // AudioStream // @@ -3250,7 +3443,7 @@ impl AudioSourceType { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiRequest { - #[prost(oneof="ffi_request::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32")] + #[prost(oneof="ffi_request::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35")] pub message: ::core::option::Option, } /// Nested message and enum types in `FfiRequest`. @@ -3324,13 +3517,19 @@ pub mod ffi_request { E2ee(super::E2eeRequest), #[prost(message, tag="32")] AudioStreamFromParticipant(super::AudioStreamFromParticipantRequest), + #[prost(message, tag="33")] + NewSoxResampler(super::NewSoxResamplerRequest), + #[prost(message, tag="34")] + PushSoxResampler(super::PushSoxResamplerRequest), + #[prost(message, tag="35")] + FlushSoxResampler(super::FlushSoxResamplerRequest), } } /// This is the output of livekit_ffi_request function. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiResponse { - #[prost(oneof="ffi_response::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32")] + #[prost(oneof="ffi_response::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35")] pub message: ::core::option::Option, } /// Nested message and enum types in `FfiResponse`. @@ -3404,6 +3603,12 @@ pub mod ffi_response { AudioStreamFromParticipant(super::AudioStreamFromParticipantResponse), #[prost(message, tag="32")] E2ee(super::E2eeResponse), + #[prost(message, tag="33")] + NewSoxResampler(super::NewSoxResamplerResponse), + #[prost(message, tag="34")] + PushSoxResampler(super::PushSoxResamplerResponse), + #[prost(message, tag="35")] + FlushSoxResampler(super::FlushSoxResamplerResponse), } } /// To minimize complexity, participant events are not included in the protocol. diff --git a/livekit-ffi/src/server/mod.rs b/livekit-ffi/src/server/mod.rs index 74d00fcd3..efe1e0976 100644 --- a/livekit-ffi/src/server/mod.rs +++ b/livekit-ffi/src/server/mod.rs @@ -39,6 +39,7 @@ pub mod audio_stream; pub mod colorcvt; pub mod logger; pub mod requests; +pub mod resampler; pub mod room; mod utils; pub mod video_source; @@ -66,6 +67,7 @@ pub struct FfiDataBuffer { impl FfiHandle for FfiDataBuffer {} impl FfiHandle for Arc> {} +impl FfiHandle for Arc> {} impl FfiHandle for AudioFrame<'static> {} impl FfiHandle for BoxVideoBuffer {} impl FfiHandle for Box<[u8]> {} diff --git a/livekit-ffi/src/server/requests.rs b/livekit-ffi/src/server/requests.rs index 89e395769..70cd1f39e 100644 --- a/livekit-ffi/src/server/requests.rs +++ b/livekit-ffi/src/server/requests.rs @@ -22,7 +22,7 @@ use livekit::{ use parking_lot::Mutex; use super::{ - audio_source, audio_stream, colorcvt, + audio_source, audio_stream, colorcvt, resampler, room::{self, FfiParticipant, FfiPublication, FfiTrack}, video_source, video_stream, FfiError, FfiResult, FfiServer, }; @@ -657,6 +657,101 @@ fn on_get_session_stats( Ok(proto::GetSessionStatsResponse { async_id }) } +fn on_new_sox_resampler( + server: &'static FfiServer, + new_soxr: proto::NewSoxResamplerRequest, +) -> FfiResult { + let io_spec = resampler::IOSpec { + input_type: new_soxr.input_data_type(), + output_type: new_soxr.output_data_type(), + }; + + let quality_spec = + resampler::QualitySpec { quality: new_soxr.quality_recipe(), flags: new_soxr.flags }; + + let runtime_spec = resampler::RuntimeSpec { num_threads: 1 }; + + match resampler::SoxResampler::new( + new_soxr.input_rate, + new_soxr.output_rate, + new_soxr.num_channels, + io_spec, + quality_spec, + runtime_spec, + ) { + Ok(resampler) => { + let resampler = Arc::new(Mutex::new(resampler)); + + let handle_id = server.next_id(); + server.store_handle(handle_id, resampler); + + Ok(proto::NewSoxResamplerResponse { + resampler: Some(proto::OwnedSoxResampler { + handle: Some(proto::FfiOwnedHandle { id: handle_id }), + info: Some(proto::SoxResamplerInfo {}), + }), + ..Default::default() + }) + } + Err(e) => { + Ok(proto::NewSoxResamplerResponse { error: Some(e.to_string()), ..Default::default() }) + } + } +} + +fn on_push_sox_resampler( + server: &'static FfiServer, + push: proto::PushSoxResamplerRequest, +) -> FfiResult { + let resampler = server + .retrieve_handle::>>(push.resampler_handle)? + .clone(); + + let data_ptr = push.data_ptr; + let data_size = push.size; + + let data = unsafe { + slice::from_raw_parts( + data_ptr as *const i16, + data_size as usize / std::mem::size_of::(), + ) + }; + + let mut resampler = resampler.lock(); + match resampler.push(data) { + Ok(output) => Ok(proto::PushSoxResamplerResponse { + output_ptr: output.as_ptr() as u64, + size: (output.len() * std::mem::size_of::()) as u32, + ..Default::default() + }), + Err(e) => { + Ok(proto::PushSoxResamplerResponse { error: Some(e.to_string()), ..Default::default() }) + } + } +} + +fn on_flush_sox_resampler( + server: &'static FfiServer, + flush: proto::FlushSoxResamplerRequest, +) -> FfiResult { + let resampler = server + .retrieve_handle::>>(flush.resampler_handle)? + .clone(); + + let mut resampler = resampler.lock(); + match resampler.flush() { + Ok(output) => Ok(proto::FlushSoxResamplerResponse { + output_ptr: output.as_ptr() as u64, + size: (output.len() * std::mem::size_of::()) as u32, + ..Default::default() + }), + Err(e) => Ok(proto::FlushSoxResamplerResponse { + error: Some(e.to_string()), + ..Default::default() + }), + } +} + #[allow(clippy::field_reassign_with_default)] // Avoid uggly format pub fn handle_request( server: &'static FfiServer, @@ -772,6 +867,19 @@ pub fn handle_request( get_session_stats, )?) } + proto::ffi_request::Message::NewSoxResampler(new_soxr) => { + proto::ffi_response::Message::NewSoxResampler(on_new_sox_resampler(server, new_soxr)?) + } + proto::ffi_request::Message::PushSoxResampler(push_soxr) => { + proto::ffi_response::Message::PushSoxResampler(on_push_sox_resampler( + server, push_soxr, + )?) + } + proto::ffi_request::Message::FlushSoxResampler(flush_soxr) => { + proto::ffi_response::Message::FlushSoxResampler(on_flush_sox_resampler( + server, flush_soxr, + )?) + } }); Ok(res) diff --git a/livekit-ffi/src/server/resampler.rs b/livekit-ffi/src/server/resampler.rs new file mode 100644 index 000000000..0f75a7060 --- /dev/null +++ b/livekit-ffi/src/server/resampler.rs @@ -0,0 +1,139 @@ +use std::{ + ffi::c_char, + os::raw::{c_ulong, c_void}, +}; + +use soxr_sys; + +use crate::proto; + +pub struct IOSpec { + pub input_type: proto::SoxResamplerDataType, + pub output_type: proto::SoxResamplerDataType, +} + +pub struct QualitySpec { + pub quality: proto::SoxQualityRecipe, + pub flags: u32, // proto::SoxQualityFlags +} + +pub struct RuntimeSpec { + pub num_threads: u32, +} + +pub struct SoxResampler { + soxr_ptr: soxr_sys::soxr_t, + out_buf: Vec, + input_rate: f64, + output_rate: f64, + num_channels: u32, +} + +unsafe impl Send for SoxResampler {} + +impl SoxResampler { + pub fn new( + input_rate: f64, + output_rate: f64, + num_channels: u32, + io_spec: IOSpec, + quality_spec: QualitySpec, + runtime_spec: RuntimeSpec, + ) -> Result { + let error: *mut *const c_char = std::ptr::null_mut(); + + let soxr_ptr = unsafe { + let io_spec = soxr_sys::soxr_io_spec( + to_soxr_datatype(io_spec.input_type), + to_soxr_datatype(io_spec.output_type), + ); + + let quality_spec = soxr_sys::soxr_quality_spec( + quality_spec.quality as c_ulong, + quality_spec.flags as c_ulong, + ); + + let runtime_spec = soxr_sys::soxr_runtime_spec(runtime_spec.num_threads); + + soxr_sys::soxr_create( + input_rate, + output_rate, + num_channels, + error, + &io_spec, + &quality_spec, + &runtime_spec, + ) + }; + + if !error.is_null() { + let error_msg = unsafe { std::ffi::CStr::from_ptr(*error) }; + return Err(error_msg.to_string_lossy().to_string()); + } + + Ok(Self { soxr_ptr, out_buf: Vec::new(), input_rate, output_rate, num_channels }) + } + + pub fn push(&mut self, input: &[i16]) -> Result<&[i16], String> { + let input_length = input.len() / self.num_channels as usize; + let ratio = self.output_rate / self.input_rate; + let soxr_delay = unsafe { soxr_sys::soxr_delay(self.soxr_ptr) }; + + let max_out_len = + ((input_length as f64 * ratio).ceil() as usize) + (soxr_delay.ceil() as usize) + 1; + + let required_output_size = max_out_len * self.num_channels as usize; + if self.out_buf.len() < required_output_size { + self.out_buf.resize(required_output_size, 0); + } + + let mut idone: usize = 0; + let mut odone: usize = 0; + let error = unsafe { + soxr_sys::soxr_process( + self.soxr_ptr, + input.as_ptr() as *const c_void, + input_length, + &mut idone, + self.out_buf.as_mut_ptr() as *mut c_void, + max_out_len, + &mut odone, + ) + }; + if !error.is_null() { + let error_msg = unsafe { std::ffi::CStr::from_ptr(error) }; + return Err(error_msg.to_string_lossy().to_string()); + } + + let output_samples = odone * self.num_channels as usize; + Ok(&self.out_buf[..output_samples]) + } + + pub fn flush(&mut self) -> Result<&[i16], String> { + let mut odone: usize = 0; + let error = unsafe { + soxr_sys::soxr_process( + self.soxr_ptr, + std::ptr::null(), + 0, + std::ptr::null_mut(), + self.out_buf.as_mut_ptr() as *mut c_void, + self.out_buf.len(), + &mut odone, + ) + }; + if !error.is_null() { + let error_msg = unsafe { std::ffi::CStr::from_ptr(error) }; + return Err(error_msg.to_string_lossy().to_string()); + } + + Ok(&self.out_buf[..odone]) + } +} + +fn to_soxr_datatype(datatype: proto::SoxResamplerDataType) -> soxr_sys::soxr_datatype_t { + match datatype { + proto::SoxResamplerDataType::SoxrDatatypeInt16i => soxr_sys::soxr_datatype_t_SOXR_INT16_I, + proto::SoxResamplerDataType::SoxrDatatypeInt16s => soxr_sys::soxr_datatype_t_SOXR_INT16_S, + } +} diff --git a/soxr-sys/build.rs b/soxr-sys/build.rs index 59be377a7..17fef5ace 100644 --- a/soxr-sys/build.rs +++ b/soxr-sys/build.rs @@ -1,3 +1,5 @@ +use std::env; + fn main() { let mut build = cc::Build::new(); @@ -36,5 +38,9 @@ fn main() { } build.compile("libsoxr.a"); - println!("cargo:rustc-link-lib=m"); + + let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap(); + if target_os.as_str() != "windows" { + println!("cargo:rustc-link-lib=m"); + } } diff --git a/soxr-sys/generate_bindings.sh b/soxr-sys/generate_bindings.sh index 308f9c884..62c7525bc 100755 --- a/soxr-sys/generate_bindings.sh +++ b/soxr-sys/generate_bindings.sh @@ -1 +1 @@ -bindgen --no-prepend-enum-name src/soxr.h -o src/soxr.rs +bindgen src/soxr.h -o src/soxr.rs diff --git a/soxr-sys/src/lib.rs b/soxr-sys/src/lib.rs index 31c0d8dbf..e8f775907 100644 --- a/soxr-sys/src/lib.rs +++ b/soxr-sys/src/lib.rs @@ -31,14 +31,12 @@ mod tests { let wav_spec = reader.spec(); let input_rate = wav_spec.sample_rate as f64; - let output_rate = 44100.0; + let output_rate = 24000.0; let num_channels = wav_spec.channels as u32; - let samples: Vec = reader - .samples::() - .map(|s| s.expect("Failed to read sample")) - .collect(); + let samples: Vec = + reader.samples::().map(|s| s.expect("Failed to read sample")).collect(); let buf_total_len = samples.len(); let olen = diff --git a/soxr-sys/src/soxr.rs b/soxr-sys/src/soxr.rs index 442dd03aa..ec2a0fd72 100644 --- a/soxr-sys/src/soxr.rs +++ b/soxr-sys/src/soxr.rs @@ -125,19 +125,19 @@ extern "C" { extern "C" { pub fn soxr_set_io_ratio(arg1: soxr_t, io_ratio: f64, slew_len: usize) -> soxr_error_t; } -pub const SOXR_FLOAT32: soxr_datatype_t = 0; -pub const SOXR_FLOAT64: soxr_datatype_t = 1; -pub const SOXR_INT32: soxr_datatype_t = 2; -pub const SOXR_INT16: soxr_datatype_t = 3; -pub const SOXR_SPLIT: soxr_datatype_t = 4; -pub const SOXR_FLOAT32_I: soxr_datatype_t = 0; -pub const SOXR_FLOAT64_I: soxr_datatype_t = 1; -pub const SOXR_INT32_I: soxr_datatype_t = 2; -pub const SOXR_INT16_I: soxr_datatype_t = 3; -pub const SOXR_FLOAT32_S: soxr_datatype_t = 4; -pub const SOXR_FLOAT64_S: soxr_datatype_t = 5; -pub const SOXR_INT32_S: soxr_datatype_t = 6; -pub const SOXR_INT16_S: soxr_datatype_t = 7; +pub const soxr_datatype_t_SOXR_FLOAT32: soxr_datatype_t = 0; +pub const soxr_datatype_t_SOXR_FLOAT64: soxr_datatype_t = 1; +pub const soxr_datatype_t_SOXR_INT32: soxr_datatype_t = 2; +pub const soxr_datatype_t_SOXR_INT16: soxr_datatype_t = 3; +pub const soxr_datatype_t_SOXR_SPLIT: soxr_datatype_t = 4; +pub const soxr_datatype_t_SOXR_FLOAT32_I: soxr_datatype_t = 0; +pub const soxr_datatype_t_SOXR_FLOAT64_I: soxr_datatype_t = 1; +pub const soxr_datatype_t_SOXR_INT32_I: soxr_datatype_t = 2; +pub const soxr_datatype_t_SOXR_INT16_I: soxr_datatype_t = 3; +pub const soxr_datatype_t_SOXR_FLOAT32_S: soxr_datatype_t = 4; +pub const soxr_datatype_t_SOXR_FLOAT64_S: soxr_datatype_t = 5; +pub const soxr_datatype_t_SOXR_INT32_S: soxr_datatype_t = 6; +pub const soxr_datatype_t_SOXR_INT16_S: soxr_datatype_t = 7; pub type soxr_datatype_t = ::std::os::raw::c_uint; #[repr(C)] #[derive(Debug, Copy, Clone)] @@ -165,52 +165,27 @@ fn bindgen_test_layout_soxr_io_spec() { assert_eq!( unsafe { ::std::ptr::addr_of!((*ptr).itype) as usize - ptr as usize }, 0usize, - concat!( - "Offset of field: ", - stringify!(soxr_io_spec), - "::", - stringify!(itype) - ) + concat!("Offset of field: ", stringify!(soxr_io_spec), "::", stringify!(itype)) ); assert_eq!( unsafe { ::std::ptr::addr_of!((*ptr).otype) as usize - ptr as usize }, 4usize, - concat!( - "Offset of field: ", - stringify!(soxr_io_spec), - "::", - stringify!(otype) - ) + concat!("Offset of field: ", stringify!(soxr_io_spec), "::", stringify!(otype)) ); assert_eq!( unsafe { ::std::ptr::addr_of!((*ptr).scale) as usize - ptr as usize }, 8usize, - concat!( - "Offset of field: ", - stringify!(soxr_io_spec), - "::", - stringify!(scale) - ) + concat!("Offset of field: ", stringify!(soxr_io_spec), "::", stringify!(scale)) ); assert_eq!( unsafe { ::std::ptr::addr_of!((*ptr).e) as usize - ptr as usize }, 16usize, - concat!( - "Offset of field: ", - stringify!(soxr_io_spec), - "::", - stringify!(e) - ) + concat!("Offset of field: ", stringify!(soxr_io_spec), "::", stringify!(e)) ); assert_eq!( unsafe { ::std::ptr::addr_of!((*ptr).flags) as usize - ptr as usize }, 24usize, - concat!( - "Offset of field: ", - stringify!(soxr_io_spec), - "::", - stringify!(flags) - ) + concat!("Offset of field: ", stringify!(soxr_io_spec), "::", stringify!(flags)) ); } #[repr(C)] @@ -240,12 +215,7 @@ fn bindgen_test_layout_soxr_quality_spec() { assert_eq!( unsafe { ::std::ptr::addr_of!((*ptr).precision) as usize - ptr as usize }, 0usize, - concat!( - "Offset of field: ", - stringify!(soxr_quality_spec), - "::", - stringify!(precision) - ) + concat!("Offset of field: ", stringify!(soxr_quality_spec), "::", stringify!(precision)) ); assert_eq!( unsafe { ::std::ptr::addr_of!((*ptr).phase_response) as usize - ptr as usize }, @@ -260,12 +230,7 @@ fn bindgen_test_layout_soxr_quality_spec() { assert_eq!( unsafe { ::std::ptr::addr_of!((*ptr).passband_end) as usize - ptr as usize }, 16usize, - concat!( - "Offset of field: ", - stringify!(soxr_quality_spec), - "::", - stringify!(passband_end) - ) + concat!("Offset of field: ", stringify!(soxr_quality_spec), "::", stringify!(passband_end)) ); assert_eq!( unsafe { ::std::ptr::addr_of!((*ptr).stopband_begin) as usize - ptr as usize }, @@ -280,22 +245,12 @@ fn bindgen_test_layout_soxr_quality_spec() { assert_eq!( unsafe { ::std::ptr::addr_of!((*ptr).e) as usize - ptr as usize }, 32usize, - concat!( - "Offset of field: ", - stringify!(soxr_quality_spec), - "::", - stringify!(e) - ) + concat!("Offset of field: ", stringify!(soxr_quality_spec), "::", stringify!(e)) ); assert_eq!( unsafe { ::std::ptr::addr_of!((*ptr).flags) as usize - ptr as usize }, 40usize, - concat!( - "Offset of field: ", - stringify!(soxr_quality_spec), - "::", - stringify!(flags) - ) + concat!("Offset of field: ", stringify!(soxr_quality_spec), "::", stringify!(flags)) ); } #[repr(C)] @@ -355,32 +310,17 @@ fn bindgen_test_layout_soxr_runtime_spec() { assert_eq!( unsafe { ::std::ptr::addr_of!((*ptr).num_threads) as usize - ptr as usize }, 12usize, - concat!( - "Offset of field: ", - stringify!(soxr_runtime_spec), - "::", - stringify!(num_threads) - ) + concat!("Offset of field: ", stringify!(soxr_runtime_spec), "::", stringify!(num_threads)) ); assert_eq!( unsafe { ::std::ptr::addr_of!((*ptr).e) as usize - ptr as usize }, 16usize, - concat!( - "Offset of field: ", - stringify!(soxr_runtime_spec), - "::", - stringify!(e) - ) + concat!("Offset of field: ", stringify!(soxr_runtime_spec), "::", stringify!(e)) ); assert_eq!( unsafe { ::std::ptr::addr_of!((*ptr).flags) as usize - ptr as usize }, 24usize, - concat!( - "Offset of field: ", - stringify!(soxr_runtime_spec), - "::", - stringify!(flags) - ) + concat!("Offset of field: ", stringify!(soxr_runtime_spec), "::", stringify!(flags)) ); } extern "C" { From 03a266663ba4c7a24c33766fc93c7d9475746ac3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Thu, 26 Sep 2024 14:15:27 -0700 Subject: [PATCH 045/274] free soxr resampler (#445) --- livekit-ffi/src/server/resampler.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/livekit-ffi/src/server/resampler.rs b/livekit-ffi/src/server/resampler.rs index 0f75a7060..ad2eaa753 100644 --- a/livekit-ffi/src/server/resampler.rs +++ b/livekit-ffi/src/server/resampler.rs @@ -131,6 +131,14 @@ impl SoxResampler { } } +impl Drop for SoxResampler { + fn drop(&mut self) { + unsafe { + soxr_sys::soxr_delete(self.soxr_ptr); + } + } +} + fn to_soxr_datatype(datatype: proto::SoxResamplerDataType) -> soxr_sys::soxr_datatype_t { match datatype { proto::SoxResamplerDataType::SoxrDatatypeInt16i => soxr_sys::soxr_datatype_t_SOXR_INT16_I, From 1ee55b14163faa9535e5a6e787744e7b8759b9c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Thu, 26 Sep 2024 14:34:49 -0700 Subject: [PATCH 046/274] ffi-v0.11.0 (#446) --- Cargo.lock | 2 +- livekit-ffi/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c082ba91d..e7d37c0f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1597,7 +1597,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.10.3" +version = "0.11.0" dependencies = [ "console-subscriber", "dashmap", diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 13a78560c..565f4304c 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.10.3" +version = "0.11.0" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" From dd5cb7ee4800aaf86ddee049dbbd0b1aab427824 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Fri, 27 Sep 2024 20:38:48 -0700 Subject: [PATCH 047/274] soxresampler: return nullptr when empty (#448) --- livekit-ffi/src/server/requests.rs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/livekit-ffi/src/server/requests.rs b/livekit-ffi/src/server/requests.rs index 70cd1f39e..a1043c8c3 100644 --- a/livekit-ffi/src/server/requests.rs +++ b/livekit-ffi/src/server/requests.rs @@ -719,11 +719,21 @@ fn on_push_sox_resampler( let mut resampler = resampler.lock(); match resampler.push(data) { - Ok(output) => Ok(proto::PushSoxResamplerResponse { - output_ptr: output.as_ptr() as u64, - size: (output.len() * std::mem::size_of::()) as u32, - ..Default::default() - }), + Ok(output) => { + if output.is_empty() { + return Ok(proto::PushSoxResamplerResponse { + output_ptr: 0, + size: 0, + ..Default::default() + }); + } + + Ok(proto::PushSoxResamplerResponse { + output_ptr: output.as_ptr() as u64, + size: (output.len() * std::mem::size_of::()) as u32, + ..Default::default() + }) + } Err(e) => { Ok(proto::PushSoxResamplerResponse { error: Some(e.to_string()), ..Default::default() }) } From 5bc686bd2f04a99e25b6364fc5514abc371c7bdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Mon, 30 Sep 2024 12:25:33 -0700 Subject: [PATCH 048/274] avoid reconnection logs on normal close (#449) --- livekit/src/rtc_engine/mod.rs | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/livekit/src/rtc_engine/mod.rs b/livekit/src/rtc_engine/mod.rs index f718585b8..d0a235510 100644 --- a/livekit/src/rtc_engine/mod.rs +++ b/livekit/src/rtc_engine/mod.rs @@ -148,6 +148,7 @@ struct EngineHandle { session: Arc, closed: bool, reconnecting: bool, + can_reconnect: bool, // If full_reconnect is true, the next attempt will not try to resume // and will instead do a full reconnect @@ -318,6 +319,7 @@ impl EngineInner { session: Arc::new(session), closed: false, reconnecting: false, + can_reconnect: true, full_reconnect: false, engine_task: None, }), @@ -402,15 +404,25 @@ impl EngineInner { async fn on_session_event(self: &Arc, event: SessionEvent) -> EngineResult<()> { match event { SessionEvent::Close { source, reason, action, retry_now } => { - log::warn!("received session close: {:?} {:?} {:?}", source, reason, action); match action { - proto::leave_request::Action::Resume => { - self.reconnection_needed(retry_now, false) - } - proto::leave_request::Action::Reconnect => { - self.reconnection_needed(retry_now, true) + proto::leave_request::Action::Resume + | proto::leave_request::Action::Reconnect => { + log::warn!( + "received session close: {:?} {:?} {:?}", + source, + reason, + action + ); + self.reconnection_needed( + retry_now, + action == proto::leave_request::Action::Reconnect, + ); } proto::leave_request::Action::Disconnect => { + // Disallow reconnection to avoid races + let mut running_handle = self.running_handle.write(); + running_handle.can_reconnect = false; + // Spawning a new task because the close function wait for the engine_task to // finish. (So it doesn't make sense to await it here) livekit_runtime::spawn({ @@ -505,6 +517,11 @@ impl EngineInner { /// Ask for a full reconnect if `full_reconnect` is true fn reconnection_needed(self: &Arc, retry_now: bool, full_reconnect: bool) { let mut running_handle = self.running_handle.write(); + + if !running_handle.can_reconnect { + return; + } + if running_handle.reconnecting { // If we're already reconnecting just update the interval to restart a new attempt // ASAP From f2fb3e53d19653e0eef8f79f727e2fe9c889dacf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Mon, 30 Sep 2024 21:34:17 -0700 Subject: [PATCH 049/274] ffi-v0.11.1 (#451) --- livekit-ffi/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 565f4304c..b3c956413 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.11.0" +version = "0.11.1" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" From 0bbaae8d9cda0c1047441b45271b26e5b06b4c15 Mon Sep 17 00:00:00 2001 From: lukasIO Date: Wed, 2 Oct 2024 21:38:34 +0200 Subject: [PATCH 050/274] Ignore unknown DataPacket types (#452) --- livekit/src/rtc_engine/rtc_session.rs | 128 +++++++++++++------------- 1 file changed, 65 insertions(+), 63 deletions(-) diff --git a/livekit/src/rtc_engine/rtc_session.rs b/livekit/src/rtc_engine/rtc_session.rs index cadbd8ff7..16dcf4e6d 100644 --- a/livekit/src/rtc_engine/rtc_session.rs +++ b/livekit/src/rtc_engine/rtc_session.rs @@ -592,69 +592,71 @@ impl SessionInner { } let data = proto::DataPacket::decode(&*data).unwrap(); - match data.value.as_ref().unwrap() { - proto::data_packet::Value::User(user) => { - let participant_sid = user - .participant_sid - .is_empty() - .not() - .then_some(user.participant_sid.clone()); - - let participant_identity = if !data.participant_identity.is_empty() { - Some(data.participant_identity.clone()) - } else if !user.participant_identity.is_empty() { - Some(user.participant_identity.clone()) - } else { - None - }; - - let _ = self.emitter.send(SessionEvent::Data { - kind: data.kind().into(), - participant_sid: participant_sid.map(|s| s.try_into().unwrap()), - participant_identity: participant_identity - .map(|s| s.try_into().unwrap()), - payload: user.payload.clone(), - topic: user.topic.clone(), - }); - } - proto::data_packet::Value::SipDtmf(dtmf) => { - let participant_identity = data - .participant_identity - .is_empty() - .not() - .then_some(data.participant_identity.clone()); - let digit = dtmf.digit.is_empty().not().then_some(dtmf.digit.clone()); - - let _ = self.emitter.send(SessionEvent::SipDTMF { - participant_identity: participant_identity - .map(|s| s.try_into().unwrap()), - digit: digit.map(|s| s.try_into().unwrap()), - code: dtmf.code, - }); - } - proto::data_packet::Value::Speaker(_) => {} - proto::data_packet::Value::Transcription(transcription) => { - let track_sid = transcription.track_id.clone(); - // let segments = transcription.segments.clone(); - let segments = transcription - .segments - .iter() - .map(|s| TranscriptionSegment { - id: s.id.clone(), - start_time: s.start_time, - end_time: s.end_time, - text: s.text.clone(), - language: s.language.clone(), - r#final: s.r#final, - }) - .collect(); - let participant_identity: ParticipantIdentity = - transcription.transcribed_participant_identity.clone().into(); - let _ = self.emitter.send(SessionEvent::Transcription { - participant_identity, - track_sid, - segments, - }); + if let Some(packet) = data.value.as_ref() { + match packet { + proto::data_packet::Value::User(user) => { + let participant_sid = user + .participant_sid + .is_empty() + .not() + .then_some(user.participant_sid.clone()); + + let participant_identity = if !data.participant_identity.is_empty() { + Some(data.participant_identity.clone()) + } else if !user.participant_identity.is_empty() { + Some(user.participant_identity.clone()) + } else { + None + }; + + let _ = self.emitter.send(SessionEvent::Data { + kind: data.kind().into(), + participant_sid: participant_sid.map(|s| s.try_into().unwrap()), + participant_identity: participant_identity + .map(|s| s.try_into().unwrap()), + payload: user.payload.clone(), + topic: user.topic.clone(), + }); + } + proto::data_packet::Value::SipDtmf(dtmf) => { + let participant_identity = data + .participant_identity + .is_empty() + .not() + .then_some(data.participant_identity.clone()); + let digit = dtmf.digit.is_empty().not().then_some(dtmf.digit.clone()); + + let _ = self.emitter.send(SessionEvent::SipDTMF { + participant_identity: participant_identity + .map(|s| s.try_into().unwrap()), + digit: digit.map(|s| s.try_into().unwrap()), + code: dtmf.code, + }); + } + proto::data_packet::Value::Speaker(_) => {} + proto::data_packet::Value::Transcription(transcription) => { + let track_sid = transcription.track_id.clone(); + // let segments = transcription.segments.clone(); + let segments = transcription + .segments + .iter() + .map(|s| TranscriptionSegment { + id: s.id.clone(), + start_time: s.start_time, + end_time: s.end_time, + text: s.text.clone(), + language: s.language.clone(), + r#final: s.r#final, + }) + .collect(); + let participant_identity: ParticipantIdentity = + transcription.transcribed_participant_identity.clone().into(); + let _ = self.emitter.send(SessionEvent::Transcription { + participant_identity, + track_sid, + segments, + }); + } } } } From 788945c8479ffce5dcc3cbba59ed88c214d15a2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Wed, 2 Oct 2024 13:49:20 -0700 Subject: [PATCH 051/274] add livekit_ffi_dispose (#453) --- Cargo.lock | 2 +- livekit-ffi/src/cabi.rs | 5 +++++ livekit-ffi/src/server/mod.rs | 6 ++++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e7d37c0f7..9604f5ff3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1597,7 +1597,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.11.0" +version = "0.11.1" dependencies = [ "console-subscriber", "dashmap", diff --git a/livekit-ffi/src/cabi.rs b/livekit-ffi/src/cabi.rs index a56c8f8ca..1df37c538 100644 --- a/livekit-ffi/src/cabi.rs +++ b/livekit-ffi/src/cabi.rs @@ -85,6 +85,11 @@ pub extern "C" fn livekit_ffi_drop_handle(handle_id: FfiHandleId) -> bool { FFI_SERVER.drop_handle(handle_id) } +#[no_mangle] +pub extern "C" fn livekit_ffi_dispose() { + FFI_SERVER.async_runtime.block_on(FFI_SERVER.dispose()); +} + #[cfg(target_os = "android")] pub mod android { use jni::{ diff --git a/livekit-ffi/src/server/mod.rs b/livekit-ffi/src/server/mod.rs index efe1e0976..725c9e961 100644 --- a/livekit-ffi/src/server/mod.rs +++ b/livekit-ffi/src/server/mod.rs @@ -76,7 +76,7 @@ pub struct FfiServer { /// Store all Ffi handles inside an HashMap, if this isn't efficient enough /// We can still use Box::into_raw & Box::from_raw in the future (but keep it safe for now) ffi_handles: DashMap>, - async_runtime: tokio::runtime::Runtime, + pub async_runtime: tokio::runtime::Runtime, next_id: AtomicU64, config: Mutex>, @@ -135,7 +135,9 @@ impl FfiServer { } pub async fn dispose(&self) { - log::info!("disposing the FfiServer, closing all rooms..."); + self.logger.set_capture_logs(false); + + log::info!("disposing ffi server"); // Close all rooms let mut rooms = Vec::new(); From 10b10c79cf0c349e28880d3f73118d4f4bd45d53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Wed, 2 Oct 2024 14:21:34 -0700 Subject: [PATCH 052/274] ffi-v0.11.2 (#454) --- Cargo.lock | 2 +- livekit-ffi/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9604f5ff3..c2fb6b0c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1597,7 +1597,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.11.1" +version = "0.11.2" dependencies = [ "console-subscriber", "dashmap", diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index b3c956413..409baaf2e 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.11.1" +version = "0.11.2" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" From a44934c031ac8cd171aaa995ac371fd185836d9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Wed, 2 Oct 2024 16:05:20 -0700 Subject: [PATCH 053/274] fix audiosource bit depth (#455) --- webrtc-sys/src/audio_track.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/webrtc-sys/src/audio_track.cpp b/webrtc-sys/src/audio_track.cpp index 1d42f14e9..8a49c3203 100644 --- a/webrtc-sys/src/audio_track.cpp +++ b/webrtc-sys/src/audio_track.cpp @@ -163,7 +163,7 @@ AudioTrackSource::InternalSource::InternalSource( if (buffer_.size() >= samples10ms) { for (auto sink : sinks_) - sink->OnData(buffer_.data(), sizeof(int16_t), sample_rate_, + sink->OnData(buffer_.data(), sizeof(int16_t) * 8, sample_rate_, num_channels_, samples10ms / num_channels_); buffer_.erase(buffer_.begin(), buffer_.begin() + samples10ms); @@ -171,7 +171,7 @@ AudioTrackSource::InternalSource::InternalSource( missed_frames_++; if (missed_frames_ >= silence_frames_threshold) { for (auto sink : sinks_) - sink->OnData(silence_buffer_, sizeof(int16_t), sample_rate_, + sink->OnData(silence_buffer_, sizeof(int16_t) * 8, sample_rate_, num_channels_, samples10ms / num_channels_); } } @@ -221,7 +221,7 @@ bool AudioTrackSource::InternalSource::capture_frame( } else { // capture directly when the queue buffer is 0 (frame size must be 10ms) for (auto sink : sinks_) - sink->OnData(data.data(), sizeof(int16_t), sample_rate, + sink->OnData(data.data(), sizeof(int16_t) * 8, sample_rate, number_of_channels, number_of_frames); } From 5d1f91eed163d9badb296ff605cb0a500235a741 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Wed, 2 Oct 2024 16:09:22 -0700 Subject: [PATCH 054/274] ffi-v0.11.3 (#456) --- Cargo.lock | 2 +- livekit-ffi/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c2fb6b0c6..34fcf8284 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1597,7 +1597,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.11.2" +version = "0.11.3" dependencies = [ "console-subscriber", "dashmap", diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 409baaf2e..5f957816d 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.11.2" +version = "0.11.3" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" From 8afcb52c9a2c9054867e1396eed4434542d04d44 Mon Sep 17 00:00:00 2001 From: Ben Cherry Date: Thu, 3 Oct 2024 16:06:47 -0700 Subject: [PATCH 055/274] Update to livekit-protocol v1.23.0 (Stubs only), fix tests (#458) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * proto * generated protobuf * stubs * Fix datatype * Fix tests * file tweak * diag * diag * synthetic * fmt * Cleanup * 3s * doc * Default::default(), * comma * Update livekit/src/rtc_engine/rtc_session.rs Co-authored-by: Théo Monnom --------- Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Théo Monnom --- .gitignore | 5 +- examples/play_from_disk/change-sophie.wav | 4 +- livekit-api/src/services/sip.rs | 6 + livekit-protocol/protocol | 2 +- livekit-protocol/src/livekit.rs | 370 +- livekit-protocol/src/livekit.serde.rs | 9297 +++++++++++++-------- livekit/src/rtc_engine/rtc_session.rs | 1 + soxr-sys/src/lib.rs | 63 +- 8 files changed, 6428 insertions(+), 3320 deletions(-) diff --git a/.gitignore b/.gitignore index 3d98c20c8..b37534470 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ target -/.idea \ No newline at end of file +/.idea +soxr-sys/test-input.wav +soxr-sys/test-output.wav +.DS_Store \ No newline at end of file diff --git a/examples/play_from_disk/change-sophie.wav b/examples/play_from_disk/change-sophie.wav index 80a9c6af9..f4f760c72 100644 --- a/examples/play_from_disk/change-sophie.wav +++ b/examples/play_from_disk/change-sophie.wav @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:16a978d4be2506cca5e25c5b7aa5b0e53451049d979684d9168c1a81beb4babd -size 570264 +oid sha256:d55ab1ab891488d129ab94ddc4ea567a5e297cc183f84a7d98c6741070334fb6 +size 570298 diff --git a/livekit-api/src/services/sip.rs b/livekit-api/src/services/sip.rs index 55ddf30ad..aaf796f5d 100644 --- a/livekit-api/src/services/sip.rs +++ b/livekit-api/src/services/sip.rs @@ -200,6 +200,9 @@ impl SIPClient { allowed_addresses: options.allowed_addresses.to_owned(), auth_username: options.auth_username.to_owned(), auth_password: options.auth_password.to_owned(), + + headers: Default::default(), + headers_to_attributes: Default::default(), }), }, self.base.auth_header( @@ -233,6 +236,9 @@ impl SIPClient { auth_username: options.auth_username.to_owned(), auth_password: options.auth_password.to_owned(), + + headers: Default::default(), + headers_to_attributes: Default::default(), }), }, self.base.auth_header( diff --git a/livekit-protocol/protocol b/livekit-protocol/protocol index 5c7350d25..a601adc5e 160000 --- a/livekit-protocol/protocol +++ b/livekit-protocol/protocol @@ -1 +1 @@ -Subproject commit 5c7350d25904ed8fd8163e91ff47f0577ca6afad +Subproject commit a601adc5e9027820857a6d445b32a868b19d4184 diff --git a/livekit-protocol/src/livekit.rs b/livekit-protocol/src/livekit.rs index 39426bdef..19181c255 100644 --- a/livekit-protocol/src/livekit.rs +++ b/livekit-protocol/src/livekit.rs @@ -2,6 +2,184 @@ // This file is @generated by prost-build. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct MetricsBatch { + /// time at which this batch is sent based on a monotonic clock (millisecond resolution) + #[prost(int64, tag="1")] + pub timestamp_ms: i64, + #[prost(message, optional, tag="2")] + pub normalized_timestamp: ::core::option::Option<::pbjson_types::Timestamp>, + /// To avoid repeating string values, we store them in a separate list and reference them by index + /// This is useful for storing participant identities, track names, etc. + /// There is also a predefined list of labels that can be used to reference common metrics. + /// They have reserved indices from 0 to (METRIC_LABEL_PREDEFINED_MAX_VALUE - 1). + /// Indexes pointing at str_data should start from METRIC_LABEL_PREDEFINED_MAX_VALUE, + /// such that str_data\[0\] == index of METRIC_LABEL_PREDEFINED_MAX_VALUE. + #[prost(string, repeated, tag="3")] + pub str_data: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(message, repeated, tag="4")] + pub time_series: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="5")] + pub events: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TimeSeriesMetric { + /// Metric name e.g "speech_probablity". The string value is not directly stored in the message, but referenced by index + /// in the `str_data` field of `MetricsBatch` + #[prost(uint32, tag="1")] + pub label: u32, + /// index into `str_data` + #[prost(uint32, tag="2")] + pub participant_identity: u32, + /// index into `str_data` + #[prost(uint32, tag="3")] + pub track_sid: u32, + #[prost(message, repeated, tag="4")] + pub samples: ::prost::alloc::vec::Vec, + /// index into 'str_data' + #[prost(uint32, tag="5")] + pub rid: u32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MetricSample { + /// time of metric based on a monotonic clock (in milliseconds) + #[prost(int64, tag="1")] + pub timestamp_ms: i64, + #[prost(message, optional, tag="2")] + pub normalized_timestamp: ::core::option::Option<::pbjson_types::Timestamp>, + #[prost(float, tag="3")] + pub value: f32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EventMetric { + #[prost(uint32, tag="1")] + pub label: u32, + /// index into `str_data` + #[prost(uint32, tag="2")] + pub participant_identity: u32, + /// index into `str_data` + #[prost(uint32, tag="3")] + pub track_sid: u32, + /// start time of event based on a monotonic clock (in milliseconds) + #[prost(int64, tag="4")] + pub start_timestamp_ms: i64, + /// end time of event based on a monotonic clock (in milliseconds), if needed + #[prost(int64, optional, tag="5")] + pub end_timestamp_ms: ::core::option::Option, + #[prost(message, optional, tag="6")] + pub normalized_start_timestamp: ::core::option::Option<::pbjson_types::Timestamp>, + #[prost(message, optional, tag="7")] + pub normalized_end_timestamp: ::core::option::Option<::pbjson_types::Timestamp>, + #[prost(string, tag="8")] + pub metadata: ::prost::alloc::string::String, + /// index into 'str_data' + #[prost(uint32, tag="9")] + pub rid: u32, +} +// +// Protocol used to record metrics for a specific session. +// +// Clients send their timestamp in their own monotonically increasing time (e.g `performance.now` on JS). +// These timestamps are then augmented by the SFU to its time base. +// +// A metric can be linked to a specific track by setting `track_sid`. + +/// index from [0: MAX_LABEL_PREDEFINED_MAX_VALUE) are for predefined labels (`MetricLabel`) +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum MetricLabel { + /// time to first token from LLM + AgentsLlmTtft = 0, + /// time to final transcription + AgentsSttTtft = 1, + /// time to first byte + AgentsTtsTtfb = 2, + /// Number of video freezes + ClientVideoSubscriberFreezeCount = 3, + /// total duration of freezes + ClientVideoSubscriberTotalFreezeDuration = 4, + /// number of video pauses + ClientVideoSubscriberPauseCount = 5, + /// total duration of pauses + ClientVideoSubscriberTotalPausesDuration = 6, + /// number of concealed (synthesized) audio samples + ClientAudioSubscriberConcealedSamples = 7, + /// number of silent concealed samples + ClientAudioSubscriberSilentConcealedSamples = 8, + /// number of concealment events + ClientAudioSubscriberConcealmentEvents = 9, + /// number of interruptions + ClientAudioSubscriberInterruptionCount = 10, + /// total duration of interruptions + ClientAudioSubscriberTotalInterruptionDuration = 11, + /// total time spent in jitter buffer + ClientSubscriberJitterBufferDelay = 12, + /// total time spent in jitter buffer + ClientSubscriberJitterBufferEmittedCount = 13, + /// total duration spent in bandwidth quality limitation + ClientVideoPublisherQualityLimitationDurationBandwidth = 14, + /// total duration spent in cpu quality limitation + ClientVideoPublisherQualityLimitationDurationCpu = 15, + /// total duration spent in other quality limitation + ClientVideoPublisherQualityLimitationDurationOther = 16, + PredefinedMaxValue = 4096, +} +impl MetricLabel { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + MetricLabel::AgentsLlmTtft => "AGENTS_LLM_TTFT", + MetricLabel::AgentsSttTtft => "AGENTS_STT_TTFT", + MetricLabel::AgentsTtsTtfb => "AGENTS_TTS_TTFB", + MetricLabel::ClientVideoSubscriberFreezeCount => "CLIENT_VIDEO_SUBSCRIBER_FREEZE_COUNT", + MetricLabel::ClientVideoSubscriberTotalFreezeDuration => "CLIENT_VIDEO_SUBSCRIBER_TOTAL_FREEZE_DURATION", + MetricLabel::ClientVideoSubscriberPauseCount => "CLIENT_VIDEO_SUBSCRIBER_PAUSE_COUNT", + MetricLabel::ClientVideoSubscriberTotalPausesDuration => "CLIENT_VIDEO_SUBSCRIBER_TOTAL_PAUSES_DURATION", + MetricLabel::ClientAudioSubscriberConcealedSamples => "CLIENT_AUDIO_SUBSCRIBER_CONCEALED_SAMPLES", + MetricLabel::ClientAudioSubscriberSilentConcealedSamples => "CLIENT_AUDIO_SUBSCRIBER_SILENT_CONCEALED_SAMPLES", + MetricLabel::ClientAudioSubscriberConcealmentEvents => "CLIENT_AUDIO_SUBSCRIBER_CONCEALMENT_EVENTS", + MetricLabel::ClientAudioSubscriberInterruptionCount => "CLIENT_AUDIO_SUBSCRIBER_INTERRUPTION_COUNT", + MetricLabel::ClientAudioSubscriberTotalInterruptionDuration => "CLIENT_AUDIO_SUBSCRIBER_TOTAL_INTERRUPTION_DURATION", + MetricLabel::ClientSubscriberJitterBufferDelay => "CLIENT_SUBSCRIBER_JITTER_BUFFER_DELAY", + MetricLabel::ClientSubscriberJitterBufferEmittedCount => "CLIENT_SUBSCRIBER_JITTER_BUFFER_EMITTED_COUNT", + MetricLabel::ClientVideoPublisherQualityLimitationDurationBandwidth => "CLIENT_VIDEO_PUBLISHER_QUALITY_LIMITATION_DURATION_BANDWIDTH", + MetricLabel::ClientVideoPublisherQualityLimitationDurationCpu => "CLIENT_VIDEO_PUBLISHER_QUALITY_LIMITATION_DURATION_CPU", + MetricLabel::ClientVideoPublisherQualityLimitationDurationOther => "CLIENT_VIDEO_PUBLISHER_QUALITY_LIMITATION_DURATION_OTHER", + MetricLabel::PredefinedMaxValue => "METRIC_LABEL_PREDEFINED_MAX_VALUE", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "AGENTS_LLM_TTFT" => Some(Self::AgentsLlmTtft), + "AGENTS_STT_TTFT" => Some(Self::AgentsSttTtft), + "AGENTS_TTS_TTFB" => Some(Self::AgentsTtsTtfb), + "CLIENT_VIDEO_SUBSCRIBER_FREEZE_COUNT" => Some(Self::ClientVideoSubscriberFreezeCount), + "CLIENT_VIDEO_SUBSCRIBER_TOTAL_FREEZE_DURATION" => Some(Self::ClientVideoSubscriberTotalFreezeDuration), + "CLIENT_VIDEO_SUBSCRIBER_PAUSE_COUNT" => Some(Self::ClientVideoSubscriberPauseCount), + "CLIENT_VIDEO_SUBSCRIBER_TOTAL_PAUSES_DURATION" => Some(Self::ClientVideoSubscriberTotalPausesDuration), + "CLIENT_AUDIO_SUBSCRIBER_CONCEALED_SAMPLES" => Some(Self::ClientAudioSubscriberConcealedSamples), + "CLIENT_AUDIO_SUBSCRIBER_SILENT_CONCEALED_SAMPLES" => Some(Self::ClientAudioSubscriberSilentConcealedSamples), + "CLIENT_AUDIO_SUBSCRIBER_CONCEALMENT_EVENTS" => Some(Self::ClientAudioSubscriberConcealmentEvents), + "CLIENT_AUDIO_SUBSCRIBER_INTERRUPTION_COUNT" => Some(Self::ClientAudioSubscriberInterruptionCount), + "CLIENT_AUDIO_SUBSCRIBER_TOTAL_INTERRUPTION_DURATION" => Some(Self::ClientAudioSubscriberTotalInterruptionDuration), + "CLIENT_SUBSCRIBER_JITTER_BUFFER_DELAY" => Some(Self::ClientSubscriberJitterBufferDelay), + "CLIENT_SUBSCRIBER_JITTER_BUFFER_EMITTED_COUNT" => Some(Self::ClientSubscriberJitterBufferEmittedCount), + "CLIENT_VIDEO_PUBLISHER_QUALITY_LIMITATION_DURATION_BANDWIDTH" => Some(Self::ClientVideoPublisherQualityLimitationDurationBandwidth), + "CLIENT_VIDEO_PUBLISHER_QUALITY_LIMITATION_DURATION_CPU" => Some(Self::ClientVideoPublisherQualityLimitationDurationCpu), + "CLIENT_VIDEO_PUBLISHER_QUALITY_LIMITATION_DURATION_OTHER" => Some(Self::ClientVideoPublisherQualityLimitationDurationOther), + "METRIC_LABEL_PREDEFINED_MAX_VALUE" => Some(Self::PredefinedMaxValue), + _ => None, + } + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct Room { #[prost(string, tag="1")] pub sid: ::prost::alloc::string::String, @@ -79,6 +257,9 @@ pub struct ParticipantPermission { #[deprecated] #[prost(bool, tag="11")] pub agent: bool, + /// if a participant can subscribe to metrics + #[prost(bool, tag="12")] + pub can_subscribe_metrics: bool, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -322,7 +503,7 @@ pub struct DataPacket { /// identities of participants who will receive the message (sent to all by default) #[prost(string, repeated, tag="5")] pub destination_identities: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, - #[prost(oneof="data_packet::Value", tags="2, 3, 6, 7")] + #[prost(oneof="data_packet::Value", tags="2, 3, 6, 7, 8, 9, 10, 11, 12")] pub value: ::core::option::Option, } /// Nested message and enum types in `DataPacket`. @@ -364,6 +545,16 @@ pub mod data_packet { SipDtmf(super::SipDtmf), #[prost(message, tag="7")] Transcription(super::Transcription), + #[prost(message, tag="8")] + Metrics(super::MetricsBatch), + #[prost(message, tag="9")] + ChatMessage(super::ChatMessage), + #[prost(message, tag="10")] + RpcRequest(super::RpcRequest), + #[prost(message, tag="11")] + RpcAck(super::RpcAck), + #[prost(message, tag="12")] + RpcResponse(super::RpcResponse), } } #[allow(clippy::derive_partial_eq_without_eq)] @@ -454,6 +645,75 @@ pub struct TranscriptionSegment { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct ChatMessage { + /// uuid + #[prost(string, tag="1")] + pub id: ::prost::alloc::string::String, + #[prost(int64, tag="2")] + pub timestamp: i64, + /// populated only if the intent is to edit/update an existing message + #[prost(int64, optional, tag="3")] + pub edit_timestamp: ::core::option::Option, + #[prost(string, tag="4")] + pub message: ::prost::alloc::string::String, + /// true to remove message + #[prost(bool, tag="5")] + pub deleted: bool, + /// true if the chat message has been generated by an agent from a participant's audio transcription + #[prost(bool, tag="6")] + pub generated: bool, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RpcRequest { + #[prost(string, tag="1")] + pub id: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub method: ::prost::alloc::string::String, + #[prost(string, tag="3")] + pub payload: ::prost::alloc::string::String, + #[prost(uint32, tag="4")] + pub response_timeout_ms: u32, + #[prost(uint32, tag="5")] + pub version: u32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RpcAck { + #[prost(string, tag="1")] + pub request_id: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RpcResponse { + #[prost(string, tag="1")] + pub request_id: ::prost::alloc::string::String, + #[prost(oneof="rpc_response::Value", tags="2, 3")] + pub value: ::core::option::Option, +} +/// Nested message and enum types in `RpcResponse`. +pub mod rpc_response { + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Value { + #[prost(string, tag="2")] + Payload(::prost::alloc::string::String), + #[prost(message, tag="3")] + Error(super::RpcError), + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RpcError { + #[prost(uint32, tag="1")] + pub code: u32, + #[prost(string, tag="2")] + pub message: ::prost::alloc::string::String, + #[prost(string, tag="3")] + pub data: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct ParticipantTracks { /// participant ID of participant to whom the tracks belong #[prost(string, tag="1")] @@ -535,6 +795,10 @@ pub struct ClientInfo { /// wifi, wired, cellular, vpn, empty if not known #[prost(string, tag="10")] pub network: ::prost::alloc::string::String, + /// comma separated list of additional LiveKit SDKs in use of this client, with versions + /// e.g. "components-js:1.2.3,track-processors-js:1.2.3" + #[prost(string, tag="11")] + pub other_sdks: ::prost::alloc::string::String, } /// Nested message and enum types in `ClientInfo`. pub mod client_info { @@ -552,6 +816,8 @@ pub mod client_info { Rust = 8, Python = 9, Cpp = 10, + UnityWeb = 11, + Node = 12, } impl Sdk { /// String value of the enum field names used in the ProtoBuf definition. @@ -571,6 +837,8 @@ pub mod client_info { Sdk::Rust => "RUST", Sdk::Python => "PYTHON", Sdk::Cpp => "CPP", + Sdk::UnityWeb => "UNITY_WEB", + Sdk::Node => "NODE", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -587,6 +855,8 @@ pub mod client_info { "RUST" => Some(Self::Rust), "PYTHON" => Some(Self::Python), "CPP" => Some(Self::Cpp), + "UNITY_WEB" => Some(Self::UnityWeb), + "NODE" => Some(Self::Node), _ => None, } } @@ -733,10 +1003,31 @@ pub struct RtpStats { #[prost(message, optional, tag="44")] pub packet_drift: ::core::option::Option, #[prost(message, optional, tag="45")] - pub report_drift: ::core::option::Option, - /// NEXT_ID: 47 + pub ntp_report_drift: ::core::option::Option, #[prost(message, optional, tag="46")] pub rebased_report_drift: ::core::option::Option, + /// NEXT_ID: 48 + #[prost(message, optional, tag="47")] + pub received_report_drift: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RtcpSenderReportState { + #[prost(uint32, tag="1")] + pub rtp_timestamp: u32, + #[prost(uint64, tag="2")] + pub rtp_timestamp_ext: u64, + #[prost(uint64, tag="3")] + pub ntp_timestamp: u64, + /// time at which this happened + #[prost(int64, tag="4")] + pub at: i64, + #[prost(int64, tag="5")] + pub at_adjusted: i64, + #[prost(uint32, tag="6")] + pub packets: u32, + #[prost(uint64, tag="7")] + pub octets: u64, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -753,6 +1044,8 @@ pub struct RtpForwarderState { pub dummy_start_timestamp_offset: u64, #[prost(message, optional, tag="6")] pub rtp_munger: ::core::option::Option, + #[prost(message, repeated, tag="8")] + pub sender_report_state: ::prost::alloc::vec::Vec, #[prost(oneof="rtp_forwarder_state::CodecMunger", tags="7")] pub codec_munger: ::core::option::Option, } @@ -2448,6 +2741,11 @@ pub struct JoinResponse { /// Server-Injected-Frame byte trailer, used to identify unencrypted frames when e2ee is enabled #[prost(bytes="vec", tag="13")] pub sif_trailer: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="14")] + pub enabled_publish_codecs: ::prost::alloc::vec::Vec, + /// when set, client should attempt to establish publish peer connection when joining room to speed up publishing + #[prost(bool, tag="15")] + pub fast_publish: bool, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -4165,6 +4463,12 @@ pub struct SipInboundTrunkInfo { pub auth_username: ::prost::alloc::string::String, #[prost(string, tag="8")] pub auth_password: ::prost::alloc::string::String, + /// Include these SIP X-* headers in 200 OK responses. + #[prost(map="string, string", tag="9")] + pub headers: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + /// Map SIP X-* headers from INVITE to SIP participant attributes. + #[prost(map="string, string", tag="10")] + pub headers_to_attributes: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -4200,6 +4504,38 @@ pub struct SipOutboundTrunkInfo { pub auth_username: ::prost::alloc::string::String, #[prost(string, tag="8")] pub auth_password: ::prost::alloc::string::String, + /// Include these SIP X-* headers in INVITE request. + /// These headers are sent as-is and may help identify this call as coming from LiveKit for the other SIP endpoint. + #[prost(map="string, string", tag="9")] + pub headers: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + /// Map SIP X-* headers from 200 OK to SIP participant attributes. + /// Keys are the names of X-* headers and values are the names of attributes they will be mapped to. + #[prost(map="string, string", tag="10")] + pub headers_to_attributes: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetSipInboundTrunkRequest { + #[prost(string, tag="1")] + pub sip_trunk_id: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetSipInboundTrunkResponse { + #[prost(message, optional, tag="1")] + pub trunk: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetSipOutboundTrunkRequest { + #[prost(string, tag="1")] + pub sip_trunk_id: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetSipOutboundTrunkResponse { + #[prost(message, optional, tag="1")] + pub trunk: ::core::option::Option, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -4259,8 +4595,21 @@ pub struct SipDispatchRuleIndividual { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct SipDispatchRuleCallee { + /// Prefix used on new room name + #[prost(string, tag="1")] + pub room_prefix: ::prost::alloc::string::String, + /// Optional pin required to enter room + #[prost(string, tag="2")] + pub pin: ::prost::alloc::string::String, + /// Optionally append random suffix + #[prost(bool, tag="3")] + pub randomize: bool, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct SipDispatchRule { - #[prost(oneof="sip_dispatch_rule::Rule", tags="1, 2")] + #[prost(oneof="sip_dispatch_rule::Rule", tags="1, 2, 3")] pub rule: ::core::option::Option, } /// Nested message and enum types in `SIPDispatchRule`. @@ -4276,6 +4625,9 @@ pub mod sip_dispatch_rule { /// SIPDispatchRuleIndividual is a `SIP Dispatch Rule` that creates a new room for each caller. #[prost(message, tag="2")] DispatchRuleIndividual(super::SipDispatchRuleIndividual), + /// SIPDispatchRuleCallee is a `SIP Dispatch Rule` that creates a new room for each callee. + #[prost(message, tag="3")] + DispatchRuleCallee(super::SipDispatchRuleCallee), } } #[allow(clippy::derive_partial_eq_without_eq)] @@ -4398,6 +4750,16 @@ pub struct SipParticipantInfo { #[prost(string, tag="4")] pub sip_call_id: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TransferSipParticipantRequest { + #[prost(string, tag="1")] + pub participant_identity: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub room_name: ::prost::alloc::string::String, + #[prost(string, tag="3")] + pub transfer_to: ::prost::alloc::string::String, +} #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] pub enum SipTransport { diff --git a/livekit-protocol/src/livekit.serde.rs b/livekit-protocol/src/livekit.serde.rs index 62e7c6904..4e6a0fb03 100644 --- a/livekit-protocol/src/livekit.serde.rs +++ b/livekit-protocol/src/livekit.serde.rs @@ -1911,6 +1911,195 @@ impl<'de> serde::Deserialize<'de> for CandidateProtocol { deserializer.deserialize_any(GeneratedVisitor) } } +impl serde::Serialize for ChatMessage { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.id.is_empty() { + len += 1; + } + if self.timestamp != 0 { + len += 1; + } + if self.edit_timestamp.is_some() { + len += 1; + } + if !self.message.is_empty() { + len += 1; + } + if self.deleted { + len += 1; + } + if self.generated { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.ChatMessage", len)?; + if !self.id.is_empty() { + struct_ser.serialize_field("id", &self.id)?; + } + if self.timestamp != 0 { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("timestamp", ToString::to_string(&self.timestamp).as_str())?; + } + if let Some(v) = self.edit_timestamp.as_ref() { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("editTimestamp", ToString::to_string(&v).as_str())?; + } + if !self.message.is_empty() { + struct_ser.serialize_field("message", &self.message)?; + } + if self.deleted { + struct_ser.serialize_field("deleted", &self.deleted)?; + } + if self.generated { + struct_ser.serialize_field("generated", &self.generated)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for ChatMessage { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "id", + "timestamp", + "edit_timestamp", + "editTimestamp", + "message", + "deleted", + "generated", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Id, + Timestamp, + EditTimestamp, + Message, + Deleted, + Generated, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "id" => Ok(GeneratedField::Id), + "timestamp" => Ok(GeneratedField::Timestamp), + "editTimestamp" | "edit_timestamp" => Ok(GeneratedField::EditTimestamp), + "message" => Ok(GeneratedField::Message), + "deleted" => Ok(GeneratedField::Deleted), + "generated" => Ok(GeneratedField::Generated), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = ChatMessage; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.ChatMessage") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut id__ = None; + let mut timestamp__ = None; + let mut edit_timestamp__ = None; + let mut message__ = None; + let mut deleted__ = None; + let mut generated__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Id => { + if id__.is_some() { + return Err(serde::de::Error::duplicate_field("id")); + } + id__ = Some(map_.next_value()?); + } + GeneratedField::Timestamp => { + if timestamp__.is_some() { + return Err(serde::de::Error::duplicate_field("timestamp")); + } + timestamp__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::EditTimestamp => { + if edit_timestamp__.is_some() { + return Err(serde::de::Error::duplicate_field("editTimestamp")); + } + edit_timestamp__ = + map_.next_value::<::std::option::Option<::pbjson::private::NumberDeserialize<_>>>()?.map(|x| x.0) + ; + } + GeneratedField::Message => { + if message__.is_some() { + return Err(serde::de::Error::duplicate_field("message")); + } + message__ = Some(map_.next_value()?); + } + GeneratedField::Deleted => { + if deleted__.is_some() { + return Err(serde::de::Error::duplicate_field("deleted")); + } + deleted__ = Some(map_.next_value()?); + } + GeneratedField::Generated => { + if generated__.is_some() { + return Err(serde::de::Error::duplicate_field("generated")); + } + generated__ = Some(map_.next_value()?); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(ChatMessage { + id: id__.unwrap_or_default(), + timestamp: timestamp__.unwrap_or_default(), + edit_timestamp: edit_timestamp__, + message: message__.unwrap_or_default(), + deleted: deleted__.unwrap_or_default(), + generated: generated__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.ChatMessage", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for ClientConfigSetting { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -2193,6 +2382,9 @@ impl serde::Serialize for ClientInfo { if !self.network.is_empty() { len += 1; } + if !self.other_sdks.is_empty() { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.ClientInfo", len)?; if self.sdk != 0 { let v = client_info::Sdk::try_from(self.sdk) @@ -2226,6 +2418,9 @@ impl serde::Serialize for ClientInfo { if !self.network.is_empty() { struct_ser.serialize_field("network", &self.network)?; } + if !self.other_sdks.is_empty() { + struct_ser.serialize_field("otherSdks", &self.other_sdks)?; + } struct_ser.end() } } @@ -2249,6 +2444,8 @@ impl<'de> serde::Deserialize<'de> for ClientInfo { "browserVersion", "address", "network", + "other_sdks", + "otherSdks", ]; #[allow(clippy::enum_variant_names)] @@ -2263,6 +2460,7 @@ impl<'de> serde::Deserialize<'de> for ClientInfo { BrowserVersion, Address, Network, + OtherSdks, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -2295,6 +2493,7 @@ impl<'de> serde::Deserialize<'de> for ClientInfo { "browserVersion" | "browser_version" => Ok(GeneratedField::BrowserVersion), "address" => Ok(GeneratedField::Address), "network" => Ok(GeneratedField::Network), + "otherSdks" | "other_sdks" => Ok(GeneratedField::OtherSdks), _ => Ok(GeneratedField::__SkipField__), } } @@ -2324,6 +2523,7 @@ impl<'de> serde::Deserialize<'de> for ClientInfo { let mut browser_version__ = None; let mut address__ = None; let mut network__ = None; + let mut other_sdks__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::Sdk => { @@ -2388,6 +2588,12 @@ impl<'de> serde::Deserialize<'de> for ClientInfo { } network__ = Some(map_.next_value()?); } + GeneratedField::OtherSdks => { + if other_sdks__.is_some() { + return Err(serde::de::Error::duplicate_field("otherSdks")); + } + other_sdks__ = Some(map_.next_value()?); + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -2404,6 +2610,7 @@ impl<'de> serde::Deserialize<'de> for ClientInfo { browser_version: browser_version__.unwrap_or_default(), address: address__.unwrap_or_default(), network: network__.unwrap_or_default(), + other_sdks: other_sdks__.unwrap_or_default(), }) } } @@ -2428,6 +2635,8 @@ impl serde::Serialize for client_info::Sdk { Self::Rust => "RUST", Self::Python => "PYTHON", Self::Cpp => "CPP", + Self::UnityWeb => "UNITY_WEB", + Self::Node => "NODE", }; serializer.serialize_str(variant) } @@ -2450,6 +2659,8 @@ impl<'de> serde::Deserialize<'de> for client_info::Sdk { "RUST", "PYTHON", "CPP", + "UNITY_WEB", + "NODE", ]; struct GeneratedVisitor; @@ -2501,6 +2712,8 @@ impl<'de> serde::Deserialize<'de> for client_info::Sdk { "RUST" => Ok(client_info::Sdk::Rust), "PYTHON" => Ok(client_info::Sdk::Python), "CPP" => Ok(client_info::Sdk::Cpp), + "UNITY_WEB" => Ok(client_info::Sdk::UnityWeb), + "NODE" => Ok(client_info::Sdk::Node), _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), } } @@ -4753,6 +4966,21 @@ impl serde::Serialize for DataPacket { data_packet::Value::Transcription(v) => { struct_ser.serialize_field("transcription", v)?; } + data_packet::Value::Metrics(v) => { + struct_ser.serialize_field("metrics", v)?; + } + data_packet::Value::ChatMessage(v) => { + struct_ser.serialize_field("chatMessage", v)?; + } + data_packet::Value::RpcRequest(v) => { + struct_ser.serialize_field("rpcRequest", v)?; + } + data_packet::Value::RpcAck(v) => { + struct_ser.serialize_field("rpcAck", v)?; + } + data_packet::Value::RpcResponse(v) => { + struct_ser.serialize_field("rpcResponse", v)?; + } } } struct_ser.end() @@ -4775,6 +5003,15 @@ impl<'de> serde::Deserialize<'de> for DataPacket { "sip_dtmf", "sipDtmf", "transcription", + "metrics", + "chat_message", + "chatMessage", + "rpc_request", + "rpcRequest", + "rpc_ack", + "rpcAck", + "rpc_response", + "rpcResponse", ]; #[allow(clippy::enum_variant_names)] @@ -4786,6 +5023,11 @@ impl<'de> serde::Deserialize<'de> for DataPacket { Speaker, SipDtmf, Transcription, + Metrics, + ChatMessage, + RpcRequest, + RpcAck, + RpcResponse, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -4815,6 +5057,11 @@ impl<'de> serde::Deserialize<'de> for DataPacket { "speaker" => Ok(GeneratedField::Speaker), "sipDtmf" | "sip_dtmf" => Ok(GeneratedField::SipDtmf), "transcription" => Ok(GeneratedField::Transcription), + "metrics" => Ok(GeneratedField::Metrics), + "chatMessage" | "chat_message" => Ok(GeneratedField::ChatMessage), + "rpcRequest" | "rpc_request" => Ok(GeneratedField::RpcRequest), + "rpcAck" | "rpc_ack" => Ok(GeneratedField::RpcAck), + "rpcResponse" | "rpc_response" => Ok(GeneratedField::RpcResponse), _ => Ok(GeneratedField::__SkipField__), } } @@ -4884,6 +5131,41 @@ impl<'de> serde::Deserialize<'de> for DataPacket { return Err(serde::de::Error::duplicate_field("transcription")); } value__ = map_.next_value::<::std::option::Option<_>>()?.map(data_packet::Value::Transcription) +; + } + GeneratedField::Metrics => { + if value__.is_some() { + return Err(serde::de::Error::duplicate_field("metrics")); + } + value__ = map_.next_value::<::std::option::Option<_>>()?.map(data_packet::Value::Metrics) +; + } + GeneratedField::ChatMessage => { + if value__.is_some() { + return Err(serde::de::Error::duplicate_field("chatMessage")); + } + value__ = map_.next_value::<::std::option::Option<_>>()?.map(data_packet::Value::ChatMessage) +; + } + GeneratedField::RpcRequest => { + if value__.is_some() { + return Err(serde::de::Error::duplicate_field("rpcRequest")); + } + value__ = map_.next_value::<::std::option::Option<_>>()?.map(data_packet::Value::RpcRequest) +; + } + GeneratedField::RpcAck => { + if value__.is_some() { + return Err(serde::de::Error::duplicate_field("rpcAck")); + } + value__ = map_.next_value::<::std::option::Option<_>>()?.map(data_packet::Value::RpcAck) +; + } + GeneratedField::RpcResponse => { + if value__.is_some() { + return Err(serde::de::Error::duplicate_field("rpcResponse")); + } + value__ = map_.next_value::<::std::option::Option<_>>()?.map(data_packet::Value::RpcResponse) ; } GeneratedField::__SkipField__ => { @@ -7295,7 +7577,7 @@ impl<'de> serde::Deserialize<'de> for encryption::Type { deserializer.deserialize_any(GeneratedVisitor) } } -impl serde::Serialize for FileInfo { +impl serde::Serialize for EventMetric { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -7303,79 +7585,103 @@ impl serde::Serialize for FileInfo { { use serde::ser::SerializeStruct; let mut len = 0; - if !self.filename.is_empty() { + if self.label != 0 { len += 1; } - if self.started_at != 0 { + if self.participant_identity != 0 { len += 1; } - if self.ended_at != 0 { + if self.track_sid != 0 { len += 1; } - if self.duration != 0 { + if self.start_timestamp_ms != 0 { len += 1; } - if self.size != 0 { + if self.end_timestamp_ms.is_some() { len += 1; } - if !self.location.is_empty() { + if self.normalized_start_timestamp.is_some() { len += 1; } - let mut struct_ser = serializer.serialize_struct("livekit.FileInfo", len)?; - if !self.filename.is_empty() { - struct_ser.serialize_field("filename", &self.filename)?; + if self.normalized_end_timestamp.is_some() { + len += 1; } - if self.started_at != 0 { - #[allow(clippy::needless_borrow)] - #[allow(clippy::needless_borrows_for_generic_args)] - struct_ser.serialize_field("startedAt", ToString::to_string(&self.started_at).as_str())?; + if !self.metadata.is_empty() { + len += 1; } - if self.ended_at != 0 { - #[allow(clippy::needless_borrow)] - #[allow(clippy::needless_borrows_for_generic_args)] - struct_ser.serialize_field("endedAt", ToString::to_string(&self.ended_at).as_str())?; + if self.rid != 0 { + len += 1; } - if self.duration != 0 { + let mut struct_ser = serializer.serialize_struct("livekit.EventMetric", len)?; + if self.label != 0 { + struct_ser.serialize_field("label", &self.label)?; + } + if self.participant_identity != 0 { + struct_ser.serialize_field("participantIdentity", &self.participant_identity)?; + } + if self.track_sid != 0 { + struct_ser.serialize_field("trackSid", &self.track_sid)?; + } + if self.start_timestamp_ms != 0 { #[allow(clippy::needless_borrow)] #[allow(clippy::needless_borrows_for_generic_args)] - struct_ser.serialize_field("duration", ToString::to_string(&self.duration).as_str())?; + struct_ser.serialize_field("startTimestampMs", ToString::to_string(&self.start_timestamp_ms).as_str())?; } - if self.size != 0 { + if let Some(v) = self.end_timestamp_ms.as_ref() { #[allow(clippy::needless_borrow)] #[allow(clippy::needless_borrows_for_generic_args)] - struct_ser.serialize_field("size", ToString::to_string(&self.size).as_str())?; + struct_ser.serialize_field("endTimestampMs", ToString::to_string(&v).as_str())?; } - if !self.location.is_empty() { - struct_ser.serialize_field("location", &self.location)?; + if let Some(v) = self.normalized_start_timestamp.as_ref() { + struct_ser.serialize_field("normalizedStartTimestamp", v)?; + } + if let Some(v) = self.normalized_end_timestamp.as_ref() { + struct_ser.serialize_field("normalizedEndTimestamp", v)?; + } + if !self.metadata.is_empty() { + struct_ser.serialize_field("metadata", &self.metadata)?; + } + if self.rid != 0 { + struct_ser.serialize_field("rid", &self.rid)?; } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for FileInfo { +impl<'de> serde::Deserialize<'de> for EventMetric { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "filename", - "started_at", - "startedAt", - "ended_at", - "endedAt", - "duration", - "size", - "location", + "label", + "participant_identity", + "participantIdentity", + "track_sid", + "trackSid", + "start_timestamp_ms", + "startTimestampMs", + "end_timestamp_ms", + "endTimestampMs", + "normalized_start_timestamp", + "normalizedStartTimestamp", + "normalized_end_timestamp", + "normalizedEndTimestamp", + "metadata", + "rid", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { - Filename, - StartedAt, - EndedAt, - Duration, - Size, - Location, + Label, + ParticipantIdentity, + TrackSid, + StartTimestampMs, + EndTimestampMs, + NormalizedStartTimestamp, + NormalizedEndTimestamp, + Metadata, + Rid, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -7398,12 +7704,15 @@ impl<'de> serde::Deserialize<'de> for FileInfo { E: serde::de::Error, { match value { - "filename" => Ok(GeneratedField::Filename), - "startedAt" | "started_at" => Ok(GeneratedField::StartedAt), - "endedAt" | "ended_at" => Ok(GeneratedField::EndedAt), - "duration" => Ok(GeneratedField::Duration), - "size" => Ok(GeneratedField::Size), - "location" => Ok(GeneratedField::Location), + "label" => Ok(GeneratedField::Label), + "participantIdentity" | "participant_identity" => Ok(GeneratedField::ParticipantIdentity), + "trackSid" | "track_sid" => Ok(GeneratedField::TrackSid), + "startTimestampMs" | "start_timestamp_ms" => Ok(GeneratedField::StartTimestampMs), + "endTimestampMs" | "end_timestamp_ms" => Ok(GeneratedField::EndTimestampMs), + "normalizedStartTimestamp" | "normalized_start_timestamp" => Ok(GeneratedField::NormalizedStartTimestamp), + "normalizedEndTimestamp" | "normalized_end_timestamp" => Ok(GeneratedField::NormalizedEndTimestamp), + "metadata" => Ok(GeneratedField::Metadata), + "rid" => Ok(GeneratedField::Rid), _ => Ok(GeneratedField::__SkipField__), } } @@ -7413,21 +7722,247 @@ impl<'de> serde::Deserialize<'de> for FileInfo { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = FileInfo; + type Value = EventMetric; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.FileInfo") + formatter.write_str("struct livekit.EventMetric") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut filename__ = None; - let mut started_at__ = None; - let mut ended_at__ = None; - let mut duration__ = None; - let mut size__ = None; + let mut label__ = None; + let mut participant_identity__ = None; + let mut track_sid__ = None; + let mut start_timestamp_ms__ = None; + let mut end_timestamp_ms__ = None; + let mut normalized_start_timestamp__ = None; + let mut normalized_end_timestamp__ = None; + let mut metadata__ = None; + let mut rid__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Label => { + if label__.is_some() { + return Err(serde::de::Error::duplicate_field("label")); + } + label__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::ParticipantIdentity => { + if participant_identity__.is_some() { + return Err(serde::de::Error::duplicate_field("participantIdentity")); + } + participant_identity__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::TrackSid => { + if track_sid__.is_some() { + return Err(serde::de::Error::duplicate_field("trackSid")); + } + track_sid__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::StartTimestampMs => { + if start_timestamp_ms__.is_some() { + return Err(serde::de::Error::duplicate_field("startTimestampMs")); + } + start_timestamp_ms__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::EndTimestampMs => { + if end_timestamp_ms__.is_some() { + return Err(serde::de::Error::duplicate_field("endTimestampMs")); + } + end_timestamp_ms__ = + map_.next_value::<::std::option::Option<::pbjson::private::NumberDeserialize<_>>>()?.map(|x| x.0) + ; + } + GeneratedField::NormalizedStartTimestamp => { + if normalized_start_timestamp__.is_some() { + return Err(serde::de::Error::duplicate_field("normalizedStartTimestamp")); + } + normalized_start_timestamp__ = map_.next_value()?; + } + GeneratedField::NormalizedEndTimestamp => { + if normalized_end_timestamp__.is_some() { + return Err(serde::de::Error::duplicate_field("normalizedEndTimestamp")); + } + normalized_end_timestamp__ = map_.next_value()?; + } + GeneratedField::Metadata => { + if metadata__.is_some() { + return Err(serde::de::Error::duplicate_field("metadata")); + } + metadata__ = Some(map_.next_value()?); + } + GeneratedField::Rid => { + if rid__.is_some() { + return Err(serde::de::Error::duplicate_field("rid")); + } + rid__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(EventMetric { + label: label__.unwrap_or_default(), + participant_identity: participant_identity__.unwrap_or_default(), + track_sid: track_sid__.unwrap_or_default(), + start_timestamp_ms: start_timestamp_ms__.unwrap_or_default(), + end_timestamp_ms: end_timestamp_ms__, + normalized_start_timestamp: normalized_start_timestamp__, + normalized_end_timestamp: normalized_end_timestamp__, + metadata: metadata__.unwrap_or_default(), + rid: rid__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.EventMetric", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for FileInfo { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.filename.is_empty() { + len += 1; + } + if self.started_at != 0 { + len += 1; + } + if self.ended_at != 0 { + len += 1; + } + if self.duration != 0 { + len += 1; + } + if self.size != 0 { + len += 1; + } + if !self.location.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.FileInfo", len)?; + if !self.filename.is_empty() { + struct_ser.serialize_field("filename", &self.filename)?; + } + if self.started_at != 0 { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("startedAt", ToString::to_string(&self.started_at).as_str())?; + } + if self.ended_at != 0 { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("endedAt", ToString::to_string(&self.ended_at).as_str())?; + } + if self.duration != 0 { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("duration", ToString::to_string(&self.duration).as_str())?; + } + if self.size != 0 { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("size", ToString::to_string(&self.size).as_str())?; + } + if !self.location.is_empty() { + struct_ser.serialize_field("location", &self.location)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for FileInfo { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "filename", + "started_at", + "startedAt", + "ended_at", + "endedAt", + "duration", + "size", + "location", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Filename, + StartedAt, + EndedAt, + Duration, + Size, + Location, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "filename" => Ok(GeneratedField::Filename), + "startedAt" | "started_at" => Ok(GeneratedField::StartedAt), + "endedAt" | "ended_at" => Ok(GeneratedField::EndedAt), + "duration" => Ok(GeneratedField::Duration), + "size" => Ok(GeneratedField::Size), + "location" => Ok(GeneratedField::Location), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = FileInfo; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.FileInfo") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut filename__ = None; + let mut started_at__ = None; + let mut ended_at__ = None; + let mut duration__ = None; + let mut size__ = None; let mut location__ = None; while let Some(k) = map_.next_key()? { match k { @@ -7622,7 +8157,7 @@ impl<'de> serde::Deserialize<'de> for GcpUpload { deserializer.deserialize_struct("livekit.GCPUpload", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for IceServer { +impl serde::Serialize for GetSipInboundTrunkRequest { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -7630,45 +8165,30 @@ impl serde::Serialize for IceServer { { use serde::ser::SerializeStruct; let mut len = 0; - if !self.urls.is_empty() { - len += 1; - } - if !self.username.is_empty() { - len += 1; - } - if !self.credential.is_empty() { + if !self.sip_trunk_id.is_empty() { len += 1; } - let mut struct_ser = serializer.serialize_struct("livekit.ICEServer", len)?; - if !self.urls.is_empty() { - struct_ser.serialize_field("urls", &self.urls)?; - } - if !self.username.is_empty() { - struct_ser.serialize_field("username", &self.username)?; - } - if !self.credential.is_empty() { - struct_ser.serialize_field("credential", &self.credential)?; + let mut struct_ser = serializer.serialize_struct("livekit.GetSIPInboundTrunkRequest", len)?; + if !self.sip_trunk_id.is_empty() { + struct_ser.serialize_field("sipTrunkId", &self.sip_trunk_id)?; } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for IceServer { +impl<'de> serde::Deserialize<'de> for GetSipInboundTrunkRequest { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "urls", - "username", - "credential", + "sip_trunk_id", + "sipTrunkId", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { - Urls, - Username, - Credential, + SipTrunkId, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -7691,9 +8211,7 @@ impl<'de> serde::Deserialize<'de> for IceServer { E: serde::de::Error, { match value { - "urls" => Ok(GeneratedField::Urls), - "username" => Ok(GeneratedField::Username), - "credential" => Ok(GeneratedField::Credential), + "sipTrunkId" | "sip_trunk_id" => Ok(GeneratedField::SipTrunkId), _ => Ok(GeneratedField::__SkipField__), } } @@ -7703,197 +8221,230 @@ impl<'de> serde::Deserialize<'de> for IceServer { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = IceServer; + type Value = GetSipInboundTrunkRequest; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.ICEServer") + formatter.write_str("struct livekit.GetSIPInboundTrunkRequest") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut urls__ = None; - let mut username__ = None; - let mut credential__ = None; + let mut sip_trunk_id__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::Urls => { - if urls__.is_some() { - return Err(serde::de::Error::duplicate_field("urls")); + GeneratedField::SipTrunkId => { + if sip_trunk_id__.is_some() { + return Err(serde::de::Error::duplicate_field("sipTrunkId")); } - urls__ = Some(map_.next_value()?); + sip_trunk_id__ = Some(map_.next_value()?); } - GeneratedField::Username => { - if username__.is_some() { - return Err(serde::de::Error::duplicate_field("username")); - } - username__ = Some(map_.next_value()?); - } - GeneratedField::Credential => { - if credential__.is_some() { - return Err(serde::de::Error::duplicate_field("credential")); - } - credential__ = Some(map_.next_value()?); - } - GeneratedField::__SkipField__ => { - let _ = map_.next_value::()?; + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; } } } - Ok(IceServer { - urls: urls__.unwrap_or_default(), - username: username__.unwrap_or_default(), - credential: credential__.unwrap_or_default(), + Ok(GetSipInboundTrunkRequest { + sip_trunk_id: sip_trunk_id__.unwrap_or_default(), }) } } - deserializer.deserialize_struct("livekit.ICEServer", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.GetSIPInboundTrunkRequest", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for ImageCodec { +impl serde::Serialize for GetSipInboundTrunkResponse { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where S: serde::Serializer, { - let variant = match self { - Self::IcDefault => "IC_DEFAULT", - Self::IcJpeg => "IC_JPEG", - }; - serializer.serialize_str(variant) + use serde::ser::SerializeStruct; + let mut len = 0; + if self.trunk.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.GetSIPInboundTrunkResponse", len)?; + if let Some(v) = self.trunk.as_ref() { + struct_ser.serialize_field("trunk", v)?; + } + struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for ImageCodec { +impl<'de> serde::Deserialize<'de> for GetSipInboundTrunkResponse { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "IC_DEFAULT", - "IC_JPEG", + "trunk", ]; - struct GeneratedVisitor; + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Trunk, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; - impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = ImageCodec; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(formatter, "expected one of: {:?}", &FIELDS) - } + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } - fn visit_i64(self, v: i64) -> std::result::Result - where - E: serde::de::Error, - { - i32::try_from(v) - .ok() - .and_then(|x| x.try_into().ok()) - .ok_or_else(|| { - serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self) - }) + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "trunk" => Ok(GeneratedField::Trunk), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GetSipInboundTrunkResponse; - fn visit_u64(self, v: u64) -> std::result::Result - where - E: serde::de::Error, - { - i32::try_from(v) - .ok() - .and_then(|x| x.try_into().ok()) - .ok_or_else(|| { - serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(v), &self) - }) + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.GetSIPInboundTrunkResponse") } - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, { - match value { - "IC_DEFAULT" => Ok(ImageCodec::IcDefault), - "IC_JPEG" => Ok(ImageCodec::IcJpeg), - _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), + let mut trunk__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Trunk => { + if trunk__.is_some() { + return Err(serde::de::Error::duplicate_field("trunk")); + } + trunk__ = map_.next_value()?; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } } + Ok(GetSipInboundTrunkResponse { + trunk: trunk__, + }) } } - deserializer.deserialize_any(GeneratedVisitor) + deserializer.deserialize_struct("livekit.GetSIPInboundTrunkResponse", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for ImageFileSuffix { +impl serde::Serialize for GetSipOutboundTrunkRequest { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where S: serde::Serializer, { - let variant = match self { - Self::ImageSuffixIndex => "IMAGE_SUFFIX_INDEX", - Self::ImageSuffixTimestamp => "IMAGE_SUFFIX_TIMESTAMP", - }; - serializer.serialize_str(variant) + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.sip_trunk_id.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.GetSIPOutboundTrunkRequest", len)?; + if !self.sip_trunk_id.is_empty() { + struct_ser.serialize_field("sipTrunkId", &self.sip_trunk_id)?; + } + struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for ImageFileSuffix { +impl<'de> serde::Deserialize<'de> for GetSipOutboundTrunkRequest { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "IMAGE_SUFFIX_INDEX", - "IMAGE_SUFFIX_TIMESTAMP", + "sip_trunk_id", + "sipTrunkId", ]; - struct GeneratedVisitor; + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + SipTrunkId, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; - impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = ImageFileSuffix; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(formatter, "expected one of: {:?}", &FIELDS) - } + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } - fn visit_i64(self, v: i64) -> std::result::Result - where - E: serde::de::Error, - { - i32::try_from(v) - .ok() - .and_then(|x| x.try_into().ok()) - .ok_or_else(|| { - serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self) - }) + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "sipTrunkId" | "sip_trunk_id" => Ok(GeneratedField::SipTrunkId), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GetSipOutboundTrunkRequest; - fn visit_u64(self, v: u64) -> std::result::Result - where - E: serde::de::Error, - { - i32::try_from(v) - .ok() - .and_then(|x| x.try_into().ok()) - .ok_or_else(|| { - serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(v), &self) - }) + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.GetSIPOutboundTrunkRequest") } - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, { - match value { - "IMAGE_SUFFIX_INDEX" => Ok(ImageFileSuffix::ImageSuffixIndex), - "IMAGE_SUFFIX_TIMESTAMP" => Ok(ImageFileSuffix::ImageSuffixTimestamp), - _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), + let mut sip_trunk_id__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::SipTrunkId => { + if sip_trunk_id__.is_some() { + return Err(serde::de::Error::duplicate_field("sipTrunkId")); + } + sip_trunk_id__ = Some(map_.next_value()?); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } } + Ok(GetSipOutboundTrunkRequest { + sip_trunk_id: sip_trunk_id__.unwrap_or_default(), + }) } } - deserializer.deserialize_any(GeneratedVisitor) + deserializer.deserialize_struct("livekit.GetSIPOutboundTrunkRequest", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for ImageOutput { +impl serde::Serialize for GetSipOutboundTrunkResponse { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -7901,114 +8452,30 @@ impl serde::Serialize for ImageOutput { { use serde::ser::SerializeStruct; let mut len = 0; - if self.capture_interval != 0 { - len += 1; - } - if self.width != 0 { - len += 1; - } - if self.height != 0 { - len += 1; - } - if !self.filename_prefix.is_empty() { - len += 1; - } - if self.filename_suffix != 0 { - len += 1; - } - if self.image_codec != 0 { + if self.trunk.is_some() { len += 1; } - if self.disable_manifest { - len += 1; + let mut struct_ser = serializer.serialize_struct("livekit.GetSIPOutboundTrunkResponse", len)?; + if let Some(v) = self.trunk.as_ref() { + struct_ser.serialize_field("trunk", v)?; } - if self.output.is_some() { - len += 1; - } - let mut struct_ser = serializer.serialize_struct("livekit.ImageOutput", len)?; - if self.capture_interval != 0 { - struct_ser.serialize_field("captureInterval", &self.capture_interval)?; - } - if self.width != 0 { - struct_ser.serialize_field("width", &self.width)?; - } - if self.height != 0 { - struct_ser.serialize_field("height", &self.height)?; - } - if !self.filename_prefix.is_empty() { - struct_ser.serialize_field("filenamePrefix", &self.filename_prefix)?; - } - if self.filename_suffix != 0 { - let v = ImageFileSuffix::try_from(self.filename_suffix) - .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.filename_suffix)))?; - struct_ser.serialize_field("filenameSuffix", &v)?; - } - if self.image_codec != 0 { - let v = ImageCodec::try_from(self.image_codec) - .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.image_codec)))?; - struct_ser.serialize_field("imageCodec", &v)?; - } - if self.disable_manifest { - struct_ser.serialize_field("disableManifest", &self.disable_manifest)?; - } - if let Some(v) = self.output.as_ref() { - match v { - image_output::Output::S3(v) => { - struct_ser.serialize_field("s3", v)?; - } - image_output::Output::Gcp(v) => { - struct_ser.serialize_field("gcp", v)?; - } - image_output::Output::Azure(v) => { - struct_ser.serialize_field("azure", v)?; - } - image_output::Output::AliOss(v) => { - struct_ser.serialize_field("aliOSS", v)?; - } - } - } - struct_ser.end() - } -} -impl<'de> serde::Deserialize<'de> for ImageOutput { - #[allow(deprecated)] - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - const FIELDS: &[&str] = &[ - "capture_interval", - "captureInterval", - "width", - "height", - "filename_prefix", - "filenamePrefix", - "filename_suffix", - "filenameSuffix", - "image_codec", - "imageCodec", - "disable_manifest", - "disableManifest", - "s3", - "gcp", - "azure", - "aliOSS", - ]; - - #[allow(clippy::enum_variant_names)] - enum GeneratedField { - CaptureInterval, - Width, - Height, - FilenamePrefix, - FilenameSuffix, - ImageCodec, - DisableManifest, - S3, - Gcp, - Azure, - AliOss, - __SkipField__, + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for GetSipOutboundTrunkResponse { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "trunk", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Trunk, + __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { fn deserialize(deserializer: D) -> std::result::Result @@ -8030,17 +8497,7 @@ impl<'de> serde::Deserialize<'de> for ImageOutput { E: serde::de::Error, { match value { - "captureInterval" | "capture_interval" => Ok(GeneratedField::CaptureInterval), - "width" => Ok(GeneratedField::Width), - "height" => Ok(GeneratedField::Height), - "filenamePrefix" | "filename_prefix" => Ok(GeneratedField::FilenamePrefix), - "filenameSuffix" | "filename_suffix" => Ok(GeneratedField::FilenameSuffix), - "imageCodec" | "image_codec" => Ok(GeneratedField::ImageCodec), - "disableManifest" | "disable_manifest" => Ok(GeneratedField::DisableManifest), - "s3" => Ok(GeneratedField::S3), - "gcp" => Ok(GeneratedField::Gcp), - "azure" => Ok(GeneratedField::Azure), - "aliOSS" => Ok(GeneratedField::AliOss), + "trunk" => Ok(GeneratedField::Trunk), _ => Ok(GeneratedField::__SkipField__), } } @@ -8050,123 +8507,39 @@ impl<'de> serde::Deserialize<'de> for ImageOutput { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = ImageOutput; + type Value = GetSipOutboundTrunkResponse; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.ImageOutput") + formatter.write_str("struct livekit.GetSIPOutboundTrunkResponse") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut capture_interval__ = None; - let mut width__ = None; - let mut height__ = None; - let mut filename_prefix__ = None; - let mut filename_suffix__ = None; - let mut image_codec__ = None; - let mut disable_manifest__ = None; - let mut output__ = None; + let mut trunk__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::CaptureInterval => { - if capture_interval__.is_some() { - return Err(serde::de::Error::duplicate_field("captureInterval")); - } - capture_interval__ = - Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; - } - GeneratedField::Width => { - if width__.is_some() { - return Err(serde::de::Error::duplicate_field("width")); - } - width__ = - Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; - } - GeneratedField::Height => { - if height__.is_some() { - return Err(serde::de::Error::duplicate_field("height")); - } - height__ = - Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; - } - GeneratedField::FilenamePrefix => { - if filename_prefix__.is_some() { - return Err(serde::de::Error::duplicate_field("filenamePrefix")); - } - filename_prefix__ = Some(map_.next_value()?); - } - GeneratedField::FilenameSuffix => { - if filename_suffix__.is_some() { - return Err(serde::de::Error::duplicate_field("filenameSuffix")); - } - filename_suffix__ = Some(map_.next_value::()? as i32); - } - GeneratedField::ImageCodec => { - if image_codec__.is_some() { - return Err(serde::de::Error::duplicate_field("imageCodec")); - } - image_codec__ = Some(map_.next_value::()? as i32); - } - GeneratedField::DisableManifest => { - if disable_manifest__.is_some() { - return Err(serde::de::Error::duplicate_field("disableManifest")); - } - disable_manifest__ = Some(map_.next_value()?); - } - GeneratedField::S3 => { - if output__.is_some() { - return Err(serde::de::Error::duplicate_field("s3")); - } - output__ = map_.next_value::<::std::option::Option<_>>()?.map(image_output::Output::S3) -; - } - GeneratedField::Gcp => { - if output__.is_some() { - return Err(serde::de::Error::duplicate_field("gcp")); - } - output__ = map_.next_value::<::std::option::Option<_>>()?.map(image_output::Output::Gcp) -; - } - GeneratedField::Azure => { - if output__.is_some() { - return Err(serde::de::Error::duplicate_field("azure")); - } - output__ = map_.next_value::<::std::option::Option<_>>()?.map(image_output::Output::Azure) -; - } - GeneratedField::AliOss => { - if output__.is_some() { - return Err(serde::de::Error::duplicate_field("aliOSS")); + GeneratedField::Trunk => { + if trunk__.is_some() { + return Err(serde::de::Error::duplicate_field("trunk")); } - output__ = map_.next_value::<::std::option::Option<_>>()?.map(image_output::Output::AliOss) -; + trunk__ = map_.next_value()?; } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } } } - Ok(ImageOutput { - capture_interval: capture_interval__.unwrap_or_default(), - width: width__.unwrap_or_default(), - height: height__.unwrap_or_default(), - filename_prefix: filename_prefix__.unwrap_or_default(), - filename_suffix: filename_suffix__.unwrap_or_default(), - image_codec: image_codec__.unwrap_or_default(), - disable_manifest: disable_manifest__.unwrap_or_default(), - output: output__, + Ok(GetSipOutboundTrunkResponse { + trunk: trunk__, }) } } - deserializer.deserialize_struct("livekit.ImageOutput", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.GetSIPOutboundTrunkResponse", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for ImagesInfo { +impl serde::Serialize for IceServer { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -8174,63 +8547,45 @@ impl serde::Serialize for ImagesInfo { { use serde::ser::SerializeStruct; let mut len = 0; - if !self.filename_prefix.is_empty() { - len += 1; - } - if self.image_count != 0 { + if !self.urls.is_empty() { len += 1; } - if self.started_at != 0 { + if !self.username.is_empty() { len += 1; } - if self.ended_at != 0 { + if !self.credential.is_empty() { len += 1; } - let mut struct_ser = serializer.serialize_struct("livekit.ImagesInfo", len)?; - if !self.filename_prefix.is_empty() { - struct_ser.serialize_field("filenamePrefix", &self.filename_prefix)?; - } - if self.image_count != 0 { - #[allow(clippy::needless_borrow)] - #[allow(clippy::needless_borrows_for_generic_args)] - struct_ser.serialize_field("imageCount", ToString::to_string(&self.image_count).as_str())?; + let mut struct_ser = serializer.serialize_struct("livekit.ICEServer", len)?; + if !self.urls.is_empty() { + struct_ser.serialize_field("urls", &self.urls)?; } - if self.started_at != 0 { - #[allow(clippy::needless_borrow)] - #[allow(clippy::needless_borrows_for_generic_args)] - struct_ser.serialize_field("startedAt", ToString::to_string(&self.started_at).as_str())?; + if !self.username.is_empty() { + struct_ser.serialize_field("username", &self.username)?; } - if self.ended_at != 0 { - #[allow(clippy::needless_borrow)] - #[allow(clippy::needless_borrows_for_generic_args)] - struct_ser.serialize_field("endedAt", ToString::to_string(&self.ended_at).as_str())?; + if !self.credential.is_empty() { + struct_ser.serialize_field("credential", &self.credential)?; } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for ImagesInfo { +impl<'de> serde::Deserialize<'de> for IceServer { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "filename_prefix", - "filenamePrefix", - "image_count", - "imageCount", - "started_at", - "startedAt", - "ended_at", - "endedAt", + "urls", + "username", + "credential", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { - FilenamePrefix, - ImageCount, - StartedAt, - EndedAt, + Urls, + Username, + Credential, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -8253,10 +8608,9 @@ impl<'de> serde::Deserialize<'de> for ImagesInfo { E: serde::de::Error, { match value { - "filenamePrefix" | "filename_prefix" => Ok(GeneratedField::FilenamePrefix), - "imageCount" | "image_count" => Ok(GeneratedField::ImageCount), - "startedAt" | "started_at" => Ok(GeneratedField::StartedAt), - "endedAt" | "ended_at" => Ok(GeneratedField::EndedAt), + "urls" => Ok(GeneratedField::Urls), + "username" => Ok(GeneratedField::Username), + "credential" => Ok(GeneratedField::Credential), _ => Ok(GeneratedField::__SkipField__), } } @@ -8266,250 +8620,153 @@ impl<'de> serde::Deserialize<'de> for ImagesInfo { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = ImagesInfo; + type Value = IceServer; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.ImagesInfo") + formatter.write_str("struct livekit.ICEServer") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut filename_prefix__ = None; - let mut image_count__ = None; - let mut started_at__ = None; - let mut ended_at__ = None; + let mut urls__ = None; + let mut username__ = None; + let mut credential__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::FilenamePrefix => { - if filename_prefix__.is_some() { - return Err(serde::de::Error::duplicate_field("filenamePrefix")); - } - filename_prefix__ = Some(map_.next_value()?); - } - GeneratedField::ImageCount => { - if image_count__.is_some() { - return Err(serde::de::Error::duplicate_field("imageCount")); + GeneratedField::Urls => { + if urls__.is_some() { + return Err(serde::de::Error::duplicate_field("urls")); } - image_count__ = - Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; + urls__ = Some(map_.next_value()?); } - GeneratedField::StartedAt => { - if started_at__.is_some() { - return Err(serde::de::Error::duplicate_field("startedAt")); + GeneratedField::Username => { + if username__.is_some() { + return Err(serde::de::Error::duplicate_field("username")); } - started_at__ = - Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; + username__ = Some(map_.next_value()?); } - GeneratedField::EndedAt => { - if ended_at__.is_some() { - return Err(serde::de::Error::duplicate_field("endedAt")); + GeneratedField::Credential => { + if credential__.is_some() { + return Err(serde::de::Error::duplicate_field("credential")); } - ended_at__ = - Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; + credential__ = Some(map_.next_value()?); } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } } } - Ok(ImagesInfo { - filename_prefix: filename_prefix__.unwrap_or_default(), - image_count: image_count__.unwrap_or_default(), - started_at: started_at__.unwrap_or_default(), - ended_at: ended_at__.unwrap_or_default(), + Ok(IceServer { + urls: urls__.unwrap_or_default(), + username: username__.unwrap_or_default(), + credential: credential__.unwrap_or_default(), }) } } - deserializer.deserialize_struct("livekit.ImagesInfo", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.ICEServer", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for IngressAudioEncodingOptions { +impl serde::Serialize for ImageCodec { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where S: serde::Serializer, { - use serde::ser::SerializeStruct; - let mut len = 0; - if self.audio_codec != 0 { - len += 1; - } - if self.bitrate != 0 { - len += 1; - } - if self.disable_dtx { - len += 1; - } - if self.channels != 0 { - len += 1; - } - let mut struct_ser = serializer.serialize_struct("livekit.IngressAudioEncodingOptions", len)?; - if self.audio_codec != 0 { - let v = AudioCodec::try_from(self.audio_codec) - .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.audio_codec)))?; - struct_ser.serialize_field("audioCodec", &v)?; - } - if self.bitrate != 0 { - struct_ser.serialize_field("bitrate", &self.bitrate)?; - } - if self.disable_dtx { - struct_ser.serialize_field("disableDtx", &self.disable_dtx)?; - } - if self.channels != 0 { - struct_ser.serialize_field("channels", &self.channels)?; - } - struct_ser.end() + let variant = match self { + Self::IcDefault => "IC_DEFAULT", + Self::IcJpeg => "IC_JPEG", + }; + serializer.serialize_str(variant) } } -impl<'de> serde::Deserialize<'de> for IngressAudioEncodingOptions { +impl<'de> serde::Deserialize<'de> for ImageCodec { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "audio_codec", - "audioCodec", - "bitrate", - "disable_dtx", - "disableDtx", - "channels", + "IC_DEFAULT", + "IC_JPEG", ]; - #[allow(clippy::enum_variant_names)] - enum GeneratedField { - AudioCodec, - Bitrate, - DisableDtx, - Channels, - __SkipField__, - } - impl<'de> serde::Deserialize<'de> for GeneratedField { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - struct GeneratedVisitor; + struct GeneratedVisitor; - impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = GeneratedField; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = ImageCodec; - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(formatter, "expected one of: {:?}", &FIELDS) - } + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } - #[allow(unused_variables)] - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - match value { - "audioCodec" | "audio_codec" => Ok(GeneratedField::AudioCodec), - "bitrate" => Ok(GeneratedField::Bitrate), - "disableDtx" | "disable_dtx" => Ok(GeneratedField::DisableDtx), - "channels" => Ok(GeneratedField::Channels), - _ => Ok(GeneratedField::__SkipField__), - } - } - } - deserializer.deserialize_identifier(GeneratedVisitor) + fn visit_i64(self, v: i64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self) + }) } - } - struct GeneratedVisitor; - impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = IngressAudioEncodingOptions; - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.IngressAudioEncodingOptions") + fn visit_u64(self, v: u64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(v), &self) + }) } - fn visit_map(self, mut map_: V) -> std::result::Result - where - V: serde::de::MapAccess<'de>, + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, { - let mut audio_codec__ = None; - let mut bitrate__ = None; - let mut disable_dtx__ = None; - let mut channels__ = None; - while let Some(k) = map_.next_key()? { - match k { - GeneratedField::AudioCodec => { - if audio_codec__.is_some() { - return Err(serde::de::Error::duplicate_field("audioCodec")); - } - audio_codec__ = Some(map_.next_value::()? as i32); - } - GeneratedField::Bitrate => { - if bitrate__.is_some() { - return Err(serde::de::Error::duplicate_field("bitrate")); - } - bitrate__ = - Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; - } - GeneratedField::DisableDtx => { - if disable_dtx__.is_some() { - return Err(serde::de::Error::duplicate_field("disableDtx")); - } - disable_dtx__ = Some(map_.next_value()?); - } - GeneratedField::Channels => { - if channels__.is_some() { - return Err(serde::de::Error::duplicate_field("channels")); - } - channels__ = - Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; - } - GeneratedField::__SkipField__ => { - let _ = map_.next_value::()?; - } - } + match value { + "IC_DEFAULT" => Ok(ImageCodec::IcDefault), + "IC_JPEG" => Ok(ImageCodec::IcJpeg), + _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), } - Ok(IngressAudioEncodingOptions { - audio_codec: audio_codec__.unwrap_or_default(), - bitrate: bitrate__.unwrap_or_default(), - disable_dtx: disable_dtx__.unwrap_or_default(), - channels: channels__.unwrap_or_default(), - }) } } - deserializer.deserialize_struct("livekit.IngressAudioEncodingOptions", FIELDS, GeneratedVisitor) + deserializer.deserialize_any(GeneratedVisitor) } } -impl serde::Serialize for IngressAudioEncodingPreset { +impl serde::Serialize for ImageFileSuffix { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where S: serde::Serializer, { let variant = match self { - Self::OpusStereo96kbps => "OPUS_STEREO_96KBPS", - Self::OpusMono64kbs => "OPUS_MONO_64KBS", + Self::ImageSuffixIndex => "IMAGE_SUFFIX_INDEX", + Self::ImageSuffixTimestamp => "IMAGE_SUFFIX_TIMESTAMP", }; serializer.serialize_str(variant) } } -impl<'de> serde::Deserialize<'de> for IngressAudioEncodingPreset { +impl<'de> serde::Deserialize<'de> for ImageFileSuffix { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "OPUS_STEREO_96KBPS", - "OPUS_MONO_64KBS", + "IMAGE_SUFFIX_INDEX", + "IMAGE_SUFFIX_TIMESTAMP", ]; struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = IngressAudioEncodingPreset; + type Value = ImageFileSuffix; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(formatter, "expected one of: {:?}", &FIELDS) @@ -8544,8 +8801,8 @@ impl<'de> serde::Deserialize<'de> for IngressAudioEncodingPreset { E: serde::de::Error, { match value { - "OPUS_STEREO_96KBPS" => Ok(IngressAudioEncodingPreset::OpusStereo96kbps), - "OPUS_MONO_64KBS" => Ok(IngressAudioEncodingPreset::OpusMono64kbs), + "IMAGE_SUFFIX_INDEX" => Ok(ImageFileSuffix::ImageSuffixIndex), + "IMAGE_SUFFIX_TIMESTAMP" => Ok(ImageFileSuffix::ImageSuffixTimestamp), _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), } } @@ -8553,7 +8810,7 @@ impl<'de> serde::Deserialize<'de> for IngressAudioEncodingPreset { deserializer.deserialize_any(GeneratedVisitor) } } -impl serde::Serialize for IngressAudioOptions { +impl serde::Serialize for ImageOutput { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -8561,58 +8818,113 @@ impl serde::Serialize for IngressAudioOptions { { use serde::ser::SerializeStruct; let mut len = 0; - if !self.name.is_empty() { + if self.capture_interval != 0 { len += 1; } - if self.source != 0 { + if self.width != 0 { len += 1; } - if self.encoding_options.is_some() { + if self.height != 0 { len += 1; } - let mut struct_ser = serializer.serialize_struct("livekit.IngressAudioOptions", len)?; - if !self.name.is_empty() { - struct_ser.serialize_field("name", &self.name)?; + if !self.filename_prefix.is_empty() { + len += 1; } - if self.source != 0 { - let v = TrackSource::try_from(self.source) - .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.source)))?; - struct_ser.serialize_field("source", &v)?; + if self.filename_suffix != 0 { + len += 1; } - if let Some(v) = self.encoding_options.as_ref() { + if self.image_codec != 0 { + len += 1; + } + if self.disable_manifest { + len += 1; + } + if self.output.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.ImageOutput", len)?; + if self.capture_interval != 0 { + struct_ser.serialize_field("captureInterval", &self.capture_interval)?; + } + if self.width != 0 { + struct_ser.serialize_field("width", &self.width)?; + } + if self.height != 0 { + struct_ser.serialize_field("height", &self.height)?; + } + if !self.filename_prefix.is_empty() { + struct_ser.serialize_field("filenamePrefix", &self.filename_prefix)?; + } + if self.filename_suffix != 0 { + let v = ImageFileSuffix::try_from(self.filename_suffix) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.filename_suffix)))?; + struct_ser.serialize_field("filenameSuffix", &v)?; + } + if self.image_codec != 0 { + let v = ImageCodec::try_from(self.image_codec) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.image_codec)))?; + struct_ser.serialize_field("imageCodec", &v)?; + } + if self.disable_manifest { + struct_ser.serialize_field("disableManifest", &self.disable_manifest)?; + } + if let Some(v) = self.output.as_ref() { match v { - ingress_audio_options::EncodingOptions::Preset(v) => { - let v = IngressAudioEncodingPreset::try_from(*v) - .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", *v)))?; - struct_ser.serialize_field("preset", &v)?; + image_output::Output::S3(v) => { + struct_ser.serialize_field("s3", v)?; } - ingress_audio_options::EncodingOptions::Options(v) => { - struct_ser.serialize_field("options", v)?; + image_output::Output::Gcp(v) => { + struct_ser.serialize_field("gcp", v)?; + } + image_output::Output::Azure(v) => { + struct_ser.serialize_field("azure", v)?; + } + image_output::Output::AliOss(v) => { + struct_ser.serialize_field("aliOSS", v)?; } } } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for IngressAudioOptions { +impl<'de> serde::Deserialize<'de> for ImageOutput { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "name", - "source", - "preset", - "options", + "capture_interval", + "captureInterval", + "width", + "height", + "filename_prefix", + "filenamePrefix", + "filename_suffix", + "filenameSuffix", + "image_codec", + "imageCodec", + "disable_manifest", + "disableManifest", + "s3", + "gcp", + "azure", + "aliOSS", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { - Name, - Source, - Preset, - Options, + CaptureInterval, + Width, + Height, + FilenamePrefix, + FilenameSuffix, + ImageCodec, + DisableManifest, + S3, + Gcp, + Azure, + AliOss, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -8635,10 +8947,17 @@ impl<'de> serde::Deserialize<'de> for IngressAudioOptions { E: serde::de::Error, { match value { - "name" => Ok(GeneratedField::Name), - "source" => Ok(GeneratedField::Source), - "preset" => Ok(GeneratedField::Preset), - "options" => Ok(GeneratedField::Options), + "captureInterval" | "capture_interval" => Ok(GeneratedField::CaptureInterval), + "width" => Ok(GeneratedField::Width), + "height" => Ok(GeneratedField::Height), + "filenamePrefix" | "filename_prefix" => Ok(GeneratedField::FilenamePrefix), + "filenameSuffix" | "filename_suffix" => Ok(GeneratedField::FilenameSuffix), + "imageCodec" | "image_codec" => Ok(GeneratedField::ImageCodec), + "disableManifest" | "disable_manifest" => Ok(GeneratedField::DisableManifest), + "s3" => Ok(GeneratedField::S3), + "gcp" => Ok(GeneratedField::Gcp), + "azure" => Ok(GeneratedField::Azure), + "aliOSS" => Ok(GeneratedField::AliOss), _ => Ok(GeneratedField::__SkipField__), } } @@ -8648,44 +8967,100 @@ impl<'de> serde::Deserialize<'de> for IngressAudioOptions { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = IngressAudioOptions; + type Value = ImageOutput; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.IngressAudioOptions") + formatter.write_str("struct livekit.ImageOutput") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut name__ = None; - let mut source__ = None; - let mut encoding_options__ = None; + let mut capture_interval__ = None; + let mut width__ = None; + let mut height__ = None; + let mut filename_prefix__ = None; + let mut filename_suffix__ = None; + let mut image_codec__ = None; + let mut disable_manifest__ = None; + let mut output__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::Name => { - if name__.is_some() { - return Err(serde::de::Error::duplicate_field("name")); + GeneratedField::CaptureInterval => { + if capture_interval__.is_some() { + return Err(serde::de::Error::duplicate_field("captureInterval")); } - name__ = Some(map_.next_value()?); + capture_interval__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; } - GeneratedField::Source => { - if source__.is_some() { - return Err(serde::de::Error::duplicate_field("source")); + GeneratedField::Width => { + if width__.is_some() { + return Err(serde::de::Error::duplicate_field("width")); } - source__ = Some(map_.next_value::()? as i32); + width__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; } - GeneratedField::Preset => { - if encoding_options__.is_some() { - return Err(serde::de::Error::duplicate_field("preset")); + GeneratedField::Height => { + if height__.is_some() { + return Err(serde::de::Error::duplicate_field("height")); } - encoding_options__ = map_.next_value::<::std::option::Option>()?.map(|x| ingress_audio_options::EncodingOptions::Preset(x as i32)); + height__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; } - GeneratedField::Options => { - if encoding_options__.is_some() { - return Err(serde::de::Error::duplicate_field("options")); + GeneratedField::FilenamePrefix => { + if filename_prefix__.is_some() { + return Err(serde::de::Error::duplicate_field("filenamePrefix")); } - encoding_options__ = map_.next_value::<::std::option::Option<_>>()?.map(ingress_audio_options::EncodingOptions::Options) + filename_prefix__ = Some(map_.next_value()?); + } + GeneratedField::FilenameSuffix => { + if filename_suffix__.is_some() { + return Err(serde::de::Error::duplicate_field("filenameSuffix")); + } + filename_suffix__ = Some(map_.next_value::()? as i32); + } + GeneratedField::ImageCodec => { + if image_codec__.is_some() { + return Err(serde::de::Error::duplicate_field("imageCodec")); + } + image_codec__ = Some(map_.next_value::()? as i32); + } + GeneratedField::DisableManifest => { + if disable_manifest__.is_some() { + return Err(serde::de::Error::duplicate_field("disableManifest")); + } + disable_manifest__ = Some(map_.next_value()?); + } + GeneratedField::S3 => { + if output__.is_some() { + return Err(serde::de::Error::duplicate_field("s3")); + } + output__ = map_.next_value::<::std::option::Option<_>>()?.map(image_output::Output::S3) +; + } + GeneratedField::Gcp => { + if output__.is_some() { + return Err(serde::de::Error::duplicate_field("gcp")); + } + output__ = map_.next_value::<::std::option::Option<_>>()?.map(image_output::Output::Gcp) +; + } + GeneratedField::Azure => { + if output__.is_some() { + return Err(serde::de::Error::duplicate_field("azure")); + } + output__ = map_.next_value::<::std::option::Option<_>>()?.map(image_output::Output::Azure) +; + } + GeneratedField::AliOss => { + if output__.is_some() { + return Err(serde::de::Error::duplicate_field("aliOSS")); + } + output__ = map_.next_value::<::std::option::Option<_>>()?.map(image_output::Output::AliOss) ; } GeneratedField::__SkipField__ => { @@ -8693,17 +9068,22 @@ impl<'de> serde::Deserialize<'de> for IngressAudioOptions { } } } - Ok(IngressAudioOptions { - name: name__.unwrap_or_default(), - source: source__.unwrap_or_default(), - encoding_options: encoding_options__, + Ok(ImageOutput { + capture_interval: capture_interval__.unwrap_or_default(), + width: width__.unwrap_or_default(), + height: height__.unwrap_or_default(), + filename_prefix: filename_prefix__.unwrap_or_default(), + filename_suffix: filename_suffix__.unwrap_or_default(), + image_codec: image_codec__.unwrap_or_default(), + disable_manifest: disable_manifest__.unwrap_or_default(), + output: output__, }) } } - deserializer.deserialize_struct("livekit.IngressAudioOptions", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.ImageOutput", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for IngressInfo { +impl serde::Serialize for ImagesInfo { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -8711,152 +9091,63 @@ impl serde::Serialize for IngressInfo { { use serde::ser::SerializeStruct; let mut len = 0; - if !self.ingress_id.is_empty() { + if !self.filename_prefix.is_empty() { len += 1; } - if !self.name.is_empty() { + if self.image_count != 0 { len += 1; } - if !self.stream_key.is_empty() { + if self.started_at != 0 { len += 1; } - if !self.url.is_empty() { + if self.ended_at != 0 { len += 1; } - if self.input_type != 0 { - len += 1; + let mut struct_ser = serializer.serialize_struct("livekit.ImagesInfo", len)?; + if !self.filename_prefix.is_empty() { + struct_ser.serialize_field("filenamePrefix", &self.filename_prefix)?; } - if self.bypass_transcoding { - len += 1; + if self.image_count != 0 { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("imageCount", ToString::to_string(&self.image_count).as_str())?; } - if self.enable_transcoding.is_some() { - len += 1; - } - if self.audio.is_some() { - len += 1; - } - if self.video.is_some() { - len += 1; - } - if !self.room_name.is_empty() { - len += 1; - } - if !self.participant_identity.is_empty() { - len += 1; - } - if !self.participant_name.is_empty() { - len += 1; - } - if !self.participant_metadata.is_empty() { - len += 1; - } - if self.reusable { - len += 1; - } - if self.state.is_some() { - len += 1; - } - let mut struct_ser = serializer.serialize_struct("livekit.IngressInfo", len)?; - if !self.ingress_id.is_empty() { - struct_ser.serialize_field("ingressId", &self.ingress_id)?; - } - if !self.name.is_empty() { - struct_ser.serialize_field("name", &self.name)?; - } - if !self.stream_key.is_empty() { - struct_ser.serialize_field("streamKey", &self.stream_key)?; - } - if !self.url.is_empty() { - struct_ser.serialize_field("url", &self.url)?; - } - if self.input_type != 0 { - let v = IngressInput::try_from(self.input_type) - .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.input_type)))?; - struct_ser.serialize_field("inputType", &v)?; - } - if self.bypass_transcoding { - struct_ser.serialize_field("bypassTranscoding", &self.bypass_transcoding)?; - } - if let Some(v) = self.enable_transcoding.as_ref() { - struct_ser.serialize_field("enableTranscoding", v)?; - } - if let Some(v) = self.audio.as_ref() { - struct_ser.serialize_field("audio", v)?; - } - if let Some(v) = self.video.as_ref() { - struct_ser.serialize_field("video", v)?; - } - if !self.room_name.is_empty() { - struct_ser.serialize_field("roomName", &self.room_name)?; - } - if !self.participant_identity.is_empty() { - struct_ser.serialize_field("participantIdentity", &self.participant_identity)?; - } - if !self.participant_name.is_empty() { - struct_ser.serialize_field("participantName", &self.participant_name)?; - } - if !self.participant_metadata.is_empty() { - struct_ser.serialize_field("participantMetadata", &self.participant_metadata)?; - } - if self.reusable { - struct_ser.serialize_field("reusable", &self.reusable)?; + if self.started_at != 0 { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("startedAt", ToString::to_string(&self.started_at).as_str())?; } - if let Some(v) = self.state.as_ref() { - struct_ser.serialize_field("state", v)?; + if self.ended_at != 0 { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("endedAt", ToString::to_string(&self.ended_at).as_str())?; } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for IngressInfo { +impl<'de> serde::Deserialize<'de> for ImagesInfo { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "ingress_id", - "ingressId", - "name", - "stream_key", - "streamKey", - "url", - "input_type", - "inputType", - "bypass_transcoding", - "bypassTranscoding", - "enable_transcoding", - "enableTranscoding", - "audio", - "video", - "room_name", - "roomName", - "participant_identity", - "participantIdentity", - "participant_name", - "participantName", - "participant_metadata", - "participantMetadata", - "reusable", - "state", + "filename_prefix", + "filenamePrefix", + "image_count", + "imageCount", + "started_at", + "startedAt", + "ended_at", + "endedAt", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { - IngressId, - Name, - StreamKey, - Url, - InputType, - BypassTranscoding, - EnableTranscoding, - Audio, - Video, - RoomName, - ParticipantIdentity, - ParticipantName, - ParticipantMetadata, - Reusable, - State, + FilenamePrefix, + ImageCount, + StartedAt, + EndedAt, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -8879,21 +9170,10 @@ impl<'de> serde::Deserialize<'de> for IngressInfo { E: serde::de::Error, { match value { - "ingressId" | "ingress_id" => Ok(GeneratedField::IngressId), - "name" => Ok(GeneratedField::Name), - "streamKey" | "stream_key" => Ok(GeneratedField::StreamKey), - "url" => Ok(GeneratedField::Url), - "inputType" | "input_type" => Ok(GeneratedField::InputType), - "bypassTranscoding" | "bypass_transcoding" => Ok(GeneratedField::BypassTranscoding), - "enableTranscoding" | "enable_transcoding" => Ok(GeneratedField::EnableTranscoding), - "audio" => Ok(GeneratedField::Audio), - "video" => Ok(GeneratedField::Video), - "roomName" | "room_name" => Ok(GeneratedField::RoomName), - "participantIdentity" | "participant_identity" => Ok(GeneratedField::ParticipantIdentity), - "participantName" | "participant_name" => Ok(GeneratedField::ParticipantName), - "participantMetadata" | "participant_metadata" => Ok(GeneratedField::ParticipantMetadata), - "reusable" => Ok(GeneratedField::Reusable), - "state" => Ok(GeneratedField::State), + "filenamePrefix" | "filename_prefix" => Ok(GeneratedField::FilenamePrefix), + "imageCount" | "image_count" => Ok(GeneratedField::ImageCount), + "startedAt" | "started_at" => Ok(GeneratedField::StartedAt), + "endedAt" | "ended_at" => Ok(GeneratedField::EndedAt), _ => Ok(GeneratedField::__SkipField__), } } @@ -8903,180 +9183,250 @@ impl<'de> serde::Deserialize<'de> for IngressInfo { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = IngressInfo; + type Value = ImagesInfo; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.IngressInfo") + formatter.write_str("struct livekit.ImagesInfo") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut ingress_id__ = None; - let mut name__ = None; - let mut stream_key__ = None; - let mut url__ = None; - let mut input_type__ = None; - let mut bypass_transcoding__ = None; - let mut enable_transcoding__ = None; - let mut audio__ = None; - let mut video__ = None; - let mut room_name__ = None; - let mut participant_identity__ = None; - let mut participant_name__ = None; - let mut participant_metadata__ = None; - let mut reusable__ = None; - let mut state__ = None; + let mut filename_prefix__ = None; + let mut image_count__ = None; + let mut started_at__ = None; + let mut ended_at__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::IngressId => { - if ingress_id__.is_some() { - return Err(serde::de::Error::duplicate_field("ingressId")); - } - ingress_id__ = Some(map_.next_value()?); - } - GeneratedField::Name => { - if name__.is_some() { - return Err(serde::de::Error::duplicate_field("name")); - } - name__ = Some(map_.next_value()?); - } - GeneratedField::StreamKey => { - if stream_key__.is_some() { - return Err(serde::de::Error::duplicate_field("streamKey")); - } - stream_key__ = Some(map_.next_value()?); - } - GeneratedField::Url => { - if url__.is_some() { - return Err(serde::de::Error::duplicate_field("url")); - } - url__ = Some(map_.next_value()?); - } - GeneratedField::InputType => { - if input_type__.is_some() { - return Err(serde::de::Error::duplicate_field("inputType")); - } - input_type__ = Some(map_.next_value::()? as i32); - } - GeneratedField::BypassTranscoding => { - if bypass_transcoding__.is_some() { - return Err(serde::de::Error::duplicate_field("bypassTranscoding")); + GeneratedField::FilenamePrefix => { + if filename_prefix__.is_some() { + return Err(serde::de::Error::duplicate_field("filenamePrefix")); } - bypass_transcoding__ = Some(map_.next_value()?); + filename_prefix__ = Some(map_.next_value()?); } - GeneratedField::EnableTranscoding => { - if enable_transcoding__.is_some() { - return Err(serde::de::Error::duplicate_field("enableTranscoding")); + GeneratedField::ImageCount => { + if image_count__.is_some() { + return Err(serde::de::Error::duplicate_field("imageCount")); } - enable_transcoding__ = map_.next_value()?; + image_count__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; } - GeneratedField::Audio => { - if audio__.is_some() { - return Err(serde::de::Error::duplicate_field("audio")); + GeneratedField::StartedAt => { + if started_at__.is_some() { + return Err(serde::de::Error::duplicate_field("startedAt")); } - audio__ = map_.next_value()?; + started_at__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; } - GeneratedField::Video => { - if video__.is_some() { - return Err(serde::de::Error::duplicate_field("video")); + GeneratedField::EndedAt => { + if ended_at__.is_some() { + return Err(serde::de::Error::duplicate_field("endedAt")); } - video__ = map_.next_value()?; + ended_at__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; } - GeneratedField::RoomName => { - if room_name__.is_some() { - return Err(serde::de::Error::duplicate_field("roomName")); - } - room_name__ = Some(map_.next_value()?); + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; } - GeneratedField::ParticipantIdentity => { - if participant_identity__.is_some() { - return Err(serde::de::Error::duplicate_field("participantIdentity")); - } - participant_identity__ = Some(map_.next_value()?); + } + } + Ok(ImagesInfo { + filename_prefix: filename_prefix__.unwrap_or_default(), + image_count: image_count__.unwrap_or_default(), + started_at: started_at__.unwrap_or_default(), + ended_at: ended_at__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.ImagesInfo", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for IngressAudioEncodingOptions { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.audio_codec != 0 { + len += 1; + } + if self.bitrate != 0 { + len += 1; + } + if self.disable_dtx { + len += 1; + } + if self.channels != 0 { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.IngressAudioEncodingOptions", len)?; + if self.audio_codec != 0 { + let v = AudioCodec::try_from(self.audio_codec) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.audio_codec)))?; + struct_ser.serialize_field("audioCodec", &v)?; + } + if self.bitrate != 0 { + struct_ser.serialize_field("bitrate", &self.bitrate)?; + } + if self.disable_dtx { + struct_ser.serialize_field("disableDtx", &self.disable_dtx)?; + } + if self.channels != 0 { + struct_ser.serialize_field("channels", &self.channels)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for IngressAudioEncodingOptions { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "audio_codec", + "audioCodec", + "bitrate", + "disable_dtx", + "disableDtx", + "channels", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + AudioCodec, + Bitrate, + DisableDtx, + Channels, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "audioCodec" | "audio_codec" => Ok(GeneratedField::AudioCodec), + "bitrate" => Ok(GeneratedField::Bitrate), + "disableDtx" | "disable_dtx" => Ok(GeneratedField::DisableDtx), + "channels" => Ok(GeneratedField::Channels), + _ => Ok(GeneratedField::__SkipField__), } - GeneratedField::ParticipantName => { - if participant_name__.is_some() { - return Err(serde::de::Error::duplicate_field("participantName")); + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = IngressAudioEncodingOptions; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.IngressAudioEncodingOptions") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut audio_codec__ = None; + let mut bitrate__ = None; + let mut disable_dtx__ = None; + let mut channels__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::AudioCodec => { + if audio_codec__.is_some() { + return Err(serde::de::Error::duplicate_field("audioCodec")); } - participant_name__ = Some(map_.next_value()?); + audio_codec__ = Some(map_.next_value::()? as i32); } - GeneratedField::ParticipantMetadata => { - if participant_metadata__.is_some() { - return Err(serde::de::Error::duplicate_field("participantMetadata")); + GeneratedField::Bitrate => { + if bitrate__.is_some() { + return Err(serde::de::Error::duplicate_field("bitrate")); } - participant_metadata__ = Some(map_.next_value()?); + bitrate__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; } - GeneratedField::Reusable => { - if reusable__.is_some() { - return Err(serde::de::Error::duplicate_field("reusable")); + GeneratedField::DisableDtx => { + if disable_dtx__.is_some() { + return Err(serde::de::Error::duplicate_field("disableDtx")); } - reusable__ = Some(map_.next_value()?); + disable_dtx__ = Some(map_.next_value()?); } - GeneratedField::State => { - if state__.is_some() { - return Err(serde::de::Error::duplicate_field("state")); + GeneratedField::Channels => { + if channels__.is_some() { + return Err(serde::de::Error::duplicate_field("channels")); } - state__ = map_.next_value()?; + channels__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } } } - Ok(IngressInfo { - ingress_id: ingress_id__.unwrap_or_default(), - name: name__.unwrap_or_default(), - stream_key: stream_key__.unwrap_or_default(), - url: url__.unwrap_or_default(), - input_type: input_type__.unwrap_or_default(), - bypass_transcoding: bypass_transcoding__.unwrap_or_default(), - enable_transcoding: enable_transcoding__, - audio: audio__, - video: video__, - room_name: room_name__.unwrap_or_default(), - participant_identity: participant_identity__.unwrap_or_default(), - participant_name: participant_name__.unwrap_or_default(), - participant_metadata: participant_metadata__.unwrap_or_default(), - reusable: reusable__.unwrap_or_default(), - state: state__, + Ok(IngressAudioEncodingOptions { + audio_codec: audio_codec__.unwrap_or_default(), + bitrate: bitrate__.unwrap_or_default(), + disable_dtx: disable_dtx__.unwrap_or_default(), + channels: channels__.unwrap_or_default(), }) } } - deserializer.deserialize_struct("livekit.IngressInfo", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.IngressAudioEncodingOptions", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for IngressInput { +impl serde::Serialize for IngressAudioEncodingPreset { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where S: serde::Serializer, { let variant = match self { - Self::RtmpInput => "RTMP_INPUT", - Self::WhipInput => "WHIP_INPUT", - Self::UrlInput => "URL_INPUT", + Self::OpusStereo96kbps => "OPUS_STEREO_96KBPS", + Self::OpusMono64kbs => "OPUS_MONO_64KBS", }; serializer.serialize_str(variant) } } -impl<'de> serde::Deserialize<'de> for IngressInput { +impl<'de> serde::Deserialize<'de> for IngressAudioEncodingPreset { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "RTMP_INPUT", - "WHIP_INPUT", - "URL_INPUT", + "OPUS_STEREO_96KBPS", + "OPUS_MONO_64KBS", ]; struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = IngressInput; + type Value = IngressAudioEncodingPreset; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(formatter, "expected one of: {:?}", &FIELDS) @@ -9111,9 +9461,8 @@ impl<'de> serde::Deserialize<'de> for IngressInput { E: serde::de::Error, { match value { - "RTMP_INPUT" => Ok(IngressInput::RtmpInput), - "WHIP_INPUT" => Ok(IngressInput::WhipInput), - "URL_INPUT" => Ok(IngressInput::UrlInput), + "OPUS_STEREO_96KBPS" => Ok(IngressAudioEncodingPreset::OpusStereo96kbps), + "OPUS_MONO_64KBS" => Ok(IngressAudioEncodingPreset::OpusMono64kbs), _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), } } @@ -9121,7 +9470,7 @@ impl<'de> serde::Deserialize<'de> for IngressInput { deserializer.deserialize_any(GeneratedVisitor) } } -impl serde::Serialize for IngressState { +impl serde::Serialize for IngressAudioOptions { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -9129,114 +9478,58 @@ impl serde::Serialize for IngressState { { use serde::ser::SerializeStruct; let mut len = 0; - if self.status != 0 { - len += 1; - } - if !self.error.is_empty() { - len += 1; - } - if self.video.is_some() { - len += 1; - } - if self.audio.is_some() { - len += 1; - } - if !self.room_id.is_empty() { - len += 1; - } - if self.started_at != 0 { - len += 1; - } - if self.ended_at != 0 { + if !self.name.is_empty() { len += 1; } - if self.updated_at != 0 { + if self.source != 0 { len += 1; } - if !self.resource_id.is_empty() { + if self.encoding_options.is_some() { len += 1; } - if !self.tracks.is_empty() { - len += 1; + let mut struct_ser = serializer.serialize_struct("livekit.IngressAudioOptions", len)?; + if !self.name.is_empty() { + struct_ser.serialize_field("name", &self.name)?; } - let mut struct_ser = serializer.serialize_struct("livekit.IngressState", len)?; - if self.status != 0 { - let v = ingress_state::Status::try_from(self.status) - .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.status)))?; - struct_ser.serialize_field("status", &v)?; + if self.source != 0 { + let v = TrackSource::try_from(self.source) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.source)))?; + struct_ser.serialize_field("source", &v)?; } - if !self.error.is_empty() { - struct_ser.serialize_field("error", &self.error)?; - } - if let Some(v) = self.video.as_ref() { - struct_ser.serialize_field("video", v)?; - } - if let Some(v) = self.audio.as_ref() { - struct_ser.serialize_field("audio", v)?; - } - if !self.room_id.is_empty() { - struct_ser.serialize_field("roomId", &self.room_id)?; - } - if self.started_at != 0 { - #[allow(clippy::needless_borrow)] - #[allow(clippy::needless_borrows_for_generic_args)] - struct_ser.serialize_field("startedAt", ToString::to_string(&self.started_at).as_str())?; - } - if self.ended_at != 0 { - #[allow(clippy::needless_borrow)] - #[allow(clippy::needless_borrows_for_generic_args)] - struct_ser.serialize_field("endedAt", ToString::to_string(&self.ended_at).as_str())?; - } - if self.updated_at != 0 { - #[allow(clippy::needless_borrow)] - #[allow(clippy::needless_borrows_for_generic_args)] - struct_ser.serialize_field("updatedAt", ToString::to_string(&self.updated_at).as_str())?; - } - if !self.resource_id.is_empty() { - struct_ser.serialize_field("resourceId", &self.resource_id)?; - } - if !self.tracks.is_empty() { - struct_ser.serialize_field("tracks", &self.tracks)?; + if let Some(v) = self.encoding_options.as_ref() { + match v { + ingress_audio_options::EncodingOptions::Preset(v) => { + let v = IngressAudioEncodingPreset::try_from(*v) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", *v)))?; + struct_ser.serialize_field("preset", &v)?; + } + ingress_audio_options::EncodingOptions::Options(v) => { + struct_ser.serialize_field("options", v)?; + } + } } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for IngressState { +impl<'de> serde::Deserialize<'de> for IngressAudioOptions { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "status", - "error", - "video", - "audio", - "room_id", - "roomId", - "started_at", - "startedAt", - "ended_at", - "endedAt", - "updated_at", - "updatedAt", - "resource_id", - "resourceId", - "tracks", + "name", + "source", + "preset", + "options", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { - Status, - Error, - Video, - Audio, - RoomId, - StartedAt, - EndedAt, - UpdatedAt, - ResourceId, - Tracks, + Name, + Source, + Preset, + Options, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -9259,16 +9552,10 @@ impl<'de> serde::Deserialize<'de> for IngressState { E: serde::de::Error, { match value { - "status" => Ok(GeneratedField::Status), - "error" => Ok(GeneratedField::Error), - "video" => Ok(GeneratedField::Video), - "audio" => Ok(GeneratedField::Audio), - "roomId" | "room_id" => Ok(GeneratedField::RoomId), - "startedAt" | "started_at" => Ok(GeneratedField::StartedAt), - "endedAt" | "ended_at" => Ok(GeneratedField::EndedAt), - "updatedAt" | "updated_at" => Ok(GeneratedField::UpdatedAt), - "resourceId" | "resource_id" => Ok(GeneratedField::ResourceId), - "tracks" => Ok(GeneratedField::Tracks), + "name" => Ok(GeneratedField::Name), + "source" => Ok(GeneratedField::Source), + "preset" => Ok(GeneratedField::Preset), + "options" => Ok(GeneratedField::Options), _ => Ok(GeneratedField::__SkipField__), } } @@ -9278,197 +9565,62 @@ impl<'de> serde::Deserialize<'de> for IngressState { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = IngressState; + type Value = IngressAudioOptions; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.IngressState") + formatter.write_str("struct livekit.IngressAudioOptions") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut status__ = None; - let mut error__ = None; - let mut video__ = None; - let mut audio__ = None; - let mut room_id__ = None; - let mut started_at__ = None; - let mut ended_at__ = None; - let mut updated_at__ = None; - let mut resource_id__ = None; - let mut tracks__ = None; + let mut name__ = None; + let mut source__ = None; + let mut encoding_options__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::Status => { - if status__.is_some() { - return Err(serde::de::Error::duplicate_field("status")); - } - status__ = Some(map_.next_value::()? as i32); - } - GeneratedField::Error => { - if error__.is_some() { - return Err(serde::de::Error::duplicate_field("error")); - } - error__ = Some(map_.next_value()?); - } - GeneratedField::Video => { - if video__.is_some() { - return Err(serde::de::Error::duplicate_field("video")); - } - video__ = map_.next_value()?; - } - GeneratedField::Audio => { - if audio__.is_some() { - return Err(serde::de::Error::duplicate_field("audio")); - } - audio__ = map_.next_value()?; - } - GeneratedField::RoomId => { - if room_id__.is_some() { - return Err(serde::de::Error::duplicate_field("roomId")); - } - room_id__ = Some(map_.next_value()?); - } - GeneratedField::StartedAt => { - if started_at__.is_some() { - return Err(serde::de::Error::duplicate_field("startedAt")); - } - started_at__ = - Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; - } - GeneratedField::EndedAt => { - if ended_at__.is_some() { - return Err(serde::de::Error::duplicate_field("endedAt")); + GeneratedField::Name => { + if name__.is_some() { + return Err(serde::de::Error::duplicate_field("name")); } - ended_at__ = - Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; + name__ = Some(map_.next_value()?); } - GeneratedField::UpdatedAt => { - if updated_at__.is_some() { - return Err(serde::de::Error::duplicate_field("updatedAt")); + GeneratedField::Source => { + if source__.is_some() { + return Err(serde::de::Error::duplicate_field("source")); } - updated_at__ = - Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; + source__ = Some(map_.next_value::()? as i32); } - GeneratedField::ResourceId => { - if resource_id__.is_some() { - return Err(serde::de::Error::duplicate_field("resourceId")); + GeneratedField::Preset => { + if encoding_options__.is_some() { + return Err(serde::de::Error::duplicate_field("preset")); } - resource_id__ = Some(map_.next_value()?); + encoding_options__ = map_.next_value::<::std::option::Option>()?.map(|x| ingress_audio_options::EncodingOptions::Preset(x as i32)); } - GeneratedField::Tracks => { - if tracks__.is_some() { - return Err(serde::de::Error::duplicate_field("tracks")); + GeneratedField::Options => { + if encoding_options__.is_some() { + return Err(serde::de::Error::duplicate_field("options")); } - tracks__ = Some(map_.next_value()?); + encoding_options__ = map_.next_value::<::std::option::Option<_>>()?.map(ingress_audio_options::EncodingOptions::Options) +; } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } } } - Ok(IngressState { - status: status__.unwrap_or_default(), - error: error__.unwrap_or_default(), - video: video__, - audio: audio__, - room_id: room_id__.unwrap_or_default(), - started_at: started_at__.unwrap_or_default(), - ended_at: ended_at__.unwrap_or_default(), - updated_at: updated_at__.unwrap_or_default(), - resource_id: resource_id__.unwrap_or_default(), - tracks: tracks__.unwrap_or_default(), + Ok(IngressAudioOptions { + name: name__.unwrap_or_default(), + source: source__.unwrap_or_default(), + encoding_options: encoding_options__, }) } } - deserializer.deserialize_struct("livekit.IngressState", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.IngressAudioOptions", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for ingress_state::Status { - #[allow(deprecated)] - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::Serializer, - { - let variant = match self { - Self::EndpointInactive => "ENDPOINT_INACTIVE", - Self::EndpointBuffering => "ENDPOINT_BUFFERING", - Self::EndpointPublishing => "ENDPOINT_PUBLISHING", - Self::EndpointError => "ENDPOINT_ERROR", - Self::EndpointComplete => "ENDPOINT_COMPLETE", - }; - serializer.serialize_str(variant) - } -} -impl<'de> serde::Deserialize<'de> for ingress_state::Status { - #[allow(deprecated)] - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - const FIELDS: &[&str] = &[ - "ENDPOINT_INACTIVE", - "ENDPOINT_BUFFERING", - "ENDPOINT_PUBLISHING", - "ENDPOINT_ERROR", - "ENDPOINT_COMPLETE", - ]; - - struct GeneratedVisitor; - - impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = ingress_state::Status; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(formatter, "expected one of: {:?}", &FIELDS) - } - - fn visit_i64(self, v: i64) -> std::result::Result - where - E: serde::de::Error, - { - i32::try_from(v) - .ok() - .and_then(|x| x.try_into().ok()) - .ok_or_else(|| { - serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self) - }) - } - - fn visit_u64(self, v: u64) -> std::result::Result - where - E: serde::de::Error, - { - i32::try_from(v) - .ok() - .and_then(|x| x.try_into().ok()) - .ok_or_else(|| { - serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(v), &self) - }) - } - - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - match value { - "ENDPOINT_INACTIVE" => Ok(ingress_state::Status::EndpointInactive), - "ENDPOINT_BUFFERING" => Ok(ingress_state::Status::EndpointBuffering), - "ENDPOINT_PUBLISHING" => Ok(ingress_state::Status::EndpointPublishing), - "ENDPOINT_ERROR" => Ok(ingress_state::Status::EndpointError), - "ENDPOINT_COMPLETE" => Ok(ingress_state::Status::EndpointComplete), - _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), - } - } - } - deserializer.deserialize_any(GeneratedVisitor) - } -} -impl serde::Serialize for IngressVideoEncodingOptions { +impl serde::Serialize for IngressInfo { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -9476,49 +9628,152 @@ impl serde::Serialize for IngressVideoEncodingOptions { { use serde::ser::SerializeStruct; let mut len = 0; - if self.video_codec != 0 { + if !self.ingress_id.is_empty() { len += 1; } - if self.frame_rate != 0. { + if !self.name.is_empty() { len += 1; } - if !self.layers.is_empty() { + if !self.stream_key.is_empty() { len += 1; } - let mut struct_ser = serializer.serialize_struct("livekit.IngressVideoEncodingOptions", len)?; - if self.video_codec != 0 { - let v = VideoCodec::try_from(self.video_codec) - .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.video_codec)))?; - struct_ser.serialize_field("videoCodec", &v)?; + if !self.url.is_empty() { + len += 1; } - if self.frame_rate != 0. { - struct_ser.serialize_field("frameRate", &self.frame_rate)?; + if self.input_type != 0 { + len += 1; } - if !self.layers.is_empty() { - struct_ser.serialize_field("layers", &self.layers)?; + if self.bypass_transcoding { + len += 1; + } + if self.enable_transcoding.is_some() { + len += 1; + } + if self.audio.is_some() { + len += 1; + } + if self.video.is_some() { + len += 1; + } + if !self.room_name.is_empty() { + len += 1; + } + if !self.participant_identity.is_empty() { + len += 1; + } + if !self.participant_name.is_empty() { + len += 1; + } + if !self.participant_metadata.is_empty() { + len += 1; + } + if self.reusable { + len += 1; + } + if self.state.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.IngressInfo", len)?; + if !self.ingress_id.is_empty() { + struct_ser.serialize_field("ingressId", &self.ingress_id)?; + } + if !self.name.is_empty() { + struct_ser.serialize_field("name", &self.name)?; + } + if !self.stream_key.is_empty() { + struct_ser.serialize_field("streamKey", &self.stream_key)?; + } + if !self.url.is_empty() { + struct_ser.serialize_field("url", &self.url)?; + } + if self.input_type != 0 { + let v = IngressInput::try_from(self.input_type) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.input_type)))?; + struct_ser.serialize_field("inputType", &v)?; + } + if self.bypass_transcoding { + struct_ser.serialize_field("bypassTranscoding", &self.bypass_transcoding)?; + } + if let Some(v) = self.enable_transcoding.as_ref() { + struct_ser.serialize_field("enableTranscoding", v)?; + } + if let Some(v) = self.audio.as_ref() { + struct_ser.serialize_field("audio", v)?; + } + if let Some(v) = self.video.as_ref() { + struct_ser.serialize_field("video", v)?; + } + if !self.room_name.is_empty() { + struct_ser.serialize_field("roomName", &self.room_name)?; + } + if !self.participant_identity.is_empty() { + struct_ser.serialize_field("participantIdentity", &self.participant_identity)?; + } + if !self.participant_name.is_empty() { + struct_ser.serialize_field("participantName", &self.participant_name)?; + } + if !self.participant_metadata.is_empty() { + struct_ser.serialize_field("participantMetadata", &self.participant_metadata)?; + } + if self.reusable { + struct_ser.serialize_field("reusable", &self.reusable)?; + } + if let Some(v) = self.state.as_ref() { + struct_ser.serialize_field("state", v)?; } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for IngressVideoEncodingOptions { +impl<'de> serde::Deserialize<'de> for IngressInfo { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "video_codec", - "videoCodec", - "frame_rate", - "frameRate", - "layers", + "ingress_id", + "ingressId", + "name", + "stream_key", + "streamKey", + "url", + "input_type", + "inputType", + "bypass_transcoding", + "bypassTranscoding", + "enable_transcoding", + "enableTranscoding", + "audio", + "video", + "room_name", + "roomName", + "participant_identity", + "participantIdentity", + "participant_name", + "participantName", + "participant_metadata", + "participantMetadata", + "reusable", + "state", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { - VideoCodec, - FrameRate, - Layers, + IngressId, + Name, + StreamKey, + Url, + InputType, + BypassTranscoding, + EnableTranscoding, + Audio, + Video, + RoomName, + ParticipantIdentity, + ParticipantName, + ParticipantMetadata, + Reusable, + State, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -9541,9 +9796,21 @@ impl<'de> serde::Deserialize<'de> for IngressVideoEncodingOptions { E: serde::de::Error, { match value { - "videoCodec" | "video_codec" => Ok(GeneratedField::VideoCodec), - "frameRate" | "frame_rate" => Ok(GeneratedField::FrameRate), - "layers" => Ok(GeneratedField::Layers), + "ingressId" | "ingress_id" => Ok(GeneratedField::IngressId), + "name" => Ok(GeneratedField::Name), + "streamKey" | "stream_key" => Ok(GeneratedField::StreamKey), + "url" => Ok(GeneratedField::Url), + "inputType" | "input_type" => Ok(GeneratedField::InputType), + "bypassTranscoding" | "bypass_transcoding" => Ok(GeneratedField::BypassTranscoding), + "enableTranscoding" | "enable_transcoding" => Ok(GeneratedField::EnableTranscoding), + "audio" => Ok(GeneratedField::Audio), + "video" => Ok(GeneratedField::Video), + "roomName" | "room_name" => Ok(GeneratedField::RoomName), + "participantIdentity" | "participant_identity" => Ok(GeneratedField::ParticipantIdentity), + "participantName" | "participant_name" => Ok(GeneratedField::ParticipantName), + "participantMetadata" | "participant_metadata" => Ok(GeneratedField::ParticipantMetadata), + "reusable" => Ok(GeneratedField::Reusable), + "state" => Ok(GeneratedField::State), _ => Ok(GeneratedField::__SkipField__), } } @@ -9553,100 +9820,180 @@ impl<'de> serde::Deserialize<'de> for IngressVideoEncodingOptions { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = IngressVideoEncodingOptions; + type Value = IngressInfo; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.IngressVideoEncodingOptions") + formatter.write_str("struct livekit.IngressInfo") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut video_codec__ = None; - let mut frame_rate__ = None; - let mut layers__ = None; + let mut ingress_id__ = None; + let mut name__ = None; + let mut stream_key__ = None; + let mut url__ = None; + let mut input_type__ = None; + let mut bypass_transcoding__ = None; + let mut enable_transcoding__ = None; + let mut audio__ = None; + let mut video__ = None; + let mut room_name__ = None; + let mut participant_identity__ = None; + let mut participant_name__ = None; + let mut participant_metadata__ = None; + let mut reusable__ = None; + let mut state__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::VideoCodec => { - if video_codec__.is_some() { - return Err(serde::de::Error::duplicate_field("videoCodec")); + GeneratedField::IngressId => { + if ingress_id__.is_some() { + return Err(serde::de::Error::duplicate_field("ingressId")); } - video_codec__ = Some(map_.next_value::()? as i32); + ingress_id__ = Some(map_.next_value()?); } - GeneratedField::FrameRate => { - if frame_rate__.is_some() { - return Err(serde::de::Error::duplicate_field("frameRate")); + GeneratedField::Name => { + if name__.is_some() { + return Err(serde::de::Error::duplicate_field("name")); } - frame_rate__ = - Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; + name__ = Some(map_.next_value()?); } - GeneratedField::Layers => { - if layers__.is_some() { - return Err(serde::de::Error::duplicate_field("layers")); + GeneratedField::StreamKey => { + if stream_key__.is_some() { + return Err(serde::de::Error::duplicate_field("streamKey")); } - layers__ = Some(map_.next_value()?); + stream_key__ = Some(map_.next_value()?); } - GeneratedField::__SkipField__ => { - let _ = map_.next_value::()?; + GeneratedField::Url => { + if url__.is_some() { + return Err(serde::de::Error::duplicate_field("url")); + } + url__ = Some(map_.next_value()?); } - } - } - Ok(IngressVideoEncodingOptions { - video_codec: video_codec__.unwrap_or_default(), - frame_rate: frame_rate__.unwrap_or_default(), - layers: layers__.unwrap_or_default(), - }) - } - } - deserializer.deserialize_struct("livekit.IngressVideoEncodingOptions", FIELDS, GeneratedVisitor) - } -} -impl serde::Serialize for IngressVideoEncodingPreset { - #[allow(deprecated)] - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::Serializer, - { - let variant = match self { - Self::H264720p30fps3Layers => "H264_720P_30FPS_3_LAYERS", - Self::H2641080p30fps3Layers => "H264_1080P_30FPS_3_LAYERS", - Self::H264540p25fps2Layers => "H264_540P_25FPS_2_LAYERS", - Self::H264720p30fps1Layer => "H264_720P_30FPS_1_LAYER", - Self::H2641080p30fps1Layer => "H264_1080P_30FPS_1_LAYER", - Self::H264720p30fps3LayersHighMotion => "H264_720P_30FPS_3_LAYERS_HIGH_MOTION", - Self::H2641080p30fps3LayersHighMotion => "H264_1080P_30FPS_3_LAYERS_HIGH_MOTION", - Self::H264540p25fps2LayersHighMotion => "H264_540P_25FPS_2_LAYERS_HIGH_MOTION", - Self::H264720p30fps1LayerHighMotion => "H264_720P_30FPS_1_LAYER_HIGH_MOTION", - Self::H2641080p30fps1LayerHighMotion => "H264_1080P_30FPS_1_LAYER_HIGH_MOTION", + GeneratedField::InputType => { + if input_type__.is_some() { + return Err(serde::de::Error::duplicate_field("inputType")); + } + input_type__ = Some(map_.next_value::()? as i32); + } + GeneratedField::BypassTranscoding => { + if bypass_transcoding__.is_some() { + return Err(serde::de::Error::duplicate_field("bypassTranscoding")); + } + bypass_transcoding__ = Some(map_.next_value()?); + } + GeneratedField::EnableTranscoding => { + if enable_transcoding__.is_some() { + return Err(serde::de::Error::duplicate_field("enableTranscoding")); + } + enable_transcoding__ = map_.next_value()?; + } + GeneratedField::Audio => { + if audio__.is_some() { + return Err(serde::de::Error::duplicate_field("audio")); + } + audio__ = map_.next_value()?; + } + GeneratedField::Video => { + if video__.is_some() { + return Err(serde::de::Error::duplicate_field("video")); + } + video__ = map_.next_value()?; + } + GeneratedField::RoomName => { + if room_name__.is_some() { + return Err(serde::de::Error::duplicate_field("roomName")); + } + room_name__ = Some(map_.next_value()?); + } + GeneratedField::ParticipantIdentity => { + if participant_identity__.is_some() { + return Err(serde::de::Error::duplicate_field("participantIdentity")); + } + participant_identity__ = Some(map_.next_value()?); + } + GeneratedField::ParticipantName => { + if participant_name__.is_some() { + return Err(serde::de::Error::duplicate_field("participantName")); + } + participant_name__ = Some(map_.next_value()?); + } + GeneratedField::ParticipantMetadata => { + if participant_metadata__.is_some() { + return Err(serde::de::Error::duplicate_field("participantMetadata")); + } + participant_metadata__ = Some(map_.next_value()?); + } + GeneratedField::Reusable => { + if reusable__.is_some() { + return Err(serde::de::Error::duplicate_field("reusable")); + } + reusable__ = Some(map_.next_value()?); + } + GeneratedField::State => { + if state__.is_some() { + return Err(serde::de::Error::duplicate_field("state")); + } + state__ = map_.next_value()?; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(IngressInfo { + ingress_id: ingress_id__.unwrap_or_default(), + name: name__.unwrap_or_default(), + stream_key: stream_key__.unwrap_or_default(), + url: url__.unwrap_or_default(), + input_type: input_type__.unwrap_or_default(), + bypass_transcoding: bypass_transcoding__.unwrap_or_default(), + enable_transcoding: enable_transcoding__, + audio: audio__, + video: video__, + room_name: room_name__.unwrap_or_default(), + participant_identity: participant_identity__.unwrap_or_default(), + participant_name: participant_name__.unwrap_or_default(), + participant_metadata: participant_metadata__.unwrap_or_default(), + reusable: reusable__.unwrap_or_default(), + state: state__, + }) + } + } + deserializer.deserialize_struct("livekit.IngressInfo", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for IngressInput { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + let variant = match self { + Self::RtmpInput => "RTMP_INPUT", + Self::WhipInput => "WHIP_INPUT", + Self::UrlInput => "URL_INPUT", }; serializer.serialize_str(variant) } } -impl<'de> serde::Deserialize<'de> for IngressVideoEncodingPreset { +impl<'de> serde::Deserialize<'de> for IngressInput { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "H264_720P_30FPS_3_LAYERS", - "H264_1080P_30FPS_3_LAYERS", - "H264_540P_25FPS_2_LAYERS", - "H264_720P_30FPS_1_LAYER", - "H264_1080P_30FPS_1_LAYER", - "H264_720P_30FPS_3_LAYERS_HIGH_MOTION", - "H264_1080P_30FPS_3_LAYERS_HIGH_MOTION", - "H264_540P_25FPS_2_LAYERS_HIGH_MOTION", - "H264_720P_30FPS_1_LAYER_HIGH_MOTION", - "H264_1080P_30FPS_1_LAYER_HIGH_MOTION", + "RTMP_INPUT", + "WHIP_INPUT", + "URL_INPUT", ]; struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = IngressVideoEncodingPreset; + type Value = IngressInput; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(formatter, "expected one of: {:?}", &FIELDS) @@ -9681,16 +10028,9 @@ impl<'de> serde::Deserialize<'de> for IngressVideoEncodingPreset { E: serde::de::Error, { match value { - "H264_720P_30FPS_3_LAYERS" => Ok(IngressVideoEncodingPreset::H264720p30fps3Layers), - "H264_1080P_30FPS_3_LAYERS" => Ok(IngressVideoEncodingPreset::H2641080p30fps3Layers), - "H264_540P_25FPS_2_LAYERS" => Ok(IngressVideoEncodingPreset::H264540p25fps2Layers), - "H264_720P_30FPS_1_LAYER" => Ok(IngressVideoEncodingPreset::H264720p30fps1Layer), - "H264_1080P_30FPS_1_LAYER" => Ok(IngressVideoEncodingPreset::H2641080p30fps1Layer), - "H264_720P_30FPS_3_LAYERS_HIGH_MOTION" => Ok(IngressVideoEncodingPreset::H264720p30fps3LayersHighMotion), - "H264_1080P_30FPS_3_LAYERS_HIGH_MOTION" => Ok(IngressVideoEncodingPreset::H2641080p30fps3LayersHighMotion), - "H264_540P_25FPS_2_LAYERS_HIGH_MOTION" => Ok(IngressVideoEncodingPreset::H264540p25fps2LayersHighMotion), - "H264_720P_30FPS_1_LAYER_HIGH_MOTION" => Ok(IngressVideoEncodingPreset::H264720p30fps1LayerHighMotion), - "H264_1080P_30FPS_1_LAYER_HIGH_MOTION" => Ok(IngressVideoEncodingPreset::H2641080p30fps1LayerHighMotion), + "RTMP_INPUT" => Ok(IngressInput::RtmpInput), + "WHIP_INPUT" => Ok(IngressInput::WhipInput), + "URL_INPUT" => Ok(IngressInput::UrlInput), _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), } } @@ -9698,7 +10038,7 @@ impl<'de> serde::Deserialize<'de> for IngressVideoEncodingPreset { deserializer.deserialize_any(GeneratedVisitor) } } -impl serde::Serialize for IngressVideoOptions { +impl serde::Serialize for IngressState { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -9706,65 +10046,121 @@ impl serde::Serialize for IngressVideoOptions { { use serde::ser::SerializeStruct; let mut len = 0; - if !self.name.is_empty() { + if self.status != 0 { len += 1; } - if self.source != 0 { + if !self.error.is_empty() { len += 1; } - if self.encoding_options.is_some() { + if self.video.is_some() { len += 1; } - let mut struct_ser = serializer.serialize_struct("livekit.IngressVideoOptions", len)?; - if !self.name.is_empty() { - struct_ser.serialize_field("name", &self.name)?; + if self.audio.is_some() { + len += 1; } - if self.source != 0 { - let v = TrackSource::try_from(self.source) - .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.source)))?; - struct_ser.serialize_field("source", &v)?; + if !self.room_id.is_empty() { + len += 1; } - if let Some(v) = self.encoding_options.as_ref() { - match v { - ingress_video_options::EncodingOptions::Preset(v) => { - let v = IngressVideoEncodingPreset::try_from(*v) - .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", *v)))?; - struct_ser.serialize_field("preset", &v)?; - } - ingress_video_options::EncodingOptions::Options(v) => { - struct_ser.serialize_field("options", v)?; - } - } + if self.started_at != 0 { + len += 1; + } + if self.ended_at != 0 { + len += 1; + } + if self.updated_at != 0 { + len += 1; + } + if !self.resource_id.is_empty() { + len += 1; + } + if !self.tracks.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.IngressState", len)?; + if self.status != 0 { + let v = ingress_state::Status::try_from(self.status) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.status)))?; + struct_ser.serialize_field("status", &v)?; + } + if !self.error.is_empty() { + struct_ser.serialize_field("error", &self.error)?; + } + if let Some(v) = self.video.as_ref() { + struct_ser.serialize_field("video", v)?; + } + if let Some(v) = self.audio.as_ref() { + struct_ser.serialize_field("audio", v)?; + } + if !self.room_id.is_empty() { + struct_ser.serialize_field("roomId", &self.room_id)?; + } + if self.started_at != 0 { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("startedAt", ToString::to_string(&self.started_at).as_str())?; + } + if self.ended_at != 0 { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("endedAt", ToString::to_string(&self.ended_at).as_str())?; + } + if self.updated_at != 0 { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("updatedAt", ToString::to_string(&self.updated_at).as_str())?; + } + if !self.resource_id.is_empty() { + struct_ser.serialize_field("resourceId", &self.resource_id)?; + } + if !self.tracks.is_empty() { + struct_ser.serialize_field("tracks", &self.tracks)?; } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for IngressVideoOptions { +impl<'de> serde::Deserialize<'de> for IngressState { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "name", - "source", - "preset", - "options", + "status", + "error", + "video", + "audio", + "room_id", + "roomId", + "started_at", + "startedAt", + "ended_at", + "endedAt", + "updated_at", + "updatedAt", + "resource_id", + "resourceId", + "tracks", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { - Name, - Source, - Preset, - Options, - __SkipField__, - } - impl<'de> serde::Deserialize<'de> for GeneratedField { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { + Status, + Error, + Video, + Audio, + RoomId, + StartedAt, + EndedAt, + UpdatedAt, + ResourceId, + Tracks, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { @@ -9780,10 +10176,16 @@ impl<'de> serde::Deserialize<'de> for IngressVideoOptions { E: serde::de::Error, { match value { - "name" => Ok(GeneratedField::Name), - "source" => Ok(GeneratedField::Source), - "preset" => Ok(GeneratedField::Preset), - "options" => Ok(GeneratedField::Options), + "status" => Ok(GeneratedField::Status), + "error" => Ok(GeneratedField::Error), + "video" => Ok(GeneratedField::Video), + "audio" => Ok(GeneratedField::Audio), + "roomId" | "room_id" => Ok(GeneratedField::RoomId), + "startedAt" | "started_at" => Ok(GeneratedField::StartedAt), + "endedAt" | "ended_at" => Ok(GeneratedField::EndedAt), + "updatedAt" | "updated_at" => Ok(GeneratedField::UpdatedAt), + "resourceId" | "resource_id" => Ok(GeneratedField::ResourceId), + "tracks" => Ok(GeneratedField::Tracks), _ => Ok(GeneratedField::__SkipField__), } } @@ -9793,62 +10195,197 @@ impl<'de> serde::Deserialize<'de> for IngressVideoOptions { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = IngressVideoOptions; + type Value = IngressState; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.IngressVideoOptions") + formatter.write_str("struct livekit.IngressState") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut name__ = None; - let mut source__ = None; - let mut encoding_options__ = None; + let mut status__ = None; + let mut error__ = None; + let mut video__ = None; + let mut audio__ = None; + let mut room_id__ = None; + let mut started_at__ = None; + let mut ended_at__ = None; + let mut updated_at__ = None; + let mut resource_id__ = None; + let mut tracks__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::Name => { - if name__.is_some() { - return Err(serde::de::Error::duplicate_field("name")); + GeneratedField::Status => { + if status__.is_some() { + return Err(serde::de::Error::duplicate_field("status")); } - name__ = Some(map_.next_value()?); + status__ = Some(map_.next_value::()? as i32); } - GeneratedField::Source => { - if source__.is_some() { - return Err(serde::de::Error::duplicate_field("source")); + GeneratedField::Error => { + if error__.is_some() { + return Err(serde::de::Error::duplicate_field("error")); } - source__ = Some(map_.next_value::()? as i32); + error__ = Some(map_.next_value()?); } - GeneratedField::Preset => { - if encoding_options__.is_some() { - return Err(serde::de::Error::duplicate_field("preset")); + GeneratedField::Video => { + if video__.is_some() { + return Err(serde::de::Error::duplicate_field("video")); } - encoding_options__ = map_.next_value::<::std::option::Option>()?.map(|x| ingress_video_options::EncodingOptions::Preset(x as i32)); + video__ = map_.next_value()?; } - GeneratedField::Options => { - if encoding_options__.is_some() { - return Err(serde::de::Error::duplicate_field("options")); + GeneratedField::Audio => { + if audio__.is_some() { + return Err(serde::de::Error::duplicate_field("audio")); } - encoding_options__ = map_.next_value::<::std::option::Option<_>>()?.map(ingress_video_options::EncodingOptions::Options) -; + audio__ = map_.next_value()?; + } + GeneratedField::RoomId => { + if room_id__.is_some() { + return Err(serde::de::Error::duplicate_field("roomId")); + } + room_id__ = Some(map_.next_value()?); + } + GeneratedField::StartedAt => { + if started_at__.is_some() { + return Err(serde::de::Error::duplicate_field("startedAt")); + } + started_at__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::EndedAt => { + if ended_at__.is_some() { + return Err(serde::de::Error::duplicate_field("endedAt")); + } + ended_at__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::UpdatedAt => { + if updated_at__.is_some() { + return Err(serde::de::Error::duplicate_field("updatedAt")); + } + updated_at__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::ResourceId => { + if resource_id__.is_some() { + return Err(serde::de::Error::duplicate_field("resourceId")); + } + resource_id__ = Some(map_.next_value()?); + } + GeneratedField::Tracks => { + if tracks__.is_some() { + return Err(serde::de::Error::duplicate_field("tracks")); + } + tracks__ = Some(map_.next_value()?); } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } } } - Ok(IngressVideoOptions { - name: name__.unwrap_or_default(), - source: source__.unwrap_or_default(), - encoding_options: encoding_options__, + Ok(IngressState { + status: status__.unwrap_or_default(), + error: error__.unwrap_or_default(), + video: video__, + audio: audio__, + room_id: room_id__.unwrap_or_default(), + started_at: started_at__.unwrap_or_default(), + ended_at: ended_at__.unwrap_or_default(), + updated_at: updated_at__.unwrap_or_default(), + resource_id: resource_id__.unwrap_or_default(), + tracks: tracks__.unwrap_or_default(), }) } } - deserializer.deserialize_struct("livekit.IngressVideoOptions", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.IngressState", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for InputAudioState { +impl serde::Serialize for ingress_state::Status { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + let variant = match self { + Self::EndpointInactive => "ENDPOINT_INACTIVE", + Self::EndpointBuffering => "ENDPOINT_BUFFERING", + Self::EndpointPublishing => "ENDPOINT_PUBLISHING", + Self::EndpointError => "ENDPOINT_ERROR", + Self::EndpointComplete => "ENDPOINT_COMPLETE", + }; + serializer.serialize_str(variant) + } +} +impl<'de> serde::Deserialize<'de> for ingress_state::Status { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "ENDPOINT_INACTIVE", + "ENDPOINT_BUFFERING", + "ENDPOINT_PUBLISHING", + "ENDPOINT_ERROR", + "ENDPOINT_COMPLETE", + ]; + + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = ingress_state::Status; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + fn visit_i64(self, v: i64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self) + }) + } + + fn visit_u64(self, v: u64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(v), &self) + }) + } + + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "ENDPOINT_INACTIVE" => Ok(ingress_state::Status::EndpointInactive), + "ENDPOINT_BUFFERING" => Ok(ingress_state::Status::EndpointBuffering), + "ENDPOINT_PUBLISHING" => Ok(ingress_state::Status::EndpointPublishing), + "ENDPOINT_ERROR" => Ok(ingress_state::Status::EndpointError), + "ENDPOINT_COMPLETE" => Ok(ingress_state::Status::EndpointComplete), + _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), + } + } + } + deserializer.deserialize_any(GeneratedVisitor) + } +} +impl serde::Serialize for IngressVideoEncodingOptions { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -9856,56 +10393,49 @@ impl serde::Serialize for InputAudioState { { use serde::ser::SerializeStruct; let mut len = 0; - if !self.mime_type.is_empty() { - len += 1; - } - if self.average_bitrate != 0 { + if self.video_codec != 0 { len += 1; } - if self.channels != 0 { + if self.frame_rate != 0. { len += 1; } - if self.sample_rate != 0 { + if !self.layers.is_empty() { len += 1; } - let mut struct_ser = serializer.serialize_struct("livekit.InputAudioState", len)?; - if !self.mime_type.is_empty() { - struct_ser.serialize_field("mimeType", &self.mime_type)?; - } - if self.average_bitrate != 0 { - struct_ser.serialize_field("averageBitrate", &self.average_bitrate)?; + let mut struct_ser = serializer.serialize_struct("livekit.IngressVideoEncodingOptions", len)?; + if self.video_codec != 0 { + let v = VideoCodec::try_from(self.video_codec) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.video_codec)))?; + struct_ser.serialize_field("videoCodec", &v)?; } - if self.channels != 0 { - struct_ser.serialize_field("channels", &self.channels)?; + if self.frame_rate != 0. { + struct_ser.serialize_field("frameRate", &self.frame_rate)?; } - if self.sample_rate != 0 { - struct_ser.serialize_field("sampleRate", &self.sample_rate)?; + if !self.layers.is_empty() { + struct_ser.serialize_field("layers", &self.layers)?; } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for InputAudioState { +impl<'de> serde::Deserialize<'de> for IngressVideoEncodingOptions { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "mime_type", - "mimeType", - "average_bitrate", - "averageBitrate", - "channels", - "sample_rate", - "sampleRate", + "video_codec", + "videoCodec", + "frame_rate", + "frameRate", + "layers", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { - MimeType, - AverageBitrate, - Channels, - SampleRate, + VideoCodec, + FrameRate, + Layers, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -9928,10 +10458,9 @@ impl<'de> serde::Deserialize<'de> for InputAudioState { E: serde::de::Error, { match value { - "mimeType" | "mime_type" => Ok(GeneratedField::MimeType), - "averageBitrate" | "average_bitrate" => Ok(GeneratedField::AverageBitrate), - "channels" => Ok(GeneratedField::Channels), - "sampleRate" | "sample_rate" => Ok(GeneratedField::SampleRate), + "videoCodec" | "video_codec" => Ok(GeneratedField::VideoCodec), + "frameRate" | "frame_rate" => Ok(GeneratedField::FrameRate), + "layers" => Ok(GeneratedField::Layers), _ => Ok(GeneratedField::__SkipField__), } } @@ -9941,69 +10470,152 @@ impl<'de> serde::Deserialize<'de> for InputAudioState { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = InputAudioState; + type Value = IngressVideoEncodingOptions; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.InputAudioState") + formatter.write_str("struct livekit.IngressVideoEncodingOptions") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut mime_type__ = None; - let mut average_bitrate__ = None; - let mut channels__ = None; - let mut sample_rate__ = None; + let mut video_codec__ = None; + let mut frame_rate__ = None; + let mut layers__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::MimeType => { - if mime_type__.is_some() { - return Err(serde::de::Error::duplicate_field("mimeType")); - } - mime_type__ = Some(map_.next_value()?); - } - GeneratedField::AverageBitrate => { - if average_bitrate__.is_some() { - return Err(serde::de::Error::duplicate_field("averageBitrate")); + GeneratedField::VideoCodec => { + if video_codec__.is_some() { + return Err(serde::de::Error::duplicate_field("videoCodec")); } - average_bitrate__ = - Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; + video_codec__ = Some(map_.next_value::()? as i32); } - GeneratedField::Channels => { - if channels__.is_some() { - return Err(serde::de::Error::duplicate_field("channels")); + GeneratedField::FrameRate => { + if frame_rate__.is_some() { + return Err(serde::de::Error::duplicate_field("frameRate")); } - channels__ = + frame_rate__ = Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) ; } - GeneratedField::SampleRate => { - if sample_rate__.is_some() { - return Err(serde::de::Error::duplicate_field("sampleRate")); + GeneratedField::Layers => { + if layers__.is_some() { + return Err(serde::de::Error::duplicate_field("layers")); } - sample_rate__ = - Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; + layers__ = Some(map_.next_value()?); } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } } } - Ok(InputAudioState { - mime_type: mime_type__.unwrap_or_default(), - average_bitrate: average_bitrate__.unwrap_or_default(), - channels: channels__.unwrap_or_default(), - sample_rate: sample_rate__.unwrap_or_default(), + Ok(IngressVideoEncodingOptions { + video_codec: video_codec__.unwrap_or_default(), + frame_rate: frame_rate__.unwrap_or_default(), + layers: layers__.unwrap_or_default(), }) } } - deserializer.deserialize_struct("livekit.InputAudioState", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.IngressVideoEncodingOptions", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for InputVideoState { +impl serde::Serialize for IngressVideoEncodingPreset { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + let variant = match self { + Self::H264720p30fps3Layers => "H264_720P_30FPS_3_LAYERS", + Self::H2641080p30fps3Layers => "H264_1080P_30FPS_3_LAYERS", + Self::H264540p25fps2Layers => "H264_540P_25FPS_2_LAYERS", + Self::H264720p30fps1Layer => "H264_720P_30FPS_1_LAYER", + Self::H2641080p30fps1Layer => "H264_1080P_30FPS_1_LAYER", + Self::H264720p30fps3LayersHighMotion => "H264_720P_30FPS_3_LAYERS_HIGH_MOTION", + Self::H2641080p30fps3LayersHighMotion => "H264_1080P_30FPS_3_LAYERS_HIGH_MOTION", + Self::H264540p25fps2LayersHighMotion => "H264_540P_25FPS_2_LAYERS_HIGH_MOTION", + Self::H264720p30fps1LayerHighMotion => "H264_720P_30FPS_1_LAYER_HIGH_MOTION", + Self::H2641080p30fps1LayerHighMotion => "H264_1080P_30FPS_1_LAYER_HIGH_MOTION", + }; + serializer.serialize_str(variant) + } +} +impl<'de> serde::Deserialize<'de> for IngressVideoEncodingPreset { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "H264_720P_30FPS_3_LAYERS", + "H264_1080P_30FPS_3_LAYERS", + "H264_540P_25FPS_2_LAYERS", + "H264_720P_30FPS_1_LAYER", + "H264_1080P_30FPS_1_LAYER", + "H264_720P_30FPS_3_LAYERS_HIGH_MOTION", + "H264_1080P_30FPS_3_LAYERS_HIGH_MOTION", + "H264_540P_25FPS_2_LAYERS_HIGH_MOTION", + "H264_720P_30FPS_1_LAYER_HIGH_MOTION", + "H264_1080P_30FPS_1_LAYER_HIGH_MOTION", + ]; + + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = IngressVideoEncodingPreset; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + fn visit_i64(self, v: i64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self) + }) + } + + fn visit_u64(self, v: u64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(v), &self) + }) + } + + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "H264_720P_30FPS_3_LAYERS" => Ok(IngressVideoEncodingPreset::H264720p30fps3Layers), + "H264_1080P_30FPS_3_LAYERS" => Ok(IngressVideoEncodingPreset::H2641080p30fps3Layers), + "H264_540P_25FPS_2_LAYERS" => Ok(IngressVideoEncodingPreset::H264540p25fps2Layers), + "H264_720P_30FPS_1_LAYER" => Ok(IngressVideoEncodingPreset::H264720p30fps1Layer), + "H264_1080P_30FPS_1_LAYER" => Ok(IngressVideoEncodingPreset::H2641080p30fps1Layer), + "H264_720P_30FPS_3_LAYERS_HIGH_MOTION" => Ok(IngressVideoEncodingPreset::H264720p30fps3LayersHighMotion), + "H264_1080P_30FPS_3_LAYERS_HIGH_MOTION" => Ok(IngressVideoEncodingPreset::H2641080p30fps3LayersHighMotion), + "H264_540P_25FPS_2_LAYERS_HIGH_MOTION" => Ok(IngressVideoEncodingPreset::H264540p25fps2LayersHighMotion), + "H264_720P_30FPS_1_LAYER_HIGH_MOTION" => Ok(IngressVideoEncodingPreset::H264720p30fps1LayerHighMotion), + "H264_1080P_30FPS_1_LAYER_HIGH_MOTION" => Ok(IngressVideoEncodingPreset::H2641080p30fps1LayerHighMotion), + _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), + } + } + } + deserializer.deserialize_any(GeneratedVisitor) + } +} +impl serde::Serialize for IngressVideoOptions { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -10011,63 +10623,58 @@ impl serde::Serialize for InputVideoState { { use serde::ser::SerializeStruct; let mut len = 0; - if !self.mime_type.is_empty() { - len += 1; - } - if self.average_bitrate != 0 { - len += 1; - } - if self.width != 0 { + if !self.name.is_empty() { len += 1; } - if self.height != 0 { + if self.source != 0 { len += 1; } - if self.framerate != 0. { + if self.encoding_options.is_some() { len += 1; } - let mut struct_ser = serializer.serialize_struct("livekit.InputVideoState", len)?; - if !self.mime_type.is_empty() { - struct_ser.serialize_field("mimeType", &self.mime_type)?; - } - if self.average_bitrate != 0 { - struct_ser.serialize_field("averageBitrate", &self.average_bitrate)?; - } - if self.width != 0 { - struct_ser.serialize_field("width", &self.width)?; + let mut struct_ser = serializer.serialize_struct("livekit.IngressVideoOptions", len)?; + if !self.name.is_empty() { + struct_ser.serialize_field("name", &self.name)?; } - if self.height != 0 { - struct_ser.serialize_field("height", &self.height)?; + if self.source != 0 { + let v = TrackSource::try_from(self.source) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.source)))?; + struct_ser.serialize_field("source", &v)?; } - if self.framerate != 0. { - struct_ser.serialize_field("framerate", &self.framerate)?; + if let Some(v) = self.encoding_options.as_ref() { + match v { + ingress_video_options::EncodingOptions::Preset(v) => { + let v = IngressVideoEncodingPreset::try_from(*v) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", *v)))?; + struct_ser.serialize_field("preset", &v)?; + } + ingress_video_options::EncodingOptions::Options(v) => { + struct_ser.serialize_field("options", v)?; + } + } } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for InputVideoState { +impl<'de> serde::Deserialize<'de> for IngressVideoOptions { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "mime_type", - "mimeType", - "average_bitrate", - "averageBitrate", - "width", - "height", - "framerate", + "name", + "source", + "preset", + "options", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { - MimeType, - AverageBitrate, - Width, - Height, - Framerate, + Name, + Source, + Preset, + Options, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -10090,11 +10697,10 @@ impl<'de> serde::Deserialize<'de> for InputVideoState { E: serde::de::Error, { match value { - "mimeType" | "mime_type" => Ok(GeneratedField::MimeType), - "averageBitrate" | "average_bitrate" => Ok(GeneratedField::AverageBitrate), - "width" => Ok(GeneratedField::Width), - "height" => Ok(GeneratedField::Height), - "framerate" => Ok(GeneratedField::Framerate), + "name" => Ok(GeneratedField::Name), + "source" => Ok(GeneratedField::Source), + "preset" => Ok(GeneratedField::Preset), + "options" => Ok(GeneratedField::Options), _ => Ok(GeneratedField::__SkipField__), } } @@ -10104,79 +10710,62 @@ impl<'de> serde::Deserialize<'de> for InputVideoState { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = InputVideoState; + type Value = IngressVideoOptions; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.InputVideoState") + formatter.write_str("struct livekit.IngressVideoOptions") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut mime_type__ = None; - let mut average_bitrate__ = None; - let mut width__ = None; - let mut height__ = None; - let mut framerate__ = None; + let mut name__ = None; + let mut source__ = None; + let mut encoding_options__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::MimeType => { - if mime_type__.is_some() { - return Err(serde::de::Error::duplicate_field("mimeType")); - } - mime_type__ = Some(map_.next_value()?); - } - GeneratedField::AverageBitrate => { - if average_bitrate__.is_some() { - return Err(serde::de::Error::duplicate_field("averageBitrate")); + GeneratedField::Name => { + if name__.is_some() { + return Err(serde::de::Error::duplicate_field("name")); } - average_bitrate__ = - Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; + name__ = Some(map_.next_value()?); } - GeneratedField::Width => { - if width__.is_some() { - return Err(serde::de::Error::duplicate_field("width")); + GeneratedField::Source => { + if source__.is_some() { + return Err(serde::de::Error::duplicate_field("source")); } - width__ = - Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; + source__ = Some(map_.next_value::()? as i32); } - GeneratedField::Height => { - if height__.is_some() { - return Err(serde::de::Error::duplicate_field("height")); + GeneratedField::Preset => { + if encoding_options__.is_some() { + return Err(serde::de::Error::duplicate_field("preset")); } - height__ = - Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; + encoding_options__ = map_.next_value::<::std::option::Option>()?.map(|x| ingress_video_options::EncodingOptions::Preset(x as i32)); } - GeneratedField::Framerate => { - if framerate__.is_some() { - return Err(serde::de::Error::duplicate_field("framerate")); + GeneratedField::Options => { + if encoding_options__.is_some() { + return Err(serde::de::Error::duplicate_field("options")); } - framerate__ = - Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; + encoding_options__ = map_.next_value::<::std::option::Option<_>>()?.map(ingress_video_options::EncodingOptions::Options) +; } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } } } - Ok(InputVideoState { - mime_type: mime_type__.unwrap_or_default(), - average_bitrate: average_bitrate__.unwrap_or_default(), - width: width__.unwrap_or_default(), - height: height__.unwrap_or_default(), - framerate: framerate__.unwrap_or_default(), + Ok(IngressVideoOptions { + name: name__.unwrap_or_default(), + source: source__.unwrap_or_default(), + encoding_options: encoding_options__, }) } } - deserializer.deserialize_struct("livekit.InputVideoState", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.IngressVideoOptions", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for Job { +impl serde::Serialize for InputAudioState { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -10184,97 +10773,56 @@ impl serde::Serialize for Job { { use serde::ser::SerializeStruct; let mut len = 0; - if !self.id.is_empty() { - len += 1; - } - if !self.dispatch_id.is_empty() { - len += 1; - } - if self.r#type != 0 { - len += 1; - } - if self.room.is_some() { - len += 1; - } - if self.participant.is_some() { - len += 1; - } - if !self.namespace.is_empty() { + if !self.mime_type.is_empty() { len += 1; } - if !self.metadata.is_empty() { + if self.average_bitrate != 0 { len += 1; } - if !self.agent_name.is_empty() { + if self.channels != 0 { len += 1; } - if self.state.is_some() { + if self.sample_rate != 0 { len += 1; } - let mut struct_ser = serializer.serialize_struct("livekit.Job", len)?; - if !self.id.is_empty() { - struct_ser.serialize_field("id", &self.id)?; - } - if !self.dispatch_id.is_empty() { - struct_ser.serialize_field("dispatchId", &self.dispatch_id)?; - } - if self.r#type != 0 { - let v = JobType::try_from(self.r#type) - .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.r#type)))?; - struct_ser.serialize_field("type", &v)?; - } - if let Some(v) = self.room.as_ref() { - struct_ser.serialize_field("room", v)?; - } - if let Some(v) = self.participant.as_ref() { - struct_ser.serialize_field("participant", v)?; - } - if !self.namespace.is_empty() { - struct_ser.serialize_field("namespace", &self.namespace)?; + let mut struct_ser = serializer.serialize_struct("livekit.InputAudioState", len)?; + if !self.mime_type.is_empty() { + struct_ser.serialize_field("mimeType", &self.mime_type)?; } - if !self.metadata.is_empty() { - struct_ser.serialize_field("metadata", &self.metadata)?; + if self.average_bitrate != 0 { + struct_ser.serialize_field("averageBitrate", &self.average_bitrate)?; } - if !self.agent_name.is_empty() { - struct_ser.serialize_field("agentName", &self.agent_name)?; + if self.channels != 0 { + struct_ser.serialize_field("channels", &self.channels)?; } - if let Some(v) = self.state.as_ref() { - struct_ser.serialize_field("state", v)?; + if self.sample_rate != 0 { + struct_ser.serialize_field("sampleRate", &self.sample_rate)?; } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for Job { +impl<'de> serde::Deserialize<'de> for InputAudioState { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "id", - "dispatch_id", - "dispatchId", - "type", - "room", - "participant", - "namespace", - "metadata", - "agent_name", - "agentName", - "state", + "mime_type", + "mimeType", + "average_bitrate", + "averageBitrate", + "channels", + "sample_rate", + "sampleRate", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { - Id, - DispatchId, - Type, - Room, - Participant, - Namespace, - Metadata, - AgentName, - State, + MimeType, + AverageBitrate, + Channels, + SampleRate, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -10297,15 +10845,10 @@ impl<'de> serde::Deserialize<'de> for Job { E: serde::de::Error, { match value { - "id" => Ok(GeneratedField::Id), - "dispatchId" | "dispatch_id" => Ok(GeneratedField::DispatchId), - "type" => Ok(GeneratedField::Type), - "room" => Ok(GeneratedField::Room), - "participant" => Ok(GeneratedField::Participant), - "namespace" => Ok(GeneratedField::Namespace), - "metadata" => Ok(GeneratedField::Metadata), - "agentName" | "agent_name" => Ok(GeneratedField::AgentName), - "state" => Ok(GeneratedField::State), + "mimeType" | "mime_type" => Ok(GeneratedField::MimeType), + "averageBitrate" | "average_bitrate" => Ok(GeneratedField::AverageBitrate), + "channels" => Ok(GeneratedField::Channels), + "sampleRate" | "sample_rate" => Ok(GeneratedField::SampleRate), _ => Ok(GeneratedField::__SkipField__), } } @@ -10315,103 +10858,69 @@ impl<'de> serde::Deserialize<'de> for Job { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = Job; + type Value = InputAudioState; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.Job") + formatter.write_str("struct livekit.InputAudioState") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut id__ = None; - let mut dispatch_id__ = None; - let mut r#type__ = None; - let mut room__ = None; - let mut participant__ = None; - let mut namespace__ = None; - let mut metadata__ = None; - let mut agent_name__ = None; - let mut state__ = None; + let mut mime_type__ = None; + let mut average_bitrate__ = None; + let mut channels__ = None; + let mut sample_rate__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::Id => { - if id__.is_some() { - return Err(serde::de::Error::duplicate_field("id")); - } - id__ = Some(map_.next_value()?); - } - GeneratedField::DispatchId => { - if dispatch_id__.is_some() { - return Err(serde::de::Error::duplicate_field("dispatchId")); - } - dispatch_id__ = Some(map_.next_value()?); - } - GeneratedField::Type => { - if r#type__.is_some() { - return Err(serde::de::Error::duplicate_field("type")); + GeneratedField::MimeType => { + if mime_type__.is_some() { + return Err(serde::de::Error::duplicate_field("mimeType")); } - r#type__ = Some(map_.next_value::()? as i32); + mime_type__ = Some(map_.next_value()?); } - GeneratedField::Room => { - if room__.is_some() { - return Err(serde::de::Error::duplicate_field("room")); + GeneratedField::AverageBitrate => { + if average_bitrate__.is_some() { + return Err(serde::de::Error::duplicate_field("averageBitrate")); } - room__ = map_.next_value()?; + average_bitrate__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; } - GeneratedField::Participant => { - if participant__.is_some() { - return Err(serde::de::Error::duplicate_field("participant")); + GeneratedField::Channels => { + if channels__.is_some() { + return Err(serde::de::Error::duplicate_field("channels")); } - participant__ = map_.next_value()?; + channels__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; } - GeneratedField::Namespace => { - if namespace__.is_some() { - return Err(serde::de::Error::duplicate_field("namespace")); + GeneratedField::SampleRate => { + if sample_rate__.is_some() { + return Err(serde::de::Error::duplicate_field("sampleRate")); } - namespace__ = Some(map_.next_value()?); + sample_rate__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; } - GeneratedField::Metadata => { - if metadata__.is_some() { - return Err(serde::de::Error::duplicate_field("metadata")); - } - metadata__ = Some(map_.next_value()?); - } - GeneratedField::AgentName => { - if agent_name__.is_some() { - return Err(serde::de::Error::duplicate_field("agentName")); - } - agent_name__ = Some(map_.next_value()?); - } - GeneratedField::State => { - if state__.is_some() { - return Err(serde::de::Error::duplicate_field("state")); - } - state__ = map_.next_value()?; - } - GeneratedField::__SkipField__ => { - let _ = map_.next_value::()?; + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; } } } - Ok(Job { - id: id__.unwrap_or_default(), - dispatch_id: dispatch_id__.unwrap_or_default(), - r#type: r#type__.unwrap_or_default(), - room: room__, - participant: participant__, - namespace: namespace__.unwrap_or_default(), - metadata: metadata__.unwrap_or_default(), - agent_name: agent_name__.unwrap_or_default(), - state: state__, + Ok(InputAudioState { + mime_type: mime_type__.unwrap_or_default(), + average_bitrate: average_bitrate__.unwrap_or_default(), + channels: channels__.unwrap_or_default(), + sample_rate: sample_rate__.unwrap_or_default(), }) } } - deserializer.deserialize_struct("livekit.Job", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.InputAudioState", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for JobAssignment { +impl serde::Serialize for InputVideoState { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -10419,45 +10928,63 @@ impl serde::Serialize for JobAssignment { { use serde::ser::SerializeStruct; let mut len = 0; - if self.job.is_some() { + if !self.mime_type.is_empty() { len += 1; } - if self.url.is_some() { + if self.average_bitrate != 0 { len += 1; } - if !self.token.is_empty() { + if self.width != 0 { len += 1; } - let mut struct_ser = serializer.serialize_struct("livekit.JobAssignment", len)?; - if let Some(v) = self.job.as_ref() { - struct_ser.serialize_field("job", v)?; + if self.height != 0 { + len += 1; } - if let Some(v) = self.url.as_ref() { - struct_ser.serialize_field("url", v)?; + if self.framerate != 0. { + len += 1; } - if !self.token.is_empty() { - struct_ser.serialize_field("token", &self.token)?; + let mut struct_ser = serializer.serialize_struct("livekit.InputVideoState", len)?; + if !self.mime_type.is_empty() { + struct_ser.serialize_field("mimeType", &self.mime_type)?; + } + if self.average_bitrate != 0 { + struct_ser.serialize_field("averageBitrate", &self.average_bitrate)?; + } + if self.width != 0 { + struct_ser.serialize_field("width", &self.width)?; + } + if self.height != 0 { + struct_ser.serialize_field("height", &self.height)?; + } + if self.framerate != 0. { + struct_ser.serialize_field("framerate", &self.framerate)?; } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for JobAssignment { +impl<'de> serde::Deserialize<'de> for InputVideoState { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "job", - "url", - "token", + "mime_type", + "mimeType", + "average_bitrate", + "averageBitrate", + "width", + "height", + "framerate", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { - Job, - Url, - Token, + MimeType, + AverageBitrate, + Width, + Height, + Framerate, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -10480,9 +11007,11 @@ impl<'de> serde::Deserialize<'de> for JobAssignment { E: serde::de::Error, { match value { - "job" => Ok(GeneratedField::Job), - "url" => Ok(GeneratedField::Url), - "token" => Ok(GeneratedField::Token), + "mimeType" | "mime_type" => Ok(GeneratedField::MimeType), + "averageBitrate" | "average_bitrate" => Ok(GeneratedField::AverageBitrate), + "width" => Ok(GeneratedField::Width), + "height" => Ok(GeneratedField::Height), + "framerate" => Ok(GeneratedField::Framerate), _ => Ok(GeneratedField::__SkipField__), } } @@ -10492,55 +11021,79 @@ impl<'de> serde::Deserialize<'de> for JobAssignment { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = JobAssignment; + type Value = InputVideoState; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.JobAssignment") + formatter.write_str("struct livekit.InputVideoState") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut job__ = None; - let mut url__ = None; - let mut token__ = None; + let mut mime_type__ = None; + let mut average_bitrate__ = None; + let mut width__ = None; + let mut height__ = None; + let mut framerate__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::Job => { - if job__.is_some() { - return Err(serde::de::Error::duplicate_field("job")); + GeneratedField::MimeType => { + if mime_type__.is_some() { + return Err(serde::de::Error::duplicate_field("mimeType")); } - job__ = map_.next_value()?; + mime_type__ = Some(map_.next_value()?); } - GeneratedField::Url => { - if url__.is_some() { - return Err(serde::de::Error::duplicate_field("url")); + GeneratedField::AverageBitrate => { + if average_bitrate__.is_some() { + return Err(serde::de::Error::duplicate_field("averageBitrate")); } - url__ = map_.next_value()?; + average_bitrate__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; } - GeneratedField::Token => { - if token__.is_some() { - return Err(serde::de::Error::duplicate_field("token")); + GeneratedField::Width => { + if width__.is_some() { + return Err(serde::de::Error::duplicate_field("width")); } - token__ = Some(map_.next_value()?); + width__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::Height => { + if height__.is_some() { + return Err(serde::de::Error::duplicate_field("height")); + } + height__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::Framerate => { + if framerate__.is_some() { + return Err(serde::de::Error::duplicate_field("framerate")); + } + framerate__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } } } - Ok(JobAssignment { - job: job__, - url: url__, - token: token__.unwrap_or_default(), + Ok(InputVideoState { + mime_type: mime_type__.unwrap_or_default(), + average_bitrate: average_bitrate__.unwrap_or_default(), + width: width__.unwrap_or_default(), + height: height__.unwrap_or_default(), + framerate: framerate__.unwrap_or_default(), }) } } - deserializer.deserialize_struct("livekit.JobAssignment", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.InputVideoState", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for JobState { +impl serde::Serialize for Job { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -10548,81 +11101,97 @@ impl serde::Serialize for JobState { { use serde::ser::SerializeStruct; let mut len = 0; - if self.status != 0 { + if !self.id.is_empty() { len += 1; } - if !self.error.is_empty() { + if !self.dispatch_id.is_empty() { len += 1; } - if self.started_at != 0 { + if self.r#type != 0 { len += 1; } - if self.ended_at != 0 { + if self.room.is_some() { len += 1; } - if self.updated_at != 0 { + if self.participant.is_some() { len += 1; } - if !self.participant_identity.is_empty() { + if !self.namespace.is_empty() { len += 1; } - let mut struct_ser = serializer.serialize_struct("livekit.JobState", len)?; - if self.status != 0 { - let v = JobStatus::try_from(self.status) - .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.status)))?; - struct_ser.serialize_field("status", &v)?; + if !self.metadata.is_empty() { + len += 1; } - if !self.error.is_empty() { - struct_ser.serialize_field("error", &self.error)?; + if !self.agent_name.is_empty() { + len += 1; } - if self.started_at != 0 { - #[allow(clippy::needless_borrow)] - #[allow(clippy::needless_borrows_for_generic_args)] - struct_ser.serialize_field("startedAt", ToString::to_string(&self.started_at).as_str())?; + if self.state.is_some() { + len += 1; } - if self.ended_at != 0 { - #[allow(clippy::needless_borrow)] - #[allow(clippy::needless_borrows_for_generic_args)] - struct_ser.serialize_field("endedAt", ToString::to_string(&self.ended_at).as_str())?; + let mut struct_ser = serializer.serialize_struct("livekit.Job", len)?; + if !self.id.is_empty() { + struct_ser.serialize_field("id", &self.id)?; } - if self.updated_at != 0 { - #[allow(clippy::needless_borrow)] - #[allow(clippy::needless_borrows_for_generic_args)] - struct_ser.serialize_field("updatedAt", ToString::to_string(&self.updated_at).as_str())?; + if !self.dispatch_id.is_empty() { + struct_ser.serialize_field("dispatchId", &self.dispatch_id)?; } - if !self.participant_identity.is_empty() { - struct_ser.serialize_field("participantIdentity", &self.participant_identity)?; + if self.r#type != 0 { + let v = JobType::try_from(self.r#type) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.r#type)))?; + struct_ser.serialize_field("type", &v)?; + } + if let Some(v) = self.room.as_ref() { + struct_ser.serialize_field("room", v)?; + } + if let Some(v) = self.participant.as_ref() { + struct_ser.serialize_field("participant", v)?; + } + if !self.namespace.is_empty() { + struct_ser.serialize_field("namespace", &self.namespace)?; + } + if !self.metadata.is_empty() { + struct_ser.serialize_field("metadata", &self.metadata)?; + } + if !self.agent_name.is_empty() { + struct_ser.serialize_field("agentName", &self.agent_name)?; + } + if let Some(v) = self.state.as_ref() { + struct_ser.serialize_field("state", v)?; } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for JobState { +impl<'de> serde::Deserialize<'de> for Job { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "status", - "error", - "started_at", - "startedAt", - "ended_at", - "endedAt", - "updated_at", - "updatedAt", - "participant_identity", - "participantIdentity", + "id", + "dispatch_id", + "dispatchId", + "type", + "room", + "participant", + "namespace", + "metadata", + "agent_name", + "agentName", + "state", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { - Status, - Error, - StartedAt, - EndedAt, - UpdatedAt, - ParticipantIdentity, + Id, + DispatchId, + Type, + Room, + Participant, + Namespace, + Metadata, + AgentName, + State, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -10645,12 +11214,15 @@ impl<'de> serde::Deserialize<'de> for JobState { E: serde::de::Error, { match value { - "status" => Ok(GeneratedField::Status), - "error" => Ok(GeneratedField::Error), - "startedAt" | "started_at" => Ok(GeneratedField::StartedAt), - "endedAt" | "ended_at" => Ok(GeneratedField::EndedAt), - "updatedAt" | "updated_at" => Ok(GeneratedField::UpdatedAt), - "participantIdentity" | "participant_identity" => Ok(GeneratedField::ParticipantIdentity), + "id" => Ok(GeneratedField::Id), + "dispatchId" | "dispatch_id" => Ok(GeneratedField::DispatchId), + "type" => Ok(GeneratedField::Type), + "room" => Ok(GeneratedField::Room), + "participant" => Ok(GeneratedField::Participant), + "namespace" => Ok(GeneratedField::Namespace), + "metadata" => Ok(GeneratedField::Metadata), + "agentName" | "agent_name" => Ok(GeneratedField::AgentName), + "state" => Ok(GeneratedField::State), _ => Ok(GeneratedField::__SkipField__), } } @@ -10660,162 +11232,103 @@ impl<'de> serde::Deserialize<'de> for JobState { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = JobState; + type Value = Job; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.JobState") + formatter.write_str("struct livekit.Job") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut status__ = None; - let mut error__ = None; - let mut started_at__ = None; - let mut ended_at__ = None; - let mut updated_at__ = None; - let mut participant_identity__ = None; + let mut id__ = None; + let mut dispatch_id__ = None; + let mut r#type__ = None; + let mut room__ = None; + let mut participant__ = None; + let mut namespace__ = None; + let mut metadata__ = None; + let mut agent_name__ = None; + let mut state__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::Status => { - if status__.is_some() { - return Err(serde::de::Error::duplicate_field("status")); + GeneratedField::Id => { + if id__.is_some() { + return Err(serde::de::Error::duplicate_field("id")); } - status__ = Some(map_.next_value::()? as i32); + id__ = Some(map_.next_value()?); } - GeneratedField::Error => { - if error__.is_some() { - return Err(serde::de::Error::duplicate_field("error")); + GeneratedField::DispatchId => { + if dispatch_id__.is_some() { + return Err(serde::de::Error::duplicate_field("dispatchId")); } - error__ = Some(map_.next_value()?); + dispatch_id__ = Some(map_.next_value()?); } - GeneratedField::StartedAt => { - if started_at__.is_some() { - return Err(serde::de::Error::duplicate_field("startedAt")); + GeneratedField::Type => { + if r#type__.is_some() { + return Err(serde::de::Error::duplicate_field("type")); } - started_at__ = - Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; + r#type__ = Some(map_.next_value::()? as i32); } - GeneratedField::EndedAt => { - if ended_at__.is_some() { - return Err(serde::de::Error::duplicate_field("endedAt")); + GeneratedField::Room => { + if room__.is_some() { + return Err(serde::de::Error::duplicate_field("room")); } - ended_at__ = - Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; + room__ = map_.next_value()?; } - GeneratedField::UpdatedAt => { - if updated_at__.is_some() { - return Err(serde::de::Error::duplicate_field("updatedAt")); + GeneratedField::Participant => { + if participant__.is_some() { + return Err(serde::de::Error::duplicate_field("participant")); } - updated_at__ = - Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; + participant__ = map_.next_value()?; } - GeneratedField::ParticipantIdentity => { - if participant_identity__.is_some() { - return Err(serde::de::Error::duplicate_field("participantIdentity")); + GeneratedField::Namespace => { + if namespace__.is_some() { + return Err(serde::de::Error::duplicate_field("namespace")); } - participant_identity__ = Some(map_.next_value()?); + namespace__ = Some(map_.next_value()?); + } + GeneratedField::Metadata => { + if metadata__.is_some() { + return Err(serde::de::Error::duplicate_field("metadata")); + } + metadata__ = Some(map_.next_value()?); + } + GeneratedField::AgentName => { + if agent_name__.is_some() { + return Err(serde::de::Error::duplicate_field("agentName")); + } + agent_name__ = Some(map_.next_value()?); + } + GeneratedField::State => { + if state__.is_some() { + return Err(serde::de::Error::duplicate_field("state")); + } + state__ = map_.next_value()?; } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } } } - Ok(JobState { - status: status__.unwrap_or_default(), - error: error__.unwrap_or_default(), - started_at: started_at__.unwrap_or_default(), - ended_at: ended_at__.unwrap_or_default(), - updated_at: updated_at__.unwrap_or_default(), - participant_identity: participant_identity__.unwrap_or_default(), + Ok(Job { + id: id__.unwrap_or_default(), + dispatch_id: dispatch_id__.unwrap_or_default(), + r#type: r#type__.unwrap_or_default(), + room: room__, + participant: participant__, + namespace: namespace__.unwrap_or_default(), + metadata: metadata__.unwrap_or_default(), + agent_name: agent_name__.unwrap_or_default(), + state: state__, }) } } - deserializer.deserialize_struct("livekit.JobState", FIELDS, GeneratedVisitor) - } -} -impl serde::Serialize for JobStatus { - #[allow(deprecated)] - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::Serializer, - { - let variant = match self { - Self::JsPending => "JS_PENDING", - Self::JsRunning => "JS_RUNNING", - Self::JsSuccess => "JS_SUCCESS", - Self::JsFailed => "JS_FAILED", - }; - serializer.serialize_str(variant) - } -} -impl<'de> serde::Deserialize<'de> for JobStatus { - #[allow(deprecated)] - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - const FIELDS: &[&str] = &[ - "JS_PENDING", - "JS_RUNNING", - "JS_SUCCESS", - "JS_FAILED", - ]; - - struct GeneratedVisitor; - - impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = JobStatus; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(formatter, "expected one of: {:?}", &FIELDS) - } - - fn visit_i64(self, v: i64) -> std::result::Result - where - E: serde::de::Error, - { - i32::try_from(v) - .ok() - .and_then(|x| x.try_into().ok()) - .ok_or_else(|| { - serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self) - }) - } - - fn visit_u64(self, v: u64) -> std::result::Result - where - E: serde::de::Error, - { - i32::try_from(v) - .ok() - .and_then(|x| x.try_into().ok()) - .ok_or_else(|| { - serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(v), &self) - }) - } - - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - match value { - "JS_PENDING" => Ok(JobStatus::JsPending), - "JS_RUNNING" => Ok(JobStatus::JsRunning), - "JS_SUCCESS" => Ok(JobStatus::JsSuccess), - "JS_FAILED" => Ok(JobStatus::JsFailed), - _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), - } - } - } - deserializer.deserialize_any(GeneratedVisitor) + deserializer.deserialize_struct("livekit.Job", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for JobTermination { +impl serde::Serialize for JobAssignment { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -10823,30 +11336,45 @@ impl serde::Serialize for JobTermination { { use serde::ser::SerializeStruct; let mut len = 0; - if !self.job_id.is_empty() { + if self.job.is_some() { len += 1; } - let mut struct_ser = serializer.serialize_struct("livekit.JobTermination", len)?; - if !self.job_id.is_empty() { - struct_ser.serialize_field("jobId", &self.job_id)?; + if self.url.is_some() { + len += 1; + } + if !self.token.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.JobAssignment", len)?; + if let Some(v) = self.job.as_ref() { + struct_ser.serialize_field("job", v)?; + } + if let Some(v) = self.url.as_ref() { + struct_ser.serialize_field("url", v)?; + } + if !self.token.is_empty() { + struct_ser.serialize_field("token", &self.token)?; } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for JobTermination { +impl<'de> serde::Deserialize<'de> for JobAssignment { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "job_id", - "jobId", + "job", + "url", + "token", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { - JobId, + Job, + Url, + Token, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -10869,7 +11397,9 @@ impl<'de> serde::Deserialize<'de> for JobTermination { E: serde::de::Error, { match value { - "jobId" | "job_id" => Ok(GeneratedField::JobId), + "job" => Ok(GeneratedField::Job), + "url" => Ok(GeneratedField::Url), + "token" => Ok(GeneratedField::Token), _ => Ok(GeneratedField::__SkipField__), } } @@ -10879,66 +11409,284 @@ impl<'de> serde::Deserialize<'de> for JobTermination { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = JobTermination; + type Value = JobAssignment; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.JobTermination") + formatter.write_str("struct livekit.JobAssignment") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut job_id__ = None; + let mut job__ = None; + let mut url__ = None; + let mut token__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::JobId => { - if job_id__.is_some() { - return Err(serde::de::Error::duplicate_field("jobId")); + GeneratedField::Job => { + if job__.is_some() { + return Err(serde::de::Error::duplicate_field("job")); } - job_id__ = Some(map_.next_value()?); + job__ = map_.next_value()?; + } + GeneratedField::Url => { + if url__.is_some() { + return Err(serde::de::Error::duplicate_field("url")); + } + url__ = map_.next_value()?; + } + GeneratedField::Token => { + if token__.is_some() { + return Err(serde::de::Error::duplicate_field("token")); + } + token__ = Some(map_.next_value()?); } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } } } - Ok(JobTermination { - job_id: job_id__.unwrap_or_default(), + Ok(JobAssignment { + job: job__, + url: url__, + token: token__.unwrap_or_default(), }) } } - deserializer.deserialize_struct("livekit.JobTermination", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.JobAssignment", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for JobType { +impl serde::Serialize for JobState { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.status != 0 { + len += 1; + } + if !self.error.is_empty() { + len += 1; + } + if self.started_at != 0 { + len += 1; + } + if self.ended_at != 0 { + len += 1; + } + if self.updated_at != 0 { + len += 1; + } + if !self.participant_identity.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.JobState", len)?; + if self.status != 0 { + let v = JobStatus::try_from(self.status) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.status)))?; + struct_ser.serialize_field("status", &v)?; + } + if !self.error.is_empty() { + struct_ser.serialize_field("error", &self.error)?; + } + if self.started_at != 0 { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("startedAt", ToString::to_string(&self.started_at).as_str())?; + } + if self.ended_at != 0 { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("endedAt", ToString::to_string(&self.ended_at).as_str())?; + } + if self.updated_at != 0 { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("updatedAt", ToString::to_string(&self.updated_at).as_str())?; + } + if !self.participant_identity.is_empty() { + struct_ser.serialize_field("participantIdentity", &self.participant_identity)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for JobState { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "status", + "error", + "started_at", + "startedAt", + "ended_at", + "endedAt", + "updated_at", + "updatedAt", + "participant_identity", + "participantIdentity", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Status, + Error, + StartedAt, + EndedAt, + UpdatedAt, + ParticipantIdentity, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "status" => Ok(GeneratedField::Status), + "error" => Ok(GeneratedField::Error), + "startedAt" | "started_at" => Ok(GeneratedField::StartedAt), + "endedAt" | "ended_at" => Ok(GeneratedField::EndedAt), + "updatedAt" | "updated_at" => Ok(GeneratedField::UpdatedAt), + "participantIdentity" | "participant_identity" => Ok(GeneratedField::ParticipantIdentity), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = JobState; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.JobState") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut status__ = None; + let mut error__ = None; + let mut started_at__ = None; + let mut ended_at__ = None; + let mut updated_at__ = None; + let mut participant_identity__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Status => { + if status__.is_some() { + return Err(serde::de::Error::duplicate_field("status")); + } + status__ = Some(map_.next_value::()? as i32); + } + GeneratedField::Error => { + if error__.is_some() { + return Err(serde::de::Error::duplicate_field("error")); + } + error__ = Some(map_.next_value()?); + } + GeneratedField::StartedAt => { + if started_at__.is_some() { + return Err(serde::de::Error::duplicate_field("startedAt")); + } + started_at__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::EndedAt => { + if ended_at__.is_some() { + return Err(serde::de::Error::duplicate_field("endedAt")); + } + ended_at__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::UpdatedAt => { + if updated_at__.is_some() { + return Err(serde::de::Error::duplicate_field("updatedAt")); + } + updated_at__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::ParticipantIdentity => { + if participant_identity__.is_some() { + return Err(serde::de::Error::duplicate_field("participantIdentity")); + } + participant_identity__ = Some(map_.next_value()?); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(JobState { + status: status__.unwrap_or_default(), + error: error__.unwrap_or_default(), + started_at: started_at__.unwrap_or_default(), + ended_at: ended_at__.unwrap_or_default(), + updated_at: updated_at__.unwrap_or_default(), + participant_identity: participant_identity__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.JobState", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for JobStatus { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where S: serde::Serializer, { let variant = match self { - Self::JtRoom => "JT_ROOM", - Self::JtPublisher => "JT_PUBLISHER", + Self::JsPending => "JS_PENDING", + Self::JsRunning => "JS_RUNNING", + Self::JsSuccess => "JS_SUCCESS", + Self::JsFailed => "JS_FAILED", }; serializer.serialize_str(variant) } } -impl<'de> serde::Deserialize<'de> for JobType { +impl<'de> serde::Deserialize<'de> for JobStatus { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "JT_ROOM", - "JT_PUBLISHER", + "JS_PENDING", + "JS_RUNNING", + "JS_SUCCESS", + "JS_FAILED", ]; struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = JobType; + type Value = JobStatus; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(formatter, "expected one of: {:?}", &FIELDS) @@ -10973,8 +11721,10 @@ impl<'de> serde::Deserialize<'de> for JobType { E: serde::de::Error, { match value { - "JT_ROOM" => Ok(JobType::JtRoom), - "JT_PUBLISHER" => Ok(JobType::JtPublisher), + "JS_PENDING" => Ok(JobStatus::JsPending), + "JS_RUNNING" => Ok(JobStatus::JsRunning), + "JS_SUCCESS" => Ok(JobStatus::JsSuccess), + "JS_FAILED" => Ok(JobStatus::JsFailed), _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), } } @@ -10982,7 +11732,174 @@ impl<'de> serde::Deserialize<'de> for JobType { deserializer.deserialize_any(GeneratedVisitor) } } -impl serde::Serialize for JoinResponse { +impl serde::Serialize for JobTermination { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.job_id.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.JobTermination", len)?; + if !self.job_id.is_empty() { + struct_ser.serialize_field("jobId", &self.job_id)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for JobTermination { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "job_id", + "jobId", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + JobId, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "jobId" | "job_id" => Ok(GeneratedField::JobId), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = JobTermination; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.JobTermination") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut job_id__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::JobId => { + if job_id__.is_some() { + return Err(serde::de::Error::duplicate_field("jobId")); + } + job_id__ = Some(map_.next_value()?); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(JobTermination { + job_id: job_id__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.JobTermination", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for JobType { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + let variant = match self { + Self::JtRoom => "JT_ROOM", + Self::JtPublisher => "JT_PUBLISHER", + }; + serializer.serialize_str(variant) + } +} +impl<'de> serde::Deserialize<'de> for JobType { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "JT_ROOM", + "JT_PUBLISHER", + ]; + + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = JobType; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + fn visit_i64(self, v: i64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self) + }) + } + + fn visit_u64(self, v: u64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(v), &self) + }) + } + + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "JT_ROOM" => Ok(JobType::JtRoom), + "JT_PUBLISHER" => Ok(JobType::JtPublisher), + _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), + } + } + } + deserializer.deserialize_any(GeneratedVisitor) + } +} +impl serde::Serialize for JoinResponse { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -11029,6 +11946,12 @@ impl serde::Serialize for JoinResponse { if !self.sif_trailer.is_empty() { len += 1; } + if !self.enabled_publish_codecs.is_empty() { + len += 1; + } + if self.fast_publish { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.JoinResponse", len)?; if let Some(v) = self.room.as_ref() { struct_ser.serialize_field("room", v)?; @@ -11071,6 +11994,12 @@ impl serde::Serialize for JoinResponse { #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("sifTrailer", pbjson::private::base64::encode(&self.sif_trailer).as_str())?; } + if !self.enabled_publish_codecs.is_empty() { + struct_ser.serialize_field("enabledPublishCodecs", &self.enabled_publish_codecs)?; + } + if self.fast_publish { + struct_ser.serialize_field("fastPublish", &self.fast_publish)?; + } struct_ser.end() } } @@ -11105,6 +12034,10 @@ impl<'de> serde::Deserialize<'de> for JoinResponse { "serverInfo", "sif_trailer", "sifTrailer", + "enabled_publish_codecs", + "enabledPublishCodecs", + "fast_publish", + "fastPublish", ]; #[allow(clippy::enum_variant_names)] @@ -11122,6 +12055,8 @@ impl<'de> serde::Deserialize<'de> for JoinResponse { PingInterval, ServerInfo, SifTrailer, + EnabledPublishCodecs, + FastPublish, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -11157,6 +12092,8 @@ impl<'de> serde::Deserialize<'de> for JoinResponse { "pingInterval" | "ping_interval" => Ok(GeneratedField::PingInterval), "serverInfo" | "server_info" => Ok(GeneratedField::ServerInfo), "sifTrailer" | "sif_trailer" => Ok(GeneratedField::SifTrailer), + "enabledPublishCodecs" | "enabled_publish_codecs" => Ok(GeneratedField::EnabledPublishCodecs), + "fastPublish" | "fast_publish" => Ok(GeneratedField::FastPublish), _ => Ok(GeneratedField::__SkipField__), } } @@ -11189,6 +12126,8 @@ impl<'de> serde::Deserialize<'de> for JoinResponse { let mut ping_interval__ = None; let mut server_info__ = None; let mut sif_trailer__ = None; + let mut enabled_publish_codecs__ = None; + let mut fast_publish__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::Room => { @@ -11275,6 +12214,18 @@ impl<'de> serde::Deserialize<'de> for JoinResponse { Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) ; } + GeneratedField::EnabledPublishCodecs => { + if enabled_publish_codecs__.is_some() { + return Err(serde::de::Error::duplicate_field("enabledPublishCodecs")); + } + enabled_publish_codecs__ = Some(map_.next_value()?); + } + GeneratedField::FastPublish => { + if fast_publish__.is_some() { + return Err(serde::de::Error::duplicate_field("fastPublish")); + } + fast_publish__ = Some(map_.next_value()?); + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -11294,6 +12245,8 @@ impl<'de> serde::Deserialize<'de> for JoinResponse { ping_interval: ping_interval__.unwrap_or_default(), server_info: server_info__, sif_trailer: sif_trailer__.unwrap_or_default(), + enabled_publish_codecs: enabled_publish_codecs__.unwrap_or_default(), + fast_publish: fast_publish__.unwrap_or_default(), }) } } @@ -13217,7 +14170,126 @@ impl<'de> serde::Deserialize<'de> for ListSipTrunkResponse { deserializer.deserialize_struct("livekit.ListSIPTrunkResponse", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for MigrateJobRequest { +impl serde::Serialize for MetricLabel { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + let variant = match self { + Self::AgentsLlmTtft => "AGENTS_LLM_TTFT", + Self::AgentsSttTtft => "AGENTS_STT_TTFT", + Self::AgentsTtsTtfb => "AGENTS_TTS_TTFB", + Self::ClientVideoSubscriberFreezeCount => "CLIENT_VIDEO_SUBSCRIBER_FREEZE_COUNT", + Self::ClientVideoSubscriberTotalFreezeDuration => "CLIENT_VIDEO_SUBSCRIBER_TOTAL_FREEZE_DURATION", + Self::ClientVideoSubscriberPauseCount => "CLIENT_VIDEO_SUBSCRIBER_PAUSE_COUNT", + Self::ClientVideoSubscriberTotalPausesDuration => "CLIENT_VIDEO_SUBSCRIBER_TOTAL_PAUSES_DURATION", + Self::ClientAudioSubscriberConcealedSamples => "CLIENT_AUDIO_SUBSCRIBER_CONCEALED_SAMPLES", + Self::ClientAudioSubscriberSilentConcealedSamples => "CLIENT_AUDIO_SUBSCRIBER_SILENT_CONCEALED_SAMPLES", + Self::ClientAudioSubscriberConcealmentEvents => "CLIENT_AUDIO_SUBSCRIBER_CONCEALMENT_EVENTS", + Self::ClientAudioSubscriberInterruptionCount => "CLIENT_AUDIO_SUBSCRIBER_INTERRUPTION_COUNT", + Self::ClientAudioSubscriberTotalInterruptionDuration => "CLIENT_AUDIO_SUBSCRIBER_TOTAL_INTERRUPTION_DURATION", + Self::ClientSubscriberJitterBufferDelay => "CLIENT_SUBSCRIBER_JITTER_BUFFER_DELAY", + Self::ClientSubscriberJitterBufferEmittedCount => "CLIENT_SUBSCRIBER_JITTER_BUFFER_EMITTED_COUNT", + Self::ClientVideoPublisherQualityLimitationDurationBandwidth => "CLIENT_VIDEO_PUBLISHER_QUALITY_LIMITATION_DURATION_BANDWIDTH", + Self::ClientVideoPublisherQualityLimitationDurationCpu => "CLIENT_VIDEO_PUBLISHER_QUALITY_LIMITATION_DURATION_CPU", + Self::ClientVideoPublisherQualityLimitationDurationOther => "CLIENT_VIDEO_PUBLISHER_QUALITY_LIMITATION_DURATION_OTHER", + Self::PredefinedMaxValue => "METRIC_LABEL_PREDEFINED_MAX_VALUE", + }; + serializer.serialize_str(variant) + } +} +impl<'de> serde::Deserialize<'de> for MetricLabel { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "AGENTS_LLM_TTFT", + "AGENTS_STT_TTFT", + "AGENTS_TTS_TTFB", + "CLIENT_VIDEO_SUBSCRIBER_FREEZE_COUNT", + "CLIENT_VIDEO_SUBSCRIBER_TOTAL_FREEZE_DURATION", + "CLIENT_VIDEO_SUBSCRIBER_PAUSE_COUNT", + "CLIENT_VIDEO_SUBSCRIBER_TOTAL_PAUSES_DURATION", + "CLIENT_AUDIO_SUBSCRIBER_CONCEALED_SAMPLES", + "CLIENT_AUDIO_SUBSCRIBER_SILENT_CONCEALED_SAMPLES", + "CLIENT_AUDIO_SUBSCRIBER_CONCEALMENT_EVENTS", + "CLIENT_AUDIO_SUBSCRIBER_INTERRUPTION_COUNT", + "CLIENT_AUDIO_SUBSCRIBER_TOTAL_INTERRUPTION_DURATION", + "CLIENT_SUBSCRIBER_JITTER_BUFFER_DELAY", + "CLIENT_SUBSCRIBER_JITTER_BUFFER_EMITTED_COUNT", + "CLIENT_VIDEO_PUBLISHER_QUALITY_LIMITATION_DURATION_BANDWIDTH", + "CLIENT_VIDEO_PUBLISHER_QUALITY_LIMITATION_DURATION_CPU", + "CLIENT_VIDEO_PUBLISHER_QUALITY_LIMITATION_DURATION_OTHER", + "METRIC_LABEL_PREDEFINED_MAX_VALUE", + ]; + + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = MetricLabel; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + fn visit_i64(self, v: i64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self) + }) + } + + fn visit_u64(self, v: u64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(v), &self) + }) + } + + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "AGENTS_LLM_TTFT" => Ok(MetricLabel::AgentsLlmTtft), + "AGENTS_STT_TTFT" => Ok(MetricLabel::AgentsSttTtft), + "AGENTS_TTS_TTFB" => Ok(MetricLabel::AgentsTtsTtfb), + "CLIENT_VIDEO_SUBSCRIBER_FREEZE_COUNT" => Ok(MetricLabel::ClientVideoSubscriberFreezeCount), + "CLIENT_VIDEO_SUBSCRIBER_TOTAL_FREEZE_DURATION" => Ok(MetricLabel::ClientVideoSubscriberTotalFreezeDuration), + "CLIENT_VIDEO_SUBSCRIBER_PAUSE_COUNT" => Ok(MetricLabel::ClientVideoSubscriberPauseCount), + "CLIENT_VIDEO_SUBSCRIBER_TOTAL_PAUSES_DURATION" => Ok(MetricLabel::ClientVideoSubscriberTotalPausesDuration), + "CLIENT_AUDIO_SUBSCRIBER_CONCEALED_SAMPLES" => Ok(MetricLabel::ClientAudioSubscriberConcealedSamples), + "CLIENT_AUDIO_SUBSCRIBER_SILENT_CONCEALED_SAMPLES" => Ok(MetricLabel::ClientAudioSubscriberSilentConcealedSamples), + "CLIENT_AUDIO_SUBSCRIBER_CONCEALMENT_EVENTS" => Ok(MetricLabel::ClientAudioSubscriberConcealmentEvents), + "CLIENT_AUDIO_SUBSCRIBER_INTERRUPTION_COUNT" => Ok(MetricLabel::ClientAudioSubscriberInterruptionCount), + "CLIENT_AUDIO_SUBSCRIBER_TOTAL_INTERRUPTION_DURATION" => Ok(MetricLabel::ClientAudioSubscriberTotalInterruptionDuration), + "CLIENT_SUBSCRIBER_JITTER_BUFFER_DELAY" => Ok(MetricLabel::ClientSubscriberJitterBufferDelay), + "CLIENT_SUBSCRIBER_JITTER_BUFFER_EMITTED_COUNT" => Ok(MetricLabel::ClientSubscriberJitterBufferEmittedCount), + "CLIENT_VIDEO_PUBLISHER_QUALITY_LIMITATION_DURATION_BANDWIDTH" => Ok(MetricLabel::ClientVideoPublisherQualityLimitationDurationBandwidth), + "CLIENT_VIDEO_PUBLISHER_QUALITY_LIMITATION_DURATION_CPU" => Ok(MetricLabel::ClientVideoPublisherQualityLimitationDurationCpu), + "CLIENT_VIDEO_PUBLISHER_QUALITY_LIMITATION_DURATION_OTHER" => Ok(MetricLabel::ClientVideoPublisherQualityLimitationDurationOther), + "METRIC_LABEL_PREDEFINED_MAX_VALUE" => Ok(MetricLabel::PredefinedMaxValue), + _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), + } + } + } + deserializer.deserialize_any(GeneratedVisitor) + } +} +impl serde::Serialize for MetricSample { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -13225,25 +14297,333 @@ impl serde::Serialize for MigrateJobRequest { { use serde::ser::SerializeStruct; let mut len = 0; - if !self.job_ids.is_empty() { + if self.timestamp_ms != 0 { len += 1; } - let mut struct_ser = serializer.serialize_struct("livekit.MigrateJobRequest", len)?; - if !self.job_ids.is_empty() { - struct_ser.serialize_field("jobIds", &self.job_ids)?; + if self.normalized_timestamp.is_some() { + len += 1; + } + if self.value != 0. { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.MetricSample", len)?; + if self.timestamp_ms != 0 { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("timestampMs", ToString::to_string(&self.timestamp_ms).as_str())?; + } + if let Some(v) = self.normalized_timestamp.as_ref() { + struct_ser.serialize_field("normalizedTimestamp", v)?; + } + if self.value != 0. { + struct_ser.serialize_field("value", &self.value)?; } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for MigrateJobRequest { +impl<'de> serde::Deserialize<'de> for MetricSample { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "job_ids", - "jobIds", + "timestamp_ms", + "timestampMs", + "normalized_timestamp", + "normalizedTimestamp", + "value", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + TimestampMs, + NormalizedTimestamp, + Value, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "timestampMs" | "timestamp_ms" => Ok(GeneratedField::TimestampMs), + "normalizedTimestamp" | "normalized_timestamp" => Ok(GeneratedField::NormalizedTimestamp), + "value" => Ok(GeneratedField::Value), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = MetricSample; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.MetricSample") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut timestamp_ms__ = None; + let mut normalized_timestamp__ = None; + let mut value__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::TimestampMs => { + if timestamp_ms__.is_some() { + return Err(serde::de::Error::duplicate_field("timestampMs")); + } + timestamp_ms__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::NormalizedTimestamp => { + if normalized_timestamp__.is_some() { + return Err(serde::de::Error::duplicate_field("normalizedTimestamp")); + } + normalized_timestamp__ = map_.next_value()?; + } + GeneratedField::Value => { + if value__.is_some() { + return Err(serde::de::Error::duplicate_field("value")); + } + value__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(MetricSample { + timestamp_ms: timestamp_ms__.unwrap_or_default(), + normalized_timestamp: normalized_timestamp__, + value: value__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.MetricSample", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for MetricsBatch { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.timestamp_ms != 0 { + len += 1; + } + if self.normalized_timestamp.is_some() { + len += 1; + } + if !self.str_data.is_empty() { + len += 1; + } + if !self.time_series.is_empty() { + len += 1; + } + if !self.events.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.MetricsBatch", len)?; + if self.timestamp_ms != 0 { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("timestampMs", ToString::to_string(&self.timestamp_ms).as_str())?; + } + if let Some(v) = self.normalized_timestamp.as_ref() { + struct_ser.serialize_field("normalizedTimestamp", v)?; + } + if !self.str_data.is_empty() { + struct_ser.serialize_field("strData", &self.str_data)?; + } + if !self.time_series.is_empty() { + struct_ser.serialize_field("timeSeries", &self.time_series)?; + } + if !self.events.is_empty() { + struct_ser.serialize_field("events", &self.events)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for MetricsBatch { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "timestamp_ms", + "timestampMs", + "normalized_timestamp", + "normalizedTimestamp", + "str_data", + "strData", + "time_series", + "timeSeries", + "events", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + TimestampMs, + NormalizedTimestamp, + StrData, + TimeSeries, + Events, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "timestampMs" | "timestamp_ms" => Ok(GeneratedField::TimestampMs), + "normalizedTimestamp" | "normalized_timestamp" => Ok(GeneratedField::NormalizedTimestamp), + "strData" | "str_data" => Ok(GeneratedField::StrData), + "timeSeries" | "time_series" => Ok(GeneratedField::TimeSeries), + "events" => Ok(GeneratedField::Events), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = MetricsBatch; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.MetricsBatch") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut timestamp_ms__ = None; + let mut normalized_timestamp__ = None; + let mut str_data__ = None; + let mut time_series__ = None; + let mut events__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::TimestampMs => { + if timestamp_ms__.is_some() { + return Err(serde::de::Error::duplicate_field("timestampMs")); + } + timestamp_ms__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::NormalizedTimestamp => { + if normalized_timestamp__.is_some() { + return Err(serde::de::Error::duplicate_field("normalizedTimestamp")); + } + normalized_timestamp__ = map_.next_value()?; + } + GeneratedField::StrData => { + if str_data__.is_some() { + return Err(serde::de::Error::duplicate_field("strData")); + } + str_data__ = Some(map_.next_value()?); + } + GeneratedField::TimeSeries => { + if time_series__.is_some() { + return Err(serde::de::Error::duplicate_field("timeSeries")); + } + time_series__ = Some(map_.next_value()?); + } + GeneratedField::Events => { + if events__.is_some() { + return Err(serde::de::Error::duplicate_field("events")); + } + events__ = Some(map_.next_value()?); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(MetricsBatch { + timestamp_ms: timestamp_ms__.unwrap_or_default(), + normalized_timestamp: normalized_timestamp__, + str_data: str_data__.unwrap_or_default(), + time_series: time_series__.unwrap_or_default(), + events: events__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.MetricsBatch", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for MigrateJobRequest { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.job_ids.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.MigrateJobRequest", len)?; + if !self.job_ids.is_empty() { + struct_ser.serialize_field("jobIds", &self.job_ids)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for MigrateJobRequest { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "job_ids", + "jobIds", ]; #[allow(clippy::enum_variant_names)] @@ -14428,6 +15808,9 @@ impl serde::Serialize for ParticipantPermission { if self.agent { len += 1; } + if self.can_subscribe_metrics { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.ParticipantPermission", len)?; if self.can_subscribe { struct_ser.serialize_field("canSubscribe", &self.can_subscribe)?; @@ -14457,6 +15840,9 @@ impl serde::Serialize for ParticipantPermission { if self.agent { struct_ser.serialize_field("agent", &self.agent)?; } + if self.can_subscribe_metrics { + struct_ser.serialize_field("canSubscribeMetrics", &self.can_subscribe_metrics)?; + } struct_ser.end() } } @@ -14480,6 +15866,8 @@ impl<'de> serde::Deserialize<'de> for ParticipantPermission { "can_update_metadata", "canUpdateMetadata", "agent", + "can_subscribe_metrics", + "canSubscribeMetrics", ]; #[allow(clippy::enum_variant_names)] @@ -14492,6 +15880,7 @@ impl<'de> serde::Deserialize<'de> for ParticipantPermission { Recorder, CanUpdateMetadata, Agent, + CanSubscribeMetrics, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -14522,6 +15911,7 @@ impl<'de> serde::Deserialize<'de> for ParticipantPermission { "recorder" => Ok(GeneratedField::Recorder), "canUpdateMetadata" | "can_update_metadata" => Ok(GeneratedField::CanUpdateMetadata), "agent" => Ok(GeneratedField::Agent), + "canSubscribeMetrics" | "can_subscribe_metrics" => Ok(GeneratedField::CanSubscribeMetrics), _ => Ok(GeneratedField::__SkipField__), } } @@ -14549,6 +15939,7 @@ impl<'de> serde::Deserialize<'de> for ParticipantPermission { let mut recorder__ = None; let mut can_update_metadata__ = None; let mut agent__ = None; + let mut can_subscribe_metrics__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::CanSubscribe => { @@ -14599,6 +15990,12 @@ impl<'de> serde::Deserialize<'de> for ParticipantPermission { } agent__ = Some(map_.next_value()?); } + GeneratedField::CanSubscribeMetrics => { + if can_subscribe_metrics__.is_some() { + return Err(serde::de::Error::duplicate_field("canSubscribeMetrics")); + } + can_subscribe_metrics__ = Some(map_.next_value()?); + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -14613,6 +16010,7 @@ impl<'de> serde::Deserialize<'de> for ParticipantPermission { recorder: recorder__.unwrap_or_default(), can_update_metadata: can_update_metadata__.unwrap_or_default(), agent: agent__.unwrap_or_default(), + can_subscribe_metrics: can_subscribe_metrics__.unwrap_or_default(), }) } } @@ -15331,7 +16729,7 @@ impl<'de> serde::Deserialize<'de> for ProxyConfig { deserializer.deserialize_struct("livekit.ProxyConfig", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for RtpDrift { +impl serde::Serialize for RtcpSenderReportState { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -15339,96 +16737,321 @@ impl serde::Serialize for RtpDrift { { use serde::ser::SerializeStruct; let mut len = 0; - if self.start_time.is_some() { - len += 1; - } - if self.end_time.is_some() { - len += 1; - } - if self.duration != 0. { + if self.rtp_timestamp != 0 { len += 1; } - if self.start_timestamp != 0 { + if self.rtp_timestamp_ext != 0 { len += 1; } - if self.end_timestamp != 0 { + if self.ntp_timestamp != 0 { len += 1; } - if self.rtp_clock_ticks != 0 { + if self.at != 0 { len += 1; } - if self.drift_samples != 0 { + if self.at_adjusted != 0 { len += 1; } - if self.drift_ms != 0. { + if self.packets != 0 { len += 1; } - if self.clock_rate != 0. { + if self.octets != 0 { len += 1; } - let mut struct_ser = serializer.serialize_struct("livekit.RTPDrift", len)?; - if let Some(v) = self.start_time.as_ref() { - struct_ser.serialize_field("startTime", v)?; - } - if let Some(v) = self.end_time.as_ref() { - struct_ser.serialize_field("endTime", v)?; - } - if self.duration != 0. { - struct_ser.serialize_field("duration", &self.duration)?; + let mut struct_ser = serializer.serialize_struct("livekit.RTCPSenderReportState", len)?; + if self.rtp_timestamp != 0 { + struct_ser.serialize_field("rtpTimestamp", &self.rtp_timestamp)?; } - if self.start_timestamp != 0 { + if self.rtp_timestamp_ext != 0 { #[allow(clippy::needless_borrow)] #[allow(clippy::needless_borrows_for_generic_args)] - struct_ser.serialize_field("startTimestamp", ToString::to_string(&self.start_timestamp).as_str())?; + struct_ser.serialize_field("rtpTimestampExt", ToString::to_string(&self.rtp_timestamp_ext).as_str())?; } - if self.end_timestamp != 0 { + if self.ntp_timestamp != 0 { #[allow(clippy::needless_borrow)] #[allow(clippy::needless_borrows_for_generic_args)] - struct_ser.serialize_field("endTimestamp", ToString::to_string(&self.end_timestamp).as_str())?; + struct_ser.serialize_field("ntpTimestamp", ToString::to_string(&self.ntp_timestamp).as_str())?; } - if self.rtp_clock_ticks != 0 { + if self.at != 0 { #[allow(clippy::needless_borrow)] #[allow(clippy::needless_borrows_for_generic_args)] - struct_ser.serialize_field("rtpClockTicks", ToString::to_string(&self.rtp_clock_ticks).as_str())?; + struct_ser.serialize_field("at", ToString::to_string(&self.at).as_str())?; } - if self.drift_samples != 0 { + if self.at_adjusted != 0 { #[allow(clippy::needless_borrow)] #[allow(clippy::needless_borrows_for_generic_args)] - struct_ser.serialize_field("driftSamples", ToString::to_string(&self.drift_samples).as_str())?; + struct_ser.serialize_field("atAdjusted", ToString::to_string(&self.at_adjusted).as_str())?; } - if self.drift_ms != 0. { - struct_ser.serialize_field("driftMs", &self.drift_ms)?; + if self.packets != 0 { + struct_ser.serialize_field("packets", &self.packets)?; } - if self.clock_rate != 0. { - struct_ser.serialize_field("clockRate", &self.clock_rate)?; + if self.octets != 0 { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("octets", ToString::to_string(&self.octets).as_str())?; } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for RtpDrift { +impl<'de> serde::Deserialize<'de> for RtcpSenderReportState { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "start_time", - "startTime", - "end_time", - "endTime", - "duration", - "start_timestamp", - "startTimestamp", - "end_timestamp", - "endTimestamp", - "rtp_clock_ticks", - "rtpClockTicks", - "drift_samples", - "driftSamples", - "drift_ms", - "driftMs", - "clock_rate", - "clockRate", + "rtp_timestamp", + "rtpTimestamp", + "rtp_timestamp_ext", + "rtpTimestampExt", + "ntp_timestamp", + "ntpTimestamp", + "at", + "at_adjusted", + "atAdjusted", + "packets", + "octets", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + RtpTimestamp, + RtpTimestampExt, + NtpTimestamp, + At, + AtAdjusted, + Packets, + Octets, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "rtpTimestamp" | "rtp_timestamp" => Ok(GeneratedField::RtpTimestamp), + "rtpTimestampExt" | "rtp_timestamp_ext" => Ok(GeneratedField::RtpTimestampExt), + "ntpTimestamp" | "ntp_timestamp" => Ok(GeneratedField::NtpTimestamp), + "at" => Ok(GeneratedField::At), + "atAdjusted" | "at_adjusted" => Ok(GeneratedField::AtAdjusted), + "packets" => Ok(GeneratedField::Packets), + "octets" => Ok(GeneratedField::Octets), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = RtcpSenderReportState; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.RTCPSenderReportState") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut rtp_timestamp__ = None; + let mut rtp_timestamp_ext__ = None; + let mut ntp_timestamp__ = None; + let mut at__ = None; + let mut at_adjusted__ = None; + let mut packets__ = None; + let mut octets__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::RtpTimestamp => { + if rtp_timestamp__.is_some() { + return Err(serde::de::Error::duplicate_field("rtpTimestamp")); + } + rtp_timestamp__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::RtpTimestampExt => { + if rtp_timestamp_ext__.is_some() { + return Err(serde::de::Error::duplicate_field("rtpTimestampExt")); + } + rtp_timestamp_ext__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::NtpTimestamp => { + if ntp_timestamp__.is_some() { + return Err(serde::de::Error::duplicate_field("ntpTimestamp")); + } + ntp_timestamp__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::At => { + if at__.is_some() { + return Err(serde::de::Error::duplicate_field("at")); + } + at__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::AtAdjusted => { + if at_adjusted__.is_some() { + return Err(serde::de::Error::duplicate_field("atAdjusted")); + } + at_adjusted__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::Packets => { + if packets__.is_some() { + return Err(serde::de::Error::duplicate_field("packets")); + } + packets__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::Octets => { + if octets__.is_some() { + return Err(serde::de::Error::duplicate_field("octets")); + } + octets__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(RtcpSenderReportState { + rtp_timestamp: rtp_timestamp__.unwrap_or_default(), + rtp_timestamp_ext: rtp_timestamp_ext__.unwrap_or_default(), + ntp_timestamp: ntp_timestamp__.unwrap_or_default(), + at: at__.unwrap_or_default(), + at_adjusted: at_adjusted__.unwrap_or_default(), + packets: packets__.unwrap_or_default(), + octets: octets__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.RTCPSenderReportState", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for RtpDrift { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.start_time.is_some() { + len += 1; + } + if self.end_time.is_some() { + len += 1; + } + if self.duration != 0. { + len += 1; + } + if self.start_timestamp != 0 { + len += 1; + } + if self.end_timestamp != 0 { + len += 1; + } + if self.rtp_clock_ticks != 0 { + len += 1; + } + if self.drift_samples != 0 { + len += 1; + } + if self.drift_ms != 0. { + len += 1; + } + if self.clock_rate != 0. { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.RTPDrift", len)?; + if let Some(v) = self.start_time.as_ref() { + struct_ser.serialize_field("startTime", v)?; + } + if let Some(v) = self.end_time.as_ref() { + struct_ser.serialize_field("endTime", v)?; + } + if self.duration != 0. { + struct_ser.serialize_field("duration", &self.duration)?; + } + if self.start_timestamp != 0 { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("startTimestamp", ToString::to_string(&self.start_timestamp).as_str())?; + } + if self.end_timestamp != 0 { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("endTimestamp", ToString::to_string(&self.end_timestamp).as_str())?; + } + if self.rtp_clock_ticks != 0 { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("rtpClockTicks", ToString::to_string(&self.rtp_clock_ticks).as_str())?; + } + if self.drift_samples != 0 { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("driftSamples", ToString::to_string(&self.drift_samples).as_str())?; + } + if self.drift_ms != 0. { + struct_ser.serialize_field("driftMs", &self.drift_ms)?; + } + if self.clock_rate != 0. { + struct_ser.serialize_field("clockRate", &self.clock_rate)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for RtpDrift { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "start_time", + "startTime", + "end_time", + "endTime", + "duration", + "start_timestamp", + "startTimestamp", + "end_timestamp", + "endTimestamp", + "rtp_clock_ticks", + "rtpClockTicks", + "drift_samples", + "driftSamples", + "drift_ms", + "driftMs", + "clock_rate", + "clockRate", ]; #[allow(clippy::enum_variant_names)] @@ -15618,6 +17241,9 @@ impl serde::Serialize for RtpForwarderState { if self.rtp_munger.is_some() { len += 1; } + if !self.sender_report_state.is_empty() { + len += 1; + } if self.codec_munger.is_some() { len += 1; } @@ -15646,6 +17272,9 @@ impl serde::Serialize for RtpForwarderState { if let Some(v) = self.rtp_munger.as_ref() { struct_ser.serialize_field("rtpMunger", v)?; } + if !self.sender_report_state.is_empty() { + struct_ser.serialize_field("senderReportState", &self.sender_report_state)?; + } if let Some(v) = self.codec_munger.as_ref() { match v { rtp_forwarder_state::CodecMunger::Vp8Munger(v) => { @@ -15674,6 +17303,8 @@ impl<'de> serde::Deserialize<'de> for RtpForwarderState { "dummyStartTimestampOffset", "rtp_munger", "rtpMunger", + "sender_report_state", + "senderReportState", "vp8_munger", "vp8Munger", ]; @@ -15686,6 +17317,7 @@ impl<'de> serde::Deserialize<'de> for RtpForwarderState { ExtFirstTimestamp, DummyStartTimestampOffset, RtpMunger, + SenderReportState, Vp8Munger, __SkipField__, } @@ -15715,6 +17347,7 @@ impl<'de> serde::Deserialize<'de> for RtpForwarderState { "extFirstTimestamp" | "ext_first_timestamp" => Ok(GeneratedField::ExtFirstTimestamp), "dummyStartTimestampOffset" | "dummy_start_timestamp_offset" => Ok(GeneratedField::DummyStartTimestampOffset), "rtpMunger" | "rtp_munger" => Ok(GeneratedField::RtpMunger), + "senderReportState" | "sender_report_state" => Ok(GeneratedField::SenderReportState), "vp8Munger" | "vp8_munger" => Ok(GeneratedField::Vp8Munger), _ => Ok(GeneratedField::__SkipField__), } @@ -15741,6 +17374,7 @@ impl<'de> serde::Deserialize<'de> for RtpForwarderState { let mut ext_first_timestamp__ = None; let mut dummy_start_timestamp_offset__ = None; let mut rtp_munger__ = None; + let mut sender_report_state__ = None; let mut codec_munger__ = None; while let Some(k) = map_.next_key()? { match k { @@ -15788,6 +17422,12 @@ impl<'de> serde::Deserialize<'de> for RtpForwarderState { } rtp_munger__ = map_.next_value()?; } + GeneratedField::SenderReportState => { + if sender_report_state__.is_some() { + return Err(serde::de::Error::duplicate_field("senderReportState")); + } + sender_report_state__ = Some(map_.next_value()?); + } GeneratedField::Vp8Munger => { if codec_munger__.is_some() { return Err(serde::de::Error::duplicate_field("vp8Munger")); @@ -15807,6 +17447,7 @@ impl<'de> serde::Deserialize<'de> for RtpForwarderState { ext_first_timestamp: ext_first_timestamp__.unwrap_or_default(), dummy_start_timestamp_offset: dummy_start_timestamp_offset__.unwrap_or_default(), rtp_munger: rtp_munger__, + sender_report_state: sender_report_state__.unwrap_or_default(), codec_munger: codec_munger__, }) } @@ -16150,12 +17791,15 @@ impl serde::Serialize for RtpStats { if self.packet_drift.is_some() { len += 1; } - if self.report_drift.is_some() { + if self.ntp_report_drift.is_some() { len += 1; } if self.rebased_report_drift.is_some() { len += 1; } + if self.received_report_drift.is_some() { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.RTPStats", len)?; if let Some(v) = self.start_time.as_ref() { struct_ser.serialize_field("startTime", v)?; @@ -16295,12 +17939,15 @@ impl serde::Serialize for RtpStats { if let Some(v) = self.packet_drift.as_ref() { struct_ser.serialize_field("packetDrift", v)?; } - if let Some(v) = self.report_drift.as_ref() { - struct_ser.serialize_field("reportDrift", v)?; + if let Some(v) = self.ntp_report_drift.as_ref() { + struct_ser.serialize_field("ntpReportDrift", v)?; } if let Some(v) = self.rebased_report_drift.as_ref() { struct_ser.serialize_field("rebasedReportDrift", v)?; } + if let Some(v) = self.received_report_drift.as_ref() { + struct_ser.serialize_field("receivedReportDrift", v)?; + } struct_ser.end() } } @@ -16387,10 +18034,12 @@ impl<'de> serde::Deserialize<'de> for RtpStats { "lastLayerLockPli", "packet_drift", "packetDrift", - "report_drift", - "reportDrift", + "ntp_report_drift", + "ntpReportDrift", "rebased_report_drift", "rebasedReportDrift", + "received_report_drift", + "receivedReportDrift", ]; #[allow(clippy::enum_variant_names)] @@ -16437,8 +18086,9 @@ impl<'de> serde::Deserialize<'de> for RtpStats { LayerLockPlis, LastLayerLockPli, PacketDrift, - ReportDrift, + NtpReportDrift, RebasedReportDrift, + ReceivedReportDrift, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -16503,8 +18153,9 @@ impl<'de> serde::Deserialize<'de> for RtpStats { "layerLockPlis" | "layer_lock_plis" => Ok(GeneratedField::LayerLockPlis), "lastLayerLockPli" | "last_layer_lock_pli" => Ok(GeneratedField::LastLayerLockPli), "packetDrift" | "packet_drift" => Ok(GeneratedField::PacketDrift), - "reportDrift" | "report_drift" => Ok(GeneratedField::ReportDrift), + "ntpReportDrift" | "ntp_report_drift" => Ok(GeneratedField::NtpReportDrift), "rebasedReportDrift" | "rebased_report_drift" => Ok(GeneratedField::RebasedReportDrift), + "receivedReportDrift" | "received_report_drift" => Ok(GeneratedField::ReceivedReportDrift), _ => Ok(GeneratedField::__SkipField__), } } @@ -16566,8 +18217,9 @@ impl<'de> serde::Deserialize<'de> for RtpStats { let mut layer_lock_plis__ = None; let mut last_layer_lock_pli__ = None; let mut packet_drift__ = None; - let mut report_drift__ = None; + let mut ntp_report_drift__ = None; let mut rebased_report_drift__ = None; + let mut received_report_drift__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::StartTime => { @@ -16891,157 +18543,693 @@ impl<'de> serde::Deserialize<'de> for RtpStats { if packet_drift__.is_some() { return Err(serde::de::Error::duplicate_field("packetDrift")); } - packet_drift__ = map_.next_value()?; + packet_drift__ = map_.next_value()?; + } + GeneratedField::NtpReportDrift => { + if ntp_report_drift__.is_some() { + return Err(serde::de::Error::duplicate_field("ntpReportDrift")); + } + ntp_report_drift__ = map_.next_value()?; + } + GeneratedField::RebasedReportDrift => { + if rebased_report_drift__.is_some() { + return Err(serde::de::Error::duplicate_field("rebasedReportDrift")); + } + rebased_report_drift__ = map_.next_value()?; + } + GeneratedField::ReceivedReportDrift => { + if received_report_drift__.is_some() { + return Err(serde::de::Error::duplicate_field("receivedReportDrift")); + } + received_report_drift__ = map_.next_value()?; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(RtpStats { + start_time: start_time__, + end_time: end_time__, + duration: duration__.unwrap_or_default(), + packets: packets__.unwrap_or_default(), + packet_rate: packet_rate__.unwrap_or_default(), + bytes: bytes__.unwrap_or_default(), + header_bytes: header_bytes__.unwrap_or_default(), + bitrate: bitrate__.unwrap_or_default(), + packets_lost: packets_lost__.unwrap_or_default(), + packet_loss_rate: packet_loss_rate__.unwrap_or_default(), + packet_loss_percentage: packet_loss_percentage__.unwrap_or_default(), + packets_duplicate: packets_duplicate__.unwrap_or_default(), + packet_duplicate_rate: packet_duplicate_rate__.unwrap_or_default(), + bytes_duplicate: bytes_duplicate__.unwrap_or_default(), + header_bytes_duplicate: header_bytes_duplicate__.unwrap_or_default(), + bitrate_duplicate: bitrate_duplicate__.unwrap_or_default(), + packets_padding: packets_padding__.unwrap_or_default(), + packet_padding_rate: packet_padding_rate__.unwrap_or_default(), + bytes_padding: bytes_padding__.unwrap_or_default(), + header_bytes_padding: header_bytes_padding__.unwrap_or_default(), + bitrate_padding: bitrate_padding__.unwrap_or_default(), + packets_out_of_order: packets_out_of_order__.unwrap_or_default(), + frames: frames__.unwrap_or_default(), + frame_rate: frame_rate__.unwrap_or_default(), + jitter_current: jitter_current__.unwrap_or_default(), + jitter_max: jitter_max__.unwrap_or_default(), + gap_histogram: gap_histogram__.unwrap_or_default(), + nacks: nacks__.unwrap_or_default(), + nack_acks: nack_acks__.unwrap_or_default(), + nack_misses: nack_misses__.unwrap_or_default(), + nack_repeated: nack_repeated__.unwrap_or_default(), + plis: plis__.unwrap_or_default(), + last_pli: last_pli__, + firs: firs__.unwrap_or_default(), + last_fir: last_fir__, + rtt_current: rtt_current__.unwrap_or_default(), + rtt_max: rtt_max__.unwrap_or_default(), + key_frames: key_frames__.unwrap_or_default(), + last_key_frame: last_key_frame__, + layer_lock_plis: layer_lock_plis__.unwrap_or_default(), + last_layer_lock_pli: last_layer_lock_pli__, + packet_drift: packet_drift__, + ntp_report_drift: ntp_report_drift__, + rebased_report_drift: rebased_report_drift__, + received_report_drift: received_report_drift__, + }) + } + } + deserializer.deserialize_struct("livekit.RTPStats", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for ReconnectReason { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + let variant = match self { + Self::RrUnknown => "RR_UNKNOWN", + Self::RrSignalDisconnected => "RR_SIGNAL_DISCONNECTED", + Self::RrPublisherFailed => "RR_PUBLISHER_FAILED", + Self::RrSubscriberFailed => "RR_SUBSCRIBER_FAILED", + Self::RrSwitchCandidate => "RR_SWITCH_CANDIDATE", + }; + serializer.serialize_str(variant) + } +} +impl<'de> serde::Deserialize<'de> for ReconnectReason { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "RR_UNKNOWN", + "RR_SIGNAL_DISCONNECTED", + "RR_PUBLISHER_FAILED", + "RR_SUBSCRIBER_FAILED", + "RR_SWITCH_CANDIDATE", + ]; + + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = ReconnectReason; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + fn visit_i64(self, v: i64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self) + }) + } + + fn visit_u64(self, v: u64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(v), &self) + }) + } + + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "RR_UNKNOWN" => Ok(ReconnectReason::RrUnknown), + "RR_SIGNAL_DISCONNECTED" => Ok(ReconnectReason::RrSignalDisconnected), + "RR_PUBLISHER_FAILED" => Ok(ReconnectReason::RrPublisherFailed), + "RR_SUBSCRIBER_FAILED" => Ok(ReconnectReason::RrSubscriberFailed), + "RR_SWITCH_CANDIDATE" => Ok(ReconnectReason::RrSwitchCandidate), + _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), + } + } + } + deserializer.deserialize_any(GeneratedVisitor) + } +} +impl serde::Serialize for ReconnectResponse { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.ice_servers.is_empty() { + len += 1; + } + if self.client_configuration.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.ReconnectResponse", len)?; + if !self.ice_servers.is_empty() { + struct_ser.serialize_field("iceServers", &self.ice_servers)?; + } + if let Some(v) = self.client_configuration.as_ref() { + struct_ser.serialize_field("clientConfiguration", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for ReconnectResponse { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "ice_servers", + "iceServers", + "client_configuration", + "clientConfiguration", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + IceServers, + ClientConfiguration, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "iceServers" | "ice_servers" => Ok(GeneratedField::IceServers), + "clientConfiguration" | "client_configuration" => Ok(GeneratedField::ClientConfiguration), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = ReconnectResponse; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.ReconnectResponse") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut ice_servers__ = None; + let mut client_configuration__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::IceServers => { + if ice_servers__.is_some() { + return Err(serde::de::Error::duplicate_field("iceServers")); + } + ice_servers__ = Some(map_.next_value()?); + } + GeneratedField::ClientConfiguration => { + if client_configuration__.is_some() { + return Err(serde::de::Error::duplicate_field("clientConfiguration")); + } + client_configuration__ = map_.next_value()?; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(ReconnectResponse { + ice_servers: ice_servers__.unwrap_or_default(), + client_configuration: client_configuration__, + }) + } + } + deserializer.deserialize_struct("livekit.ReconnectResponse", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for RegionInfo { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.region.is_empty() { + len += 1; + } + if !self.url.is_empty() { + len += 1; + } + if self.distance != 0 { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.RegionInfo", len)?; + if !self.region.is_empty() { + struct_ser.serialize_field("region", &self.region)?; + } + if !self.url.is_empty() { + struct_ser.serialize_field("url", &self.url)?; + } + if self.distance != 0 { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("distance", ToString::to_string(&self.distance).as_str())?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for RegionInfo { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "region", + "url", + "distance", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Region, + Url, + Distance, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "region" => Ok(GeneratedField::Region), + "url" => Ok(GeneratedField::Url), + "distance" => Ok(GeneratedField::Distance), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = RegionInfo; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.RegionInfo") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut region__ = None; + let mut url__ = None; + let mut distance__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Region => { + if region__.is_some() { + return Err(serde::de::Error::duplicate_field("region")); + } + region__ = Some(map_.next_value()?); + } + GeneratedField::Url => { + if url__.is_some() { + return Err(serde::de::Error::duplicate_field("url")); + } + url__ = Some(map_.next_value()?); + } + GeneratedField::Distance => { + if distance__.is_some() { + return Err(serde::de::Error::duplicate_field("distance")); + } + distance__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(RegionInfo { + region: region__.unwrap_or_default(), + url: url__.unwrap_or_default(), + distance: distance__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.RegionInfo", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for RegionSettings { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.regions.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.RegionSettings", len)?; + if !self.regions.is_empty() { + struct_ser.serialize_field("regions", &self.regions)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for RegionSettings { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "regions", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Regions, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "regions" => Ok(GeneratedField::Regions), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = RegionSettings; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.RegionSettings") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut regions__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Regions => { + if regions__.is_some() { + return Err(serde::de::Error::duplicate_field("regions")); + } + regions__ = Some(map_.next_value()?); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(RegionSettings { + regions: regions__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.RegionSettings", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for RegisterWorkerRequest { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.r#type != 0 { + len += 1; + } + if !self.agent_name.is_empty() { + len += 1; + } + if !self.version.is_empty() { + len += 1; + } + if self.ping_interval != 0 { + len += 1; + } + if self.namespace.is_some() { + len += 1; + } + if self.allowed_permissions.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.RegisterWorkerRequest", len)?; + if self.r#type != 0 { + let v = JobType::try_from(self.r#type) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.r#type)))?; + struct_ser.serialize_field("type", &v)?; + } + if !self.agent_name.is_empty() { + struct_ser.serialize_field("agentName", &self.agent_name)?; + } + if !self.version.is_empty() { + struct_ser.serialize_field("version", &self.version)?; + } + if self.ping_interval != 0 { + struct_ser.serialize_field("pingInterval", &self.ping_interval)?; + } + if let Some(v) = self.namespace.as_ref() { + struct_ser.serialize_field("namespace", v)?; + } + if let Some(v) = self.allowed_permissions.as_ref() { + struct_ser.serialize_field("allowedPermissions", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for RegisterWorkerRequest { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "type", + "agent_name", + "agentName", + "version", + "ping_interval", + "pingInterval", + "namespace", + "allowed_permissions", + "allowedPermissions", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Type, + AgentName, + Version, + PingInterval, + Namespace, + AllowedPermissions, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "type" => Ok(GeneratedField::Type), + "agentName" | "agent_name" => Ok(GeneratedField::AgentName), + "version" => Ok(GeneratedField::Version), + "pingInterval" | "ping_interval" => Ok(GeneratedField::PingInterval), + "namespace" => Ok(GeneratedField::Namespace), + "allowedPermissions" | "allowed_permissions" => Ok(GeneratedField::AllowedPermissions), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = RegisterWorkerRequest; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.RegisterWorkerRequest") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut r#type__ = None; + let mut agent_name__ = None; + let mut version__ = None; + let mut ping_interval__ = None; + let mut namespace__ = None; + let mut allowed_permissions__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Type => { + if r#type__.is_some() { + return Err(serde::de::Error::duplicate_field("type")); + } + r#type__ = Some(map_.next_value::()? as i32); + } + GeneratedField::AgentName => { + if agent_name__.is_some() { + return Err(serde::de::Error::duplicate_field("agentName")); + } + agent_name__ = Some(map_.next_value()?); + } + GeneratedField::Version => { + if version__.is_some() { + return Err(serde::de::Error::duplicate_field("version")); + } + version__ = Some(map_.next_value()?); + } + GeneratedField::PingInterval => { + if ping_interval__.is_some() { + return Err(serde::de::Error::duplicate_field("pingInterval")); + } + ping_interval__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; } - GeneratedField::ReportDrift => { - if report_drift__.is_some() { - return Err(serde::de::Error::duplicate_field("reportDrift")); + GeneratedField::Namespace => { + if namespace__.is_some() { + return Err(serde::de::Error::duplicate_field("namespace")); } - report_drift__ = map_.next_value()?; + namespace__ = map_.next_value()?; } - GeneratedField::RebasedReportDrift => { - if rebased_report_drift__.is_some() { - return Err(serde::de::Error::duplicate_field("rebasedReportDrift")); + GeneratedField::AllowedPermissions => { + if allowed_permissions__.is_some() { + return Err(serde::de::Error::duplicate_field("allowedPermissions")); } - rebased_report_drift__ = map_.next_value()?; + allowed_permissions__ = map_.next_value()?; } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } } } - Ok(RtpStats { - start_time: start_time__, - end_time: end_time__, - duration: duration__.unwrap_or_default(), - packets: packets__.unwrap_or_default(), - packet_rate: packet_rate__.unwrap_or_default(), - bytes: bytes__.unwrap_or_default(), - header_bytes: header_bytes__.unwrap_or_default(), - bitrate: bitrate__.unwrap_or_default(), - packets_lost: packets_lost__.unwrap_or_default(), - packet_loss_rate: packet_loss_rate__.unwrap_or_default(), - packet_loss_percentage: packet_loss_percentage__.unwrap_or_default(), - packets_duplicate: packets_duplicate__.unwrap_or_default(), - packet_duplicate_rate: packet_duplicate_rate__.unwrap_or_default(), - bytes_duplicate: bytes_duplicate__.unwrap_or_default(), - header_bytes_duplicate: header_bytes_duplicate__.unwrap_or_default(), - bitrate_duplicate: bitrate_duplicate__.unwrap_or_default(), - packets_padding: packets_padding__.unwrap_or_default(), - packet_padding_rate: packet_padding_rate__.unwrap_or_default(), - bytes_padding: bytes_padding__.unwrap_or_default(), - header_bytes_padding: header_bytes_padding__.unwrap_or_default(), - bitrate_padding: bitrate_padding__.unwrap_or_default(), - packets_out_of_order: packets_out_of_order__.unwrap_or_default(), - frames: frames__.unwrap_or_default(), - frame_rate: frame_rate__.unwrap_or_default(), - jitter_current: jitter_current__.unwrap_or_default(), - jitter_max: jitter_max__.unwrap_or_default(), - gap_histogram: gap_histogram__.unwrap_or_default(), - nacks: nacks__.unwrap_or_default(), - nack_acks: nack_acks__.unwrap_or_default(), - nack_misses: nack_misses__.unwrap_or_default(), - nack_repeated: nack_repeated__.unwrap_or_default(), - plis: plis__.unwrap_or_default(), - last_pli: last_pli__, - firs: firs__.unwrap_or_default(), - last_fir: last_fir__, - rtt_current: rtt_current__.unwrap_or_default(), - rtt_max: rtt_max__.unwrap_or_default(), - key_frames: key_frames__.unwrap_or_default(), - last_key_frame: last_key_frame__, - layer_lock_plis: layer_lock_plis__.unwrap_or_default(), - last_layer_lock_pli: last_layer_lock_pli__, - packet_drift: packet_drift__, - report_drift: report_drift__, - rebased_report_drift: rebased_report_drift__, + Ok(RegisterWorkerRequest { + r#type: r#type__.unwrap_or_default(), + agent_name: agent_name__.unwrap_or_default(), + version: version__.unwrap_or_default(), + ping_interval: ping_interval__.unwrap_or_default(), + namespace: namespace__, + allowed_permissions: allowed_permissions__, }) } } - deserializer.deserialize_struct("livekit.RTPStats", FIELDS, GeneratedVisitor) - } -} -impl serde::Serialize for ReconnectReason { - #[allow(deprecated)] - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::Serializer, - { - let variant = match self { - Self::RrUnknown => "RR_UNKNOWN", - Self::RrSignalDisconnected => "RR_SIGNAL_DISCONNECTED", - Self::RrPublisherFailed => "RR_PUBLISHER_FAILED", - Self::RrSubscriberFailed => "RR_SUBSCRIBER_FAILED", - Self::RrSwitchCandidate => "RR_SWITCH_CANDIDATE", - }; - serializer.serialize_str(variant) - } -} -impl<'de> serde::Deserialize<'de> for ReconnectReason { - #[allow(deprecated)] - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - const FIELDS: &[&str] = &[ - "RR_UNKNOWN", - "RR_SIGNAL_DISCONNECTED", - "RR_PUBLISHER_FAILED", - "RR_SUBSCRIBER_FAILED", - "RR_SWITCH_CANDIDATE", - ]; - - struct GeneratedVisitor; - - impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = ReconnectReason; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(formatter, "expected one of: {:?}", &FIELDS) - } - - fn visit_i64(self, v: i64) -> std::result::Result - where - E: serde::de::Error, - { - i32::try_from(v) - .ok() - .and_then(|x| x.try_into().ok()) - .ok_or_else(|| { - serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self) - }) - } - - fn visit_u64(self, v: u64) -> std::result::Result - where - E: serde::de::Error, - { - i32::try_from(v) - .ok() - .and_then(|x| x.try_into().ok()) - .ok_or_else(|| { - serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(v), &self) - }) - } - - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - match value { - "RR_UNKNOWN" => Ok(ReconnectReason::RrUnknown), - "RR_SIGNAL_DISCONNECTED" => Ok(ReconnectReason::RrSignalDisconnected), - "RR_PUBLISHER_FAILED" => Ok(ReconnectReason::RrPublisherFailed), - "RR_SUBSCRIBER_FAILED" => Ok(ReconnectReason::RrSubscriberFailed), - "RR_SWITCH_CANDIDATE" => Ok(ReconnectReason::RrSwitchCandidate), - _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), - } - } - } - deserializer.deserialize_any(GeneratedVisitor) + deserializer.deserialize_struct("livekit.RegisterWorkerRequest", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for ReconnectResponse { +impl serde::Serialize for RegisterWorkerResponse { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -17049,39 +19237,39 @@ impl serde::Serialize for ReconnectResponse { { use serde::ser::SerializeStruct; let mut len = 0; - if !self.ice_servers.is_empty() { + if !self.worker_id.is_empty() { len += 1; } - if self.client_configuration.is_some() { + if self.server_info.is_some() { len += 1; } - let mut struct_ser = serializer.serialize_struct("livekit.ReconnectResponse", len)?; - if !self.ice_servers.is_empty() { - struct_ser.serialize_field("iceServers", &self.ice_servers)?; + let mut struct_ser = serializer.serialize_struct("livekit.RegisterWorkerResponse", len)?; + if !self.worker_id.is_empty() { + struct_ser.serialize_field("workerId", &self.worker_id)?; } - if let Some(v) = self.client_configuration.as_ref() { - struct_ser.serialize_field("clientConfiguration", v)?; + if let Some(v) = self.server_info.as_ref() { + struct_ser.serialize_field("serverInfo", v)?; } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for ReconnectResponse { +impl<'de> serde::Deserialize<'de> for RegisterWorkerResponse { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "ice_servers", - "iceServers", - "client_configuration", - "clientConfiguration", + "worker_id", + "workerId", + "server_info", + "serverInfo", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { - IceServers, - ClientConfiguration, + WorkerId, + ServerInfo, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -17104,8 +19292,8 @@ impl<'de> serde::Deserialize<'de> for ReconnectResponse { E: serde::de::Error, { match value { - "iceServers" | "ice_servers" => Ok(GeneratedField::IceServers), - "clientConfiguration" | "client_configuration" => Ok(GeneratedField::ClientConfiguration), + "workerId" | "worker_id" => Ok(GeneratedField::WorkerId), + "serverInfo" | "server_info" => Ok(GeneratedField::ServerInfo), _ => Ok(GeneratedField::__SkipField__), } } @@ -17115,95 +19303,69 @@ impl<'de> serde::Deserialize<'de> for ReconnectResponse { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = ReconnectResponse; + type Value = RegisterWorkerResponse; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.ReconnectResponse") + formatter.write_str("struct livekit.RegisterWorkerResponse") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut ice_servers__ = None; - let mut client_configuration__ = None; + let mut worker_id__ = None; + let mut server_info__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::IceServers => { - if ice_servers__.is_some() { - return Err(serde::de::Error::duplicate_field("iceServers")); + GeneratedField::WorkerId => { + if worker_id__.is_some() { + return Err(serde::de::Error::duplicate_field("workerId")); } - ice_servers__ = Some(map_.next_value()?); + worker_id__ = Some(map_.next_value()?); } - GeneratedField::ClientConfiguration => { - if client_configuration__.is_some() { - return Err(serde::de::Error::duplicate_field("clientConfiguration")); + GeneratedField::ServerInfo => { + if server_info__.is_some() { + return Err(serde::de::Error::duplicate_field("serverInfo")); } - client_configuration__ = map_.next_value()?; + server_info__ = map_.next_value()?; } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } } } - Ok(ReconnectResponse { - ice_servers: ice_servers__.unwrap_or_default(), - client_configuration: client_configuration__, + Ok(RegisterWorkerResponse { + worker_id: worker_id__.unwrap_or_default(), + server_info: server_info__, }) } } - deserializer.deserialize_struct("livekit.ReconnectResponse", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.RegisterWorkerResponse", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for RegionInfo { +impl serde::Serialize for RemoveParticipantResponse { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where S: serde::Serializer, { use serde::ser::SerializeStruct; - let mut len = 0; - if !self.region.is_empty() { - len += 1; - } - if !self.url.is_empty() { - len += 1; - } - if self.distance != 0 { - len += 1; - } - let mut struct_ser = serializer.serialize_struct("livekit.RegionInfo", len)?; - if !self.region.is_empty() { - struct_ser.serialize_field("region", &self.region)?; - } - if !self.url.is_empty() { - struct_ser.serialize_field("url", &self.url)?; - } - if self.distance != 0 { - #[allow(clippy::needless_borrow)] - #[allow(clippy::needless_borrows_for_generic_args)] - struct_ser.serialize_field("distance", ToString::to_string(&self.distance).as_str())?; - } + let len = 0; + let struct_ser = serializer.serialize_struct("livekit.RemoveParticipantResponse", len)?; struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for RegionInfo { +impl<'de> serde::Deserialize<'de> for RemoveParticipantResponse { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "region", - "url", - "distance", ]; #[allow(clippy::enum_variant_names)] - enum GeneratedField { - Region, - Url, - Distance, + enum GeneratedField { __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -17225,12 +19387,7 @@ impl<'de> serde::Deserialize<'de> for RegionInfo { where E: serde::de::Error, { - match value { - "region" => Ok(GeneratedField::Region), - "url" => Ok(GeneratedField::Url), - "distance" => Ok(GeneratedField::Distance), - _ => Ok(GeneratedField::__SkipField__), - } + Ok(GeneratedField::__SkipField__) } } deserializer.deserialize_identifier(GeneratedVisitor) @@ -17238,57 +19395,27 @@ impl<'de> serde::Deserialize<'de> for RegionInfo { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = RegionInfo; + type Value = RemoveParticipantResponse; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.RegionInfo") + formatter.write_str("struct livekit.RemoveParticipantResponse") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut region__ = None; - let mut url__ = None; - let mut distance__ = None; - while let Some(k) = map_.next_key()? { - match k { - GeneratedField::Region => { - if region__.is_some() { - return Err(serde::de::Error::duplicate_field("region")); - } - region__ = Some(map_.next_value()?); - } - GeneratedField::Url => { - if url__.is_some() { - return Err(serde::de::Error::duplicate_field("url")); - } - url__ = Some(map_.next_value()?); - } - GeneratedField::Distance => { - if distance__.is_some() { - return Err(serde::de::Error::duplicate_field("distance")); - } - distance__ = - Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; - } - GeneratedField::__SkipField__ => { - let _ = map_.next_value::()?; - } - } + while map_.next_key::()?.is_some() { + let _ = map_.next_value::()?; } - Ok(RegionInfo { - region: region__.unwrap_or_default(), - url: url__.unwrap_or_default(), - distance: distance__.unwrap_or_default(), + Ok(RemoveParticipantResponse { }) } } - deserializer.deserialize_struct("livekit.RegionInfo", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.RemoveParticipantResponse", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for RegionSettings { +impl serde::Serialize for RequestResponse { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -17296,29 +19423,48 @@ impl serde::Serialize for RegionSettings { { use serde::ser::SerializeStruct; let mut len = 0; - if !self.regions.is_empty() { + if self.request_id != 0 { len += 1; } - let mut struct_ser = serializer.serialize_struct("livekit.RegionSettings", len)?; - if !self.regions.is_empty() { - struct_ser.serialize_field("regions", &self.regions)?; + if self.reason != 0 { + len += 1; + } + if !self.message.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.RequestResponse", len)?; + if self.request_id != 0 { + struct_ser.serialize_field("requestId", &self.request_id)?; + } + if self.reason != 0 { + let v = request_response::Reason::try_from(self.reason) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.reason)))?; + struct_ser.serialize_field("reason", &v)?; + } + if !self.message.is_empty() { + struct_ser.serialize_field("message", &self.message)?; } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for RegionSettings { +impl<'de> serde::Deserialize<'de> for RequestResponse { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "regions", + "request_id", + "requestId", + "reason", + "message", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { - Regions, + RequestId, + Reason, + Message, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -17341,7 +19487,9 @@ impl<'de> serde::Deserialize<'de> for RegionSettings { E: serde::de::Error, { match value { - "regions" => Ok(GeneratedField::Regions), + "requestId" | "request_id" => Ok(GeneratedField::RequestId), + "reason" => Ok(GeneratedField::Reason), + "message" => Ok(GeneratedField::Message), _ => Ok(GeneratedField::__SkipField__), } } @@ -17351,39 +19499,134 @@ impl<'de> serde::Deserialize<'de> for RegionSettings { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = RegionSettings; + type Value = RequestResponse; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.RegionSettings") + formatter.write_str("struct livekit.RequestResponse") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut regions__ = None; + let mut request_id__ = None; + let mut reason__ = None; + let mut message__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::Regions => { - if regions__.is_some() { - return Err(serde::de::Error::duplicate_field("regions")); + GeneratedField::RequestId => { + if request_id__.is_some() { + return Err(serde::de::Error::duplicate_field("requestId")); } - regions__ = Some(map_.next_value()?); + request_id__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::Reason => { + if reason__.is_some() { + return Err(serde::de::Error::duplicate_field("reason")); + } + reason__ = Some(map_.next_value::()? as i32); + } + GeneratedField::Message => { + if message__.is_some() { + return Err(serde::de::Error::duplicate_field("message")); + } + message__ = Some(map_.next_value()?); } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } } } - Ok(RegionSettings { - regions: regions__.unwrap_or_default(), + Ok(RequestResponse { + request_id: request_id__.unwrap_or_default(), + reason: reason__.unwrap_or_default(), + message: message__.unwrap_or_default(), }) } } - deserializer.deserialize_struct("livekit.RegionSettings", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.RequestResponse", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for RegisterWorkerRequest { +impl serde::Serialize for request_response::Reason { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + let variant = match self { + Self::Ok => "OK", + Self::NotFound => "NOT_FOUND", + Self::NotAllowed => "NOT_ALLOWED", + Self::LimitExceeded => "LIMIT_EXCEEDED", + }; + serializer.serialize_str(variant) + } +} +impl<'de> serde::Deserialize<'de> for request_response::Reason { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "OK", + "NOT_FOUND", + "NOT_ALLOWED", + "LIMIT_EXCEEDED", + ]; + + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = request_response::Reason; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + fn visit_i64(self, v: i64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self) + }) + } + + fn visit_u64(self, v: u64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(v), &self) + }) + } + + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "OK" => Ok(request_response::Reason::Ok), + "NOT_FOUND" => Ok(request_response::Reason::NotFound), + "NOT_ALLOWED" => Ok(request_response::Reason::NotAllowed), + "LIMIT_EXCEEDED" => Ok(request_response::Reason::LimitExceeded), + _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), + } + } + } + deserializer.deserialize_any(GeneratedVisitor) + } +} +impl serde::Serialize for Room { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -17391,74 +19634,136 @@ impl serde::Serialize for RegisterWorkerRequest { { use serde::ser::SerializeStruct; let mut len = 0; - if self.r#type != 0 { + if !self.sid.is_empty() { len += 1; } - if !self.agent_name.is_empty() { + if !self.name.is_empty() { len += 1; } - if !self.version.is_empty() { + if self.empty_timeout != 0 { + len += 1; + } + if self.departure_timeout != 0 { + len += 1; + } + if self.max_participants != 0 { + len += 1; + } + if self.creation_time != 0 { + len += 1; + } + if !self.turn_password.is_empty() { + len += 1; + } + if !self.enabled_codecs.is_empty() { + len += 1; + } + if !self.metadata.is_empty() { + len += 1; + } + if self.num_participants != 0 { + len += 1; + } + if self.num_publishers != 0 { len += 1; } - if self.ping_interval != 0 { - len += 1; + if self.active_recording { + len += 1; + } + if self.version.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.Room", len)?; + if !self.sid.is_empty() { + struct_ser.serialize_field("sid", &self.sid)?; + } + if !self.name.is_empty() { + struct_ser.serialize_field("name", &self.name)?; + } + if self.empty_timeout != 0 { + struct_ser.serialize_field("emptyTimeout", &self.empty_timeout)?; + } + if self.departure_timeout != 0 { + struct_ser.serialize_field("departureTimeout", &self.departure_timeout)?; } - if self.namespace.is_some() { - len += 1; + if self.max_participants != 0 { + struct_ser.serialize_field("maxParticipants", &self.max_participants)?; } - if self.allowed_permissions.is_some() { - len += 1; + if self.creation_time != 0 { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("creationTime", ToString::to_string(&self.creation_time).as_str())?; } - let mut struct_ser = serializer.serialize_struct("livekit.RegisterWorkerRequest", len)?; - if self.r#type != 0 { - let v = JobType::try_from(self.r#type) - .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.r#type)))?; - struct_ser.serialize_field("type", &v)?; + if !self.turn_password.is_empty() { + struct_ser.serialize_field("turnPassword", &self.turn_password)?; } - if !self.agent_name.is_empty() { - struct_ser.serialize_field("agentName", &self.agent_name)?; + if !self.enabled_codecs.is_empty() { + struct_ser.serialize_field("enabledCodecs", &self.enabled_codecs)?; } - if !self.version.is_empty() { - struct_ser.serialize_field("version", &self.version)?; + if !self.metadata.is_empty() { + struct_ser.serialize_field("metadata", &self.metadata)?; } - if self.ping_interval != 0 { - struct_ser.serialize_field("pingInterval", &self.ping_interval)?; + if self.num_participants != 0 { + struct_ser.serialize_field("numParticipants", &self.num_participants)?; } - if let Some(v) = self.namespace.as_ref() { - struct_ser.serialize_field("namespace", v)?; + if self.num_publishers != 0 { + struct_ser.serialize_field("numPublishers", &self.num_publishers)?; } - if let Some(v) = self.allowed_permissions.as_ref() { - struct_ser.serialize_field("allowedPermissions", v)?; + if self.active_recording { + struct_ser.serialize_field("activeRecording", &self.active_recording)?; + } + if let Some(v) = self.version.as_ref() { + struct_ser.serialize_field("version", v)?; } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for RegisterWorkerRequest { +impl<'de> serde::Deserialize<'de> for Room { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "type", - "agent_name", - "agentName", + "sid", + "name", + "empty_timeout", + "emptyTimeout", + "departure_timeout", + "departureTimeout", + "max_participants", + "maxParticipants", + "creation_time", + "creationTime", + "turn_password", + "turnPassword", + "enabled_codecs", + "enabledCodecs", + "metadata", + "num_participants", + "numParticipants", + "num_publishers", + "numPublishers", + "active_recording", + "activeRecording", "version", - "ping_interval", - "pingInterval", - "namespace", - "allowed_permissions", - "allowedPermissions", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { - Type, - AgentName, + Sid, + Name, + EmptyTimeout, + DepartureTimeout, + MaxParticipants, + CreationTime, + TurnPassword, + EnabledCodecs, + Metadata, + NumParticipants, + NumPublishers, + ActiveRecording, Version, - PingInterval, - Namespace, - AllowedPermissions, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -17481,12 +19786,19 @@ impl<'de> serde::Deserialize<'de> for RegisterWorkerRequest { E: serde::de::Error, { match value { - "type" => Ok(GeneratedField::Type), - "agentName" | "agent_name" => Ok(GeneratedField::AgentName), + "sid" => Ok(GeneratedField::Sid), + "name" => Ok(GeneratedField::Name), + "emptyTimeout" | "empty_timeout" => Ok(GeneratedField::EmptyTimeout), + "departureTimeout" | "departure_timeout" => Ok(GeneratedField::DepartureTimeout), + "maxParticipants" | "max_participants" => Ok(GeneratedField::MaxParticipants), + "creationTime" | "creation_time" => Ok(GeneratedField::CreationTime), + "turnPassword" | "turn_password" => Ok(GeneratedField::TurnPassword), + "enabledCodecs" | "enabled_codecs" => Ok(GeneratedField::EnabledCodecs), + "metadata" => Ok(GeneratedField::Metadata), + "numParticipants" | "num_participants" => Ok(GeneratedField::NumParticipants), + "numPublishers" | "num_publishers" => Ok(GeneratedField::NumPublishers), + "activeRecording" | "active_recording" => Ok(GeneratedField::ActiveRecording), "version" => Ok(GeneratedField::Version), - "pingInterval" | "ping_interval" => Ok(GeneratedField::PingInterval), - "namespace" => Ok(GeneratedField::Namespace), - "allowedPermissions" | "allowed_permissions" => Ok(GeneratedField::AllowedPermissions), _ => Ok(GeneratedField::__SkipField__), } } @@ -17496,81 +19808,147 @@ impl<'de> serde::Deserialize<'de> for RegisterWorkerRequest { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = RegisterWorkerRequest; + type Value = Room; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.RegisterWorkerRequest") + formatter.write_str("struct livekit.Room") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut r#type__ = None; - let mut agent_name__ = None; + let mut sid__ = None; + let mut name__ = None; + let mut empty_timeout__ = None; + let mut departure_timeout__ = None; + let mut max_participants__ = None; + let mut creation_time__ = None; + let mut turn_password__ = None; + let mut enabled_codecs__ = None; + let mut metadata__ = None; + let mut num_participants__ = None; + let mut num_publishers__ = None; + let mut active_recording__ = None; let mut version__ = None; - let mut ping_interval__ = None; - let mut namespace__ = None; - let mut allowed_permissions__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::Type => { - if r#type__.is_some() { - return Err(serde::de::Error::duplicate_field("type")); + GeneratedField::Sid => { + if sid__.is_some() { + return Err(serde::de::Error::duplicate_field("sid")); } - r#type__ = Some(map_.next_value::()? as i32); + sid__ = Some(map_.next_value()?); } - GeneratedField::AgentName => { - if agent_name__.is_some() { - return Err(serde::de::Error::duplicate_field("agentName")); + GeneratedField::Name => { + if name__.is_some() { + return Err(serde::de::Error::duplicate_field("name")); } - agent_name__ = Some(map_.next_value()?); + name__ = Some(map_.next_value()?); } - GeneratedField::Version => { - if version__.is_some() { - return Err(serde::de::Error::duplicate_field("version")); + GeneratedField::EmptyTimeout => { + if empty_timeout__.is_some() { + return Err(serde::de::Error::duplicate_field("emptyTimeout")); } - version__ = Some(map_.next_value()?); + empty_timeout__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; } - GeneratedField::PingInterval => { - if ping_interval__.is_some() { - return Err(serde::de::Error::duplicate_field("pingInterval")); + GeneratedField::DepartureTimeout => { + if departure_timeout__.is_some() { + return Err(serde::de::Error::duplicate_field("departureTimeout")); } - ping_interval__ = + departure_timeout__ = Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) ; } - GeneratedField::Namespace => { - if namespace__.is_some() { - return Err(serde::de::Error::duplicate_field("namespace")); + GeneratedField::MaxParticipants => { + if max_participants__.is_some() { + return Err(serde::de::Error::duplicate_field("maxParticipants")); } - namespace__ = map_.next_value()?; + max_participants__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; } - GeneratedField::AllowedPermissions => { - if allowed_permissions__.is_some() { - return Err(serde::de::Error::duplicate_field("allowedPermissions")); + GeneratedField::CreationTime => { + if creation_time__.is_some() { + return Err(serde::de::Error::duplicate_field("creationTime")); } - allowed_permissions__ = map_.next_value()?; + creation_time__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::TurnPassword => { + if turn_password__.is_some() { + return Err(serde::de::Error::duplicate_field("turnPassword")); + } + turn_password__ = Some(map_.next_value()?); + } + GeneratedField::EnabledCodecs => { + if enabled_codecs__.is_some() { + return Err(serde::de::Error::duplicate_field("enabledCodecs")); + } + enabled_codecs__ = Some(map_.next_value()?); + } + GeneratedField::Metadata => { + if metadata__.is_some() { + return Err(serde::de::Error::duplicate_field("metadata")); + } + metadata__ = Some(map_.next_value()?); + } + GeneratedField::NumParticipants => { + if num_participants__.is_some() { + return Err(serde::de::Error::duplicate_field("numParticipants")); + } + num_participants__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::NumPublishers => { + if num_publishers__.is_some() { + return Err(serde::de::Error::duplicate_field("numPublishers")); + } + num_publishers__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::ActiveRecording => { + if active_recording__.is_some() { + return Err(serde::de::Error::duplicate_field("activeRecording")); + } + active_recording__ = Some(map_.next_value()?); + } + GeneratedField::Version => { + if version__.is_some() { + return Err(serde::de::Error::duplicate_field("version")); + } + version__ = map_.next_value()?; } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } } } - Ok(RegisterWorkerRequest { - r#type: r#type__.unwrap_or_default(), - agent_name: agent_name__.unwrap_or_default(), - version: version__.unwrap_or_default(), - ping_interval: ping_interval__.unwrap_or_default(), - namespace: namespace__, - allowed_permissions: allowed_permissions__, + Ok(Room { + sid: sid__.unwrap_or_default(), + name: name__.unwrap_or_default(), + empty_timeout: empty_timeout__.unwrap_or_default(), + departure_timeout: departure_timeout__.unwrap_or_default(), + max_participants: max_participants__.unwrap_or_default(), + creation_time: creation_time__.unwrap_or_default(), + turn_password: turn_password__.unwrap_or_default(), + enabled_codecs: enabled_codecs__.unwrap_or_default(), + metadata: metadata__.unwrap_or_default(), + num_participants: num_participants__.unwrap_or_default(), + num_publishers: num_publishers__.unwrap_or_default(), + active_recording: active_recording__.unwrap_or_default(), + version: version__, }) } } - deserializer.deserialize_struct("livekit.RegisterWorkerRequest", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.Room", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for RegisterWorkerResponse { +impl serde::Serialize for RoomAgent { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -17578,39 +19956,29 @@ impl serde::Serialize for RegisterWorkerResponse { { use serde::ser::SerializeStruct; let mut len = 0; - if !self.worker_id.is_empty() { - len += 1; - } - if self.server_info.is_some() { + if !self.dispatches.is_empty() { len += 1; } - let mut struct_ser = serializer.serialize_struct("livekit.RegisterWorkerResponse", len)?; - if !self.worker_id.is_empty() { - struct_ser.serialize_field("workerId", &self.worker_id)?; - } - if let Some(v) = self.server_info.as_ref() { - struct_ser.serialize_field("serverInfo", v)?; + let mut struct_ser = serializer.serialize_struct("livekit.RoomAgent", len)?; + if !self.dispatches.is_empty() { + struct_ser.serialize_field("dispatches", &self.dispatches)?; } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for RegisterWorkerResponse { +impl<'de> serde::Deserialize<'de> for RoomAgent { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "worker_id", - "workerId", - "server_info", - "serverInfo", + "dispatches", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { - WorkerId, - ServerInfo, + Dispatches, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -17633,8 +20001,7 @@ impl<'de> serde::Deserialize<'de> for RegisterWorkerResponse { E: serde::de::Error, { match value { - "workerId" | "worker_id" => Ok(GeneratedField::WorkerId), - "serverInfo" | "server_info" => Ok(GeneratedField::ServerInfo), + "dispatches" => Ok(GeneratedField::Dispatches), _ => Ok(GeneratedField::__SkipField__), } } @@ -17644,69 +20011,78 @@ impl<'de> serde::Deserialize<'de> for RegisterWorkerResponse { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = RegisterWorkerResponse; + type Value = RoomAgent; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.RegisterWorkerResponse") + formatter.write_str("struct livekit.RoomAgent") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut worker_id__ = None; - let mut server_info__ = None; + let mut dispatches__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::WorkerId => { - if worker_id__.is_some() { - return Err(serde::de::Error::duplicate_field("workerId")); - } - worker_id__ = Some(map_.next_value()?); - } - GeneratedField::ServerInfo => { - if server_info__.is_some() { - return Err(serde::de::Error::duplicate_field("serverInfo")); + GeneratedField::Dispatches => { + if dispatches__.is_some() { + return Err(serde::de::Error::duplicate_field("dispatches")); } - server_info__ = map_.next_value()?; + dispatches__ = Some(map_.next_value()?); } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } } } - Ok(RegisterWorkerResponse { - worker_id: worker_id__.unwrap_or_default(), - server_info: server_info__, + Ok(RoomAgent { + dispatches: dispatches__.unwrap_or_default(), }) } } - deserializer.deserialize_struct("livekit.RegisterWorkerResponse", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.RoomAgent", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for RemoveParticipantResponse { +impl serde::Serialize for RoomAgentDispatch { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where S: serde::Serializer, { use serde::ser::SerializeStruct; - let len = 0; - let struct_ser = serializer.serialize_struct("livekit.RemoveParticipantResponse", len)?; + let mut len = 0; + if !self.agent_name.is_empty() { + len += 1; + } + if !self.metadata.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.RoomAgentDispatch", len)?; + if !self.agent_name.is_empty() { + struct_ser.serialize_field("agentName", &self.agent_name)?; + } + if !self.metadata.is_empty() { + struct_ser.serialize_field("metadata", &self.metadata)?; + } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for RemoveParticipantResponse { +impl<'de> serde::Deserialize<'de> for RoomAgentDispatch { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ + "agent_name", + "agentName", + "metadata", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { + AgentName, + Metadata, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -17728,7 +20104,11 @@ impl<'de> serde::Deserialize<'de> for RemoveParticipantResponse { where E: serde::de::Error, { - Ok(GeneratedField::__SkipField__) + match value { + "agentName" | "agent_name" => Ok(GeneratedField::AgentName), + "metadata" => Ok(GeneratedField::Metadata), + _ => Ok(GeneratedField::__SkipField__), + } } } deserializer.deserialize_identifier(GeneratedVisitor) @@ -17736,27 +20116,47 @@ impl<'de> serde::Deserialize<'de> for RemoveParticipantResponse { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = RemoveParticipantResponse; + type Value = RoomAgentDispatch; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.RemoveParticipantResponse") + formatter.write_str("struct livekit.RoomAgentDispatch") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - while map_.next_key::()?.is_some() { - let _ = map_.next_value::()?; + let mut agent_name__ = None; + let mut metadata__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::AgentName => { + if agent_name__.is_some() { + return Err(serde::de::Error::duplicate_field("agentName")); + } + agent_name__ = Some(map_.next_value()?); + } + GeneratedField::Metadata => { + if metadata__.is_some() { + return Err(serde::de::Error::duplicate_field("metadata")); + } + metadata__ = Some(map_.next_value()?); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } } - Ok(RemoveParticipantResponse { + Ok(RoomAgentDispatch { + agent_name: agent_name__.unwrap_or_default(), + metadata: metadata__.unwrap_or_default(), }) } } - deserializer.deserialize_struct("livekit.RemoveParticipantResponse", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.RoomAgentDispatch", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for RequestResponse { +impl serde::Serialize for RoomCompositeEgressRequest { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -17764,48 +20164,142 @@ impl serde::Serialize for RequestResponse { { use serde::ser::SerializeStruct; let mut len = 0; - if self.request_id != 0 { + if !self.room_name.is_empty() { len += 1; } - if self.reason != 0 { + if !self.layout.is_empty() { len += 1; } - if !self.message.is_empty() { + if self.audio_only { len += 1; } - let mut struct_ser = serializer.serialize_struct("livekit.RequestResponse", len)?; - if self.request_id != 0 { - struct_ser.serialize_field("requestId", &self.request_id)?; + if self.video_only { + len += 1; } - if self.reason != 0 { - let v = request_response::Reason::try_from(self.reason) - .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.reason)))?; - struct_ser.serialize_field("reason", &v)?; + if !self.custom_base_url.is_empty() { + len += 1; } - if !self.message.is_empty() { - struct_ser.serialize_field("message", &self.message)?; + if !self.file_outputs.is_empty() { + len += 1; + } + if !self.stream_outputs.is_empty() { + len += 1; + } + if !self.segment_outputs.is_empty() { + len += 1; + } + if !self.image_outputs.is_empty() { + len += 1; + } + if self.output.is_some() { + len += 1; + } + if self.options.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.RoomCompositeEgressRequest", len)?; + if !self.room_name.is_empty() { + struct_ser.serialize_field("roomName", &self.room_name)?; + } + if !self.layout.is_empty() { + struct_ser.serialize_field("layout", &self.layout)?; + } + if self.audio_only { + struct_ser.serialize_field("audioOnly", &self.audio_only)?; + } + if self.video_only { + struct_ser.serialize_field("videoOnly", &self.video_only)?; + } + if !self.custom_base_url.is_empty() { + struct_ser.serialize_field("customBaseUrl", &self.custom_base_url)?; + } + if !self.file_outputs.is_empty() { + struct_ser.serialize_field("fileOutputs", &self.file_outputs)?; + } + if !self.stream_outputs.is_empty() { + struct_ser.serialize_field("streamOutputs", &self.stream_outputs)?; + } + if !self.segment_outputs.is_empty() { + struct_ser.serialize_field("segmentOutputs", &self.segment_outputs)?; + } + if !self.image_outputs.is_empty() { + struct_ser.serialize_field("imageOutputs", &self.image_outputs)?; + } + if let Some(v) = self.output.as_ref() { + match v { + room_composite_egress_request::Output::File(v) => { + struct_ser.serialize_field("file", v)?; + } + room_composite_egress_request::Output::Stream(v) => { + struct_ser.serialize_field("stream", v)?; + } + room_composite_egress_request::Output::Segments(v) => { + struct_ser.serialize_field("segments", v)?; + } + } + } + if let Some(v) = self.options.as_ref() { + match v { + room_composite_egress_request::Options::Preset(v) => { + let v = EncodingOptionsPreset::try_from(*v) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", *v)))?; + struct_ser.serialize_field("preset", &v)?; + } + room_composite_egress_request::Options::Advanced(v) => { + struct_ser.serialize_field("advanced", v)?; + } + } } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for RequestResponse { +impl<'de> serde::Deserialize<'de> for RoomCompositeEgressRequest { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "request_id", - "requestId", - "reason", - "message", + "room_name", + "roomName", + "layout", + "audio_only", + "audioOnly", + "video_only", + "videoOnly", + "custom_base_url", + "customBaseUrl", + "file_outputs", + "fileOutputs", + "stream_outputs", + "streamOutputs", + "segment_outputs", + "segmentOutputs", + "image_outputs", + "imageOutputs", + "file", + "stream", + "segments", + "preset", + "advanced", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { - RequestId, - Reason, - Message, + RoomName, + Layout, + AudioOnly, + VideoOnly, + CustomBaseUrl, + FileOutputs, + StreamOutputs, + SegmentOutputs, + ImageOutputs, + File, + Stream, + Segments, + Preset, + Advanced, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -17828,9 +20322,20 @@ impl<'de> serde::Deserialize<'de> for RequestResponse { E: serde::de::Error, { match value { - "requestId" | "request_id" => Ok(GeneratedField::RequestId), - "reason" => Ok(GeneratedField::Reason), - "message" => Ok(GeneratedField::Message), + "roomName" | "room_name" => Ok(GeneratedField::RoomName), + "layout" => Ok(GeneratedField::Layout), + "audioOnly" | "audio_only" => Ok(GeneratedField::AudioOnly), + "videoOnly" | "video_only" => Ok(GeneratedField::VideoOnly), + "customBaseUrl" | "custom_base_url" => Ok(GeneratedField::CustomBaseUrl), + "fileOutputs" | "file_outputs" => Ok(GeneratedField::FileOutputs), + "streamOutputs" | "stream_outputs" => Ok(GeneratedField::StreamOutputs), + "segmentOutputs" | "segment_outputs" => Ok(GeneratedField::SegmentOutputs), + "imageOutputs" | "image_outputs" => Ok(GeneratedField::ImageOutputs), + "file" => Ok(GeneratedField::File), + "stream" => Ok(GeneratedField::Stream), + "segments" => Ok(GeneratedField::Segments), + "preset" => Ok(GeneratedField::Preset), + "advanced" => Ok(GeneratedField::Advanced), _ => Ok(GeneratedField::__SkipField__), } } @@ -17840,134 +20345,141 @@ impl<'de> serde::Deserialize<'de> for RequestResponse { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = RequestResponse; + type Value = RoomCompositeEgressRequest; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.RequestResponse") + formatter.write_str("struct livekit.RoomCompositeEgressRequest") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut request_id__ = None; - let mut reason__ = None; - let mut message__ = None; + let mut room_name__ = None; + let mut layout__ = None; + let mut audio_only__ = None; + let mut video_only__ = None; + let mut custom_base_url__ = None; + let mut file_outputs__ = None; + let mut stream_outputs__ = None; + let mut segment_outputs__ = None; + let mut image_outputs__ = None; + let mut output__ = None; + let mut options__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::RequestId => { - if request_id__.is_some() { - return Err(serde::de::Error::duplicate_field("requestId")); + GeneratedField::RoomName => { + if room_name__.is_some() { + return Err(serde::de::Error::duplicate_field("roomName")); } - request_id__ = - Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; + room_name__ = Some(map_.next_value()?); } - GeneratedField::Reason => { - if reason__.is_some() { - return Err(serde::de::Error::duplicate_field("reason")); + GeneratedField::Layout => { + if layout__.is_some() { + return Err(serde::de::Error::duplicate_field("layout")); } - reason__ = Some(map_.next_value::()? as i32); + layout__ = Some(map_.next_value()?); } - GeneratedField::Message => { - if message__.is_some() { - return Err(serde::de::Error::duplicate_field("message")); + GeneratedField::AudioOnly => { + if audio_only__.is_some() { + return Err(serde::de::Error::duplicate_field("audioOnly")); } - message__ = Some(map_.next_value()?); + audio_only__ = Some(map_.next_value()?); + } + GeneratedField::VideoOnly => { + if video_only__.is_some() { + return Err(serde::de::Error::duplicate_field("videoOnly")); + } + video_only__ = Some(map_.next_value()?); + } + GeneratedField::CustomBaseUrl => { + if custom_base_url__.is_some() { + return Err(serde::de::Error::duplicate_field("customBaseUrl")); + } + custom_base_url__ = Some(map_.next_value()?); + } + GeneratedField::FileOutputs => { + if file_outputs__.is_some() { + return Err(serde::de::Error::duplicate_field("fileOutputs")); + } + file_outputs__ = Some(map_.next_value()?); + } + GeneratedField::StreamOutputs => { + if stream_outputs__.is_some() { + return Err(serde::de::Error::duplicate_field("streamOutputs")); + } + stream_outputs__ = Some(map_.next_value()?); + } + GeneratedField::SegmentOutputs => { + if segment_outputs__.is_some() { + return Err(serde::de::Error::duplicate_field("segmentOutputs")); + } + segment_outputs__ = Some(map_.next_value()?); + } + GeneratedField::ImageOutputs => { + if image_outputs__.is_some() { + return Err(serde::de::Error::duplicate_field("imageOutputs")); + } + image_outputs__ = Some(map_.next_value()?); + } + GeneratedField::File => { + if output__.is_some() { + return Err(serde::de::Error::duplicate_field("file")); + } + output__ = map_.next_value::<::std::option::Option<_>>()?.map(room_composite_egress_request::Output::File) +; + } + GeneratedField::Stream => { + if output__.is_some() { + return Err(serde::de::Error::duplicate_field("stream")); + } + output__ = map_.next_value::<::std::option::Option<_>>()?.map(room_composite_egress_request::Output::Stream) +; + } + GeneratedField::Segments => { + if output__.is_some() { + return Err(serde::de::Error::duplicate_field("segments")); + } + output__ = map_.next_value::<::std::option::Option<_>>()?.map(room_composite_egress_request::Output::Segments) +; + } + GeneratedField::Preset => { + if options__.is_some() { + return Err(serde::de::Error::duplicate_field("preset")); + } + options__ = map_.next_value::<::std::option::Option>()?.map(|x| room_composite_egress_request::Options::Preset(x as i32)); + } + GeneratedField::Advanced => { + if options__.is_some() { + return Err(serde::de::Error::duplicate_field("advanced")); + } + options__ = map_.next_value::<::std::option::Option<_>>()?.map(room_composite_egress_request::Options::Advanced) +; } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } } } - Ok(RequestResponse { - request_id: request_id__.unwrap_or_default(), - reason: reason__.unwrap_or_default(), - message: message__.unwrap_or_default(), + Ok(RoomCompositeEgressRequest { + room_name: room_name__.unwrap_or_default(), + layout: layout__.unwrap_or_default(), + audio_only: audio_only__.unwrap_or_default(), + video_only: video_only__.unwrap_or_default(), + custom_base_url: custom_base_url__.unwrap_or_default(), + file_outputs: file_outputs__.unwrap_or_default(), + stream_outputs: stream_outputs__.unwrap_or_default(), + segment_outputs: segment_outputs__.unwrap_or_default(), + image_outputs: image_outputs__.unwrap_or_default(), + output: output__, + options: options__, }) } } - deserializer.deserialize_struct("livekit.RequestResponse", FIELDS, GeneratedVisitor) - } -} -impl serde::Serialize for request_response::Reason { - #[allow(deprecated)] - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::Serializer, - { - let variant = match self { - Self::Ok => "OK", - Self::NotFound => "NOT_FOUND", - Self::NotAllowed => "NOT_ALLOWED", - Self::LimitExceeded => "LIMIT_EXCEEDED", - }; - serializer.serialize_str(variant) - } -} -impl<'de> serde::Deserialize<'de> for request_response::Reason { - #[allow(deprecated)] - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - const FIELDS: &[&str] = &[ - "OK", - "NOT_FOUND", - "NOT_ALLOWED", - "LIMIT_EXCEEDED", - ]; - - struct GeneratedVisitor; - - impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = request_response::Reason; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(formatter, "expected one of: {:?}", &FIELDS) - } - - fn visit_i64(self, v: i64) -> std::result::Result - where - E: serde::de::Error, - { - i32::try_from(v) - .ok() - .and_then(|x| x.try_into().ok()) - .ok_or_else(|| { - serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self) - }) - } - - fn visit_u64(self, v: u64) -> std::result::Result - where - E: serde::de::Error, - { - i32::try_from(v) - .ok() - .and_then(|x| x.try_into().ok()) - .ok_or_else(|| { - serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(v), &self) - }) - } - - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - match value { - "OK" => Ok(request_response::Reason::Ok), - "NOT_FOUND" => Ok(request_response::Reason::NotFound), - "NOT_ALLOWED" => Ok(request_response::Reason::NotAllowed), - "LIMIT_EXCEEDED" => Ok(request_response::Reason::LimitExceeded), - _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), - } - } - } - deserializer.deserialize_any(GeneratedVisitor) + deserializer.deserialize_struct("livekit.RoomCompositeEgressRequest", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for Room { +impl serde::Serialize for RoomConfiguration { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -17975,9 +20487,6 @@ impl serde::Serialize for Room { { use serde::ser::SerializeStruct; let mut len = 0; - if !self.sid.is_empty() { - len += 1; - } if !self.name.is_empty() { len += 1; } @@ -17990,34 +20499,22 @@ impl serde::Serialize for Room { if self.max_participants != 0 { len += 1; } - if self.creation_time != 0 { - len += 1; - } - if !self.turn_password.is_empty() { - len += 1; - } - if !self.enabled_codecs.is_empty() { - len += 1; - } - if !self.metadata.is_empty() { - len += 1; - } - if self.num_participants != 0 { + if self.egress.is_some() { len += 1; } - if self.num_publishers != 0 { + if self.agent.is_some() { len += 1; } - if self.active_recording { + if self.min_playout_delay != 0 { len += 1; } - if self.version.is_some() { + if self.max_playout_delay != 0 { len += 1; } - let mut struct_ser = serializer.serialize_struct("livekit.Room", len)?; - if !self.sid.is_empty() { - struct_ser.serialize_field("sid", &self.sid)?; + if self.sync_streams { + len += 1; } + let mut struct_ser = serializer.serialize_struct("livekit.RoomConfiguration", len)?; if !self.name.is_empty() { struct_ser.serialize_field("name", &self.name)?; } @@ -18030,43 +20527,31 @@ impl serde::Serialize for Room { if self.max_participants != 0 { struct_ser.serialize_field("maxParticipants", &self.max_participants)?; } - if self.creation_time != 0 { - #[allow(clippy::needless_borrow)] - #[allow(clippy::needless_borrows_for_generic_args)] - struct_ser.serialize_field("creationTime", ToString::to_string(&self.creation_time).as_str())?; - } - if !self.turn_password.is_empty() { - struct_ser.serialize_field("turnPassword", &self.turn_password)?; - } - if !self.enabled_codecs.is_empty() { - struct_ser.serialize_field("enabledCodecs", &self.enabled_codecs)?; - } - if !self.metadata.is_empty() { - struct_ser.serialize_field("metadata", &self.metadata)?; + if let Some(v) = self.egress.as_ref() { + struct_ser.serialize_field("egress", v)?; } - if self.num_participants != 0 { - struct_ser.serialize_field("numParticipants", &self.num_participants)?; + if let Some(v) = self.agent.as_ref() { + struct_ser.serialize_field("agent", v)?; } - if self.num_publishers != 0 { - struct_ser.serialize_field("numPublishers", &self.num_publishers)?; + if self.min_playout_delay != 0 { + struct_ser.serialize_field("minPlayoutDelay", &self.min_playout_delay)?; } - if self.active_recording { - struct_ser.serialize_field("activeRecording", &self.active_recording)?; + if self.max_playout_delay != 0 { + struct_ser.serialize_field("maxPlayoutDelay", &self.max_playout_delay)?; } - if let Some(v) = self.version.as_ref() { - struct_ser.serialize_field("version", v)?; + if self.sync_streams { + struct_ser.serialize_field("syncStreams", &self.sync_streams)?; } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for Room { +impl<'de> serde::Deserialize<'de> for RoomConfiguration { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "sid", "name", "empty_timeout", "emptyTimeout", @@ -18074,37 +20559,27 @@ impl<'de> serde::Deserialize<'de> for Room { "departureTimeout", "max_participants", "maxParticipants", - "creation_time", - "creationTime", - "turn_password", - "turnPassword", - "enabled_codecs", - "enabledCodecs", - "metadata", - "num_participants", - "numParticipants", - "num_publishers", - "numPublishers", - "active_recording", - "activeRecording", - "version", + "egress", + "agent", + "min_playout_delay", + "minPlayoutDelay", + "max_playout_delay", + "maxPlayoutDelay", + "sync_streams", + "syncStreams", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { - Sid, Name, EmptyTimeout, DepartureTimeout, MaxParticipants, - CreationTime, - TurnPassword, - EnabledCodecs, - Metadata, - NumParticipants, - NumPublishers, - ActiveRecording, - Version, + Egress, + Agent, + MinPlayoutDelay, + MaxPlayoutDelay, + SyncStreams, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -18127,19 +20602,15 @@ impl<'de> serde::Deserialize<'de> for Room { E: serde::de::Error, { match value { - "sid" => Ok(GeneratedField::Sid), "name" => Ok(GeneratedField::Name), "emptyTimeout" | "empty_timeout" => Ok(GeneratedField::EmptyTimeout), "departureTimeout" | "departure_timeout" => Ok(GeneratedField::DepartureTimeout), "maxParticipants" | "max_participants" => Ok(GeneratedField::MaxParticipants), - "creationTime" | "creation_time" => Ok(GeneratedField::CreationTime), - "turnPassword" | "turn_password" => Ok(GeneratedField::TurnPassword), - "enabledCodecs" | "enabled_codecs" => Ok(GeneratedField::EnabledCodecs), - "metadata" => Ok(GeneratedField::Metadata), - "numParticipants" | "num_participants" => Ok(GeneratedField::NumParticipants), - "numPublishers" | "num_publishers" => Ok(GeneratedField::NumPublishers), - "activeRecording" | "active_recording" => Ok(GeneratedField::ActiveRecording), - "version" => Ok(GeneratedField::Version), + "egress" => Ok(GeneratedField::Egress), + "agent" => Ok(GeneratedField::Agent), + "minPlayoutDelay" | "min_playout_delay" => Ok(GeneratedField::MinPlayoutDelay), + "maxPlayoutDelay" | "max_playout_delay" => Ok(GeneratedField::MaxPlayoutDelay), + "syncStreams" | "sync_streams" => Ok(GeneratedField::SyncStreams), _ => Ok(GeneratedField::__SkipField__), } } @@ -18149,37 +20620,27 @@ impl<'de> serde::Deserialize<'de> for Room { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = Room; + type Value = RoomConfiguration; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.Room") + formatter.write_str("struct livekit.RoomConfiguration") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut sid__ = None; let mut name__ = None; let mut empty_timeout__ = None; let mut departure_timeout__ = None; let mut max_participants__ = None; - let mut creation_time__ = None; - let mut turn_password__ = None; - let mut enabled_codecs__ = None; - let mut metadata__ = None; - let mut num_participants__ = None; - let mut num_publishers__ = None; - let mut active_recording__ = None; - let mut version__ = None; + let mut egress__ = None; + let mut agent__ = None; + let mut min_playout_delay__ = None; + let mut max_playout_delay__ = None; + let mut sync_streams__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::Sid => { - if sid__.is_some() { - return Err(serde::de::Error::duplicate_field("sid")); - } - sid__ = Some(map_.next_value()?); - } GeneratedField::Name => { if name__.is_some() { return Err(serde::de::Error::duplicate_field("name")); @@ -18210,181 +20671,62 @@ impl<'de> serde::Deserialize<'de> for Room { Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) ; } - GeneratedField::CreationTime => { - if creation_time__.is_some() { - return Err(serde::de::Error::duplicate_field("creationTime")); - } - creation_time__ = - Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; - } - GeneratedField::TurnPassword => { - if turn_password__.is_some() { - return Err(serde::de::Error::duplicate_field("turnPassword")); - } - turn_password__ = Some(map_.next_value()?); - } - GeneratedField::EnabledCodecs => { - if enabled_codecs__.is_some() { - return Err(serde::de::Error::duplicate_field("enabledCodecs")); + GeneratedField::Egress => { + if egress__.is_some() { + return Err(serde::de::Error::duplicate_field("egress")); } - enabled_codecs__ = Some(map_.next_value()?); + egress__ = map_.next_value()?; } - GeneratedField::Metadata => { - if metadata__.is_some() { - return Err(serde::de::Error::duplicate_field("metadata")); + GeneratedField::Agent => { + if agent__.is_some() { + return Err(serde::de::Error::duplicate_field("agent")); } - metadata__ = Some(map_.next_value()?); + agent__ = map_.next_value()?; } - GeneratedField::NumParticipants => { - if num_participants__.is_some() { - return Err(serde::de::Error::duplicate_field("numParticipants")); + GeneratedField::MinPlayoutDelay => { + if min_playout_delay__.is_some() { + return Err(serde::de::Error::duplicate_field("minPlayoutDelay")); } - num_participants__ = + min_playout_delay__ = Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) ; } - GeneratedField::NumPublishers => { - if num_publishers__.is_some() { - return Err(serde::de::Error::duplicate_field("numPublishers")); + GeneratedField::MaxPlayoutDelay => { + if max_playout_delay__.is_some() { + return Err(serde::de::Error::duplicate_field("maxPlayoutDelay")); } - num_publishers__ = + max_playout_delay__ = Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; - } - GeneratedField::ActiveRecording => { - if active_recording__.is_some() { - return Err(serde::de::Error::duplicate_field("activeRecording")); - } - active_recording__ = Some(map_.next_value()?); - } - GeneratedField::Version => { - if version__.is_some() { - return Err(serde::de::Error::duplicate_field("version")); - } - version__ = map_.next_value()?; - } - GeneratedField::__SkipField__ => { - let _ = map_.next_value::()?; - } - } - } - Ok(Room { - sid: sid__.unwrap_or_default(), - name: name__.unwrap_or_default(), - empty_timeout: empty_timeout__.unwrap_or_default(), - departure_timeout: departure_timeout__.unwrap_or_default(), - max_participants: max_participants__.unwrap_or_default(), - creation_time: creation_time__.unwrap_or_default(), - turn_password: turn_password__.unwrap_or_default(), - enabled_codecs: enabled_codecs__.unwrap_or_default(), - metadata: metadata__.unwrap_or_default(), - num_participants: num_participants__.unwrap_or_default(), - num_publishers: num_publishers__.unwrap_or_default(), - active_recording: active_recording__.unwrap_or_default(), - version: version__, - }) - } - } - deserializer.deserialize_struct("livekit.Room", FIELDS, GeneratedVisitor) - } -} -impl serde::Serialize for RoomAgent { - #[allow(deprecated)] - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::Serializer, - { - use serde::ser::SerializeStruct; - let mut len = 0; - if !self.dispatches.is_empty() { - len += 1; - } - let mut struct_ser = serializer.serialize_struct("livekit.RoomAgent", len)?; - if !self.dispatches.is_empty() { - struct_ser.serialize_field("dispatches", &self.dispatches)?; - } - struct_ser.end() - } -} -impl<'de> serde::Deserialize<'de> for RoomAgent { - #[allow(deprecated)] - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - const FIELDS: &[&str] = &[ - "dispatches", - ]; - - #[allow(clippy::enum_variant_names)] - enum GeneratedField { - Dispatches, - __SkipField__, - } - impl<'de> serde::Deserialize<'de> for GeneratedField { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - struct GeneratedVisitor; - - impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = GeneratedField; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(formatter, "expected one of: {:?}", &FIELDS) - } - - #[allow(unused_variables)] - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - match value { - "dispatches" => Ok(GeneratedField::Dispatches), - _ => Ok(GeneratedField::__SkipField__), + ; } - } - } - deserializer.deserialize_identifier(GeneratedVisitor) - } - } - struct GeneratedVisitor; - impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = RoomAgent; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.RoomAgent") - } - - fn visit_map(self, mut map_: V) -> std::result::Result - where - V: serde::de::MapAccess<'de>, - { - let mut dispatches__ = None; - while let Some(k) = map_.next_key()? { - match k { - GeneratedField::Dispatches => { - if dispatches__.is_some() { - return Err(serde::de::Error::duplicate_field("dispatches")); + GeneratedField::SyncStreams => { + if sync_streams__.is_some() { + return Err(serde::de::Error::duplicate_field("syncStreams")); } - dispatches__ = Some(map_.next_value()?); + sync_streams__ = Some(map_.next_value()?); } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } } } - Ok(RoomAgent { - dispatches: dispatches__.unwrap_or_default(), + Ok(RoomConfiguration { + name: name__.unwrap_or_default(), + empty_timeout: empty_timeout__.unwrap_or_default(), + departure_timeout: departure_timeout__.unwrap_or_default(), + max_participants: max_participants__.unwrap_or_default(), + egress: egress__, + agent: agent__, + min_playout_delay: min_playout_delay__.unwrap_or_default(), + max_playout_delay: max_playout_delay__.unwrap_or_default(), + sync_streams: sync_streams__.unwrap_or_default(), }) } } - deserializer.deserialize_struct("livekit.RoomAgent", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.RoomConfiguration", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for RoomAgentDispatch { +impl serde::Serialize for RoomEgress { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -18392,38 +20734,45 @@ impl serde::Serialize for RoomAgentDispatch { { use serde::ser::SerializeStruct; let mut len = 0; - if !self.agent_name.is_empty() { + if self.room.is_some() { len += 1; } - if !self.metadata.is_empty() { + if self.participant.is_some() { len += 1; } - let mut struct_ser = serializer.serialize_struct("livekit.RoomAgentDispatch", len)?; - if !self.agent_name.is_empty() { - struct_ser.serialize_field("agentName", &self.agent_name)?; + if self.tracks.is_some() { + len += 1; } - if !self.metadata.is_empty() { - struct_ser.serialize_field("metadata", &self.metadata)?; + let mut struct_ser = serializer.serialize_struct("livekit.RoomEgress", len)?; + if let Some(v) = self.room.as_ref() { + struct_ser.serialize_field("room", v)?; + } + if let Some(v) = self.participant.as_ref() { + struct_ser.serialize_field("participant", v)?; + } + if let Some(v) = self.tracks.as_ref() { + struct_ser.serialize_field("tracks", v)?; } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for RoomAgentDispatch { +impl<'de> serde::Deserialize<'de> for RoomEgress { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "agent_name", - "agentName", - "metadata", + "room", + "participant", + "tracks", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { - AgentName, - Metadata, + Room, + Participant, + Tracks, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -18446,8 +20795,9 @@ impl<'de> serde::Deserialize<'de> for RoomAgentDispatch { E: serde::de::Error, { match value { - "agentName" | "agent_name" => Ok(GeneratedField::AgentName), - "metadata" => Ok(GeneratedField::Metadata), + "room" => Ok(GeneratedField::Room), + "participant" => Ok(GeneratedField::Participant), + "tracks" => Ok(GeneratedField::Tracks), _ => Ok(GeneratedField::__SkipField__), } } @@ -18457,47 +20807,55 @@ impl<'de> serde::Deserialize<'de> for RoomAgentDispatch { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = RoomAgentDispatch; + type Value = RoomEgress; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.RoomAgentDispatch") + formatter.write_str("struct livekit.RoomEgress") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut agent_name__ = None; - let mut metadata__ = None; + let mut room__ = None; + let mut participant__ = None; + let mut tracks__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::AgentName => { - if agent_name__.is_some() { - return Err(serde::de::Error::duplicate_field("agentName")); + GeneratedField::Room => { + if room__.is_some() { + return Err(serde::de::Error::duplicate_field("room")); } - agent_name__ = Some(map_.next_value()?); + room__ = map_.next_value()?; } - GeneratedField::Metadata => { - if metadata__.is_some() { - return Err(serde::de::Error::duplicate_field("metadata")); + GeneratedField::Participant => { + if participant__.is_some() { + return Err(serde::de::Error::duplicate_field("participant")); } - metadata__ = Some(map_.next_value()?); + participant__ = map_.next_value()?; + } + GeneratedField::Tracks => { + if tracks__.is_some() { + return Err(serde::de::Error::duplicate_field("tracks")); + } + tracks__ = map_.next_value()?; } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } } } - Ok(RoomAgentDispatch { - agent_name: agent_name__.unwrap_or_default(), - metadata: metadata__.unwrap_or_default(), + Ok(RoomEgress { + room: room__, + participant: participant__, + tracks: tracks__, }) } } - deserializer.deserialize_struct("livekit.RoomAgentDispatch", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.RoomEgress", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for RoomCompositeEgressRequest { +impl serde::Serialize for RoomParticipantIdentity { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -18505,142 +20863,37 @@ impl serde::Serialize for RoomCompositeEgressRequest { { use serde::ser::SerializeStruct; let mut len = 0; - if !self.room_name.is_empty() { - len += 1; - } - if !self.layout.is_empty() { - len += 1; - } - if self.audio_only { - len += 1; - } - if self.video_only { - len += 1; - } - if !self.custom_base_url.is_empty() { - len += 1; - } - if !self.file_outputs.is_empty() { - len += 1; - } - if !self.stream_outputs.is_empty() { - len += 1; - } - if !self.segment_outputs.is_empty() { - len += 1; - } - if !self.image_outputs.is_empty() { - len += 1; - } - if self.output.is_some() { + if !self.room.is_empty() { len += 1; } - if self.options.is_some() { + if !self.identity.is_empty() { len += 1; } - let mut struct_ser = serializer.serialize_struct("livekit.RoomCompositeEgressRequest", len)?; - if !self.room_name.is_empty() { - struct_ser.serialize_field("roomName", &self.room_name)?; - } - if !self.layout.is_empty() { - struct_ser.serialize_field("layout", &self.layout)?; - } - if self.audio_only { - struct_ser.serialize_field("audioOnly", &self.audio_only)?; - } - if self.video_only { - struct_ser.serialize_field("videoOnly", &self.video_only)?; - } - if !self.custom_base_url.is_empty() { - struct_ser.serialize_field("customBaseUrl", &self.custom_base_url)?; - } - if !self.file_outputs.is_empty() { - struct_ser.serialize_field("fileOutputs", &self.file_outputs)?; - } - if !self.stream_outputs.is_empty() { - struct_ser.serialize_field("streamOutputs", &self.stream_outputs)?; - } - if !self.segment_outputs.is_empty() { - struct_ser.serialize_field("segmentOutputs", &self.segment_outputs)?; - } - if !self.image_outputs.is_empty() { - struct_ser.serialize_field("imageOutputs", &self.image_outputs)?; - } - if let Some(v) = self.output.as_ref() { - match v { - room_composite_egress_request::Output::File(v) => { - struct_ser.serialize_field("file", v)?; - } - room_composite_egress_request::Output::Stream(v) => { - struct_ser.serialize_field("stream", v)?; - } - room_composite_egress_request::Output::Segments(v) => { - struct_ser.serialize_field("segments", v)?; - } - } + let mut struct_ser = serializer.serialize_struct("livekit.RoomParticipantIdentity", len)?; + if !self.room.is_empty() { + struct_ser.serialize_field("room", &self.room)?; } - if let Some(v) = self.options.as_ref() { - match v { - room_composite_egress_request::Options::Preset(v) => { - let v = EncodingOptionsPreset::try_from(*v) - .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", *v)))?; - struct_ser.serialize_field("preset", &v)?; - } - room_composite_egress_request::Options::Advanced(v) => { - struct_ser.serialize_field("advanced", v)?; - } - } + if !self.identity.is_empty() { + struct_ser.serialize_field("identity", &self.identity)?; } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for RoomCompositeEgressRequest { +impl<'de> serde::Deserialize<'de> for RoomParticipantIdentity { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "room_name", - "roomName", - "layout", - "audio_only", - "audioOnly", - "video_only", - "videoOnly", - "custom_base_url", - "customBaseUrl", - "file_outputs", - "fileOutputs", - "stream_outputs", - "streamOutputs", - "segment_outputs", - "segmentOutputs", - "image_outputs", - "imageOutputs", - "file", - "stream", - "segments", - "preset", - "advanced", + "room", + "identity", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { - RoomName, - Layout, - AudioOnly, - VideoOnly, - CustomBaseUrl, - FileOutputs, - StreamOutputs, - SegmentOutputs, - ImageOutputs, - File, - Stream, - Segments, - Preset, - Advanced, + Room, + Identity, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -18663,20 +20916,8 @@ impl<'de> serde::Deserialize<'de> for RoomCompositeEgressRequest { E: serde::de::Error, { match value { - "roomName" | "room_name" => Ok(GeneratedField::RoomName), - "layout" => Ok(GeneratedField::Layout), - "audioOnly" | "audio_only" => Ok(GeneratedField::AudioOnly), - "videoOnly" | "video_only" => Ok(GeneratedField::VideoOnly), - "customBaseUrl" | "custom_base_url" => Ok(GeneratedField::CustomBaseUrl), - "fileOutputs" | "file_outputs" => Ok(GeneratedField::FileOutputs), - "streamOutputs" | "stream_outputs" => Ok(GeneratedField::StreamOutputs), - "segmentOutputs" | "segment_outputs" => Ok(GeneratedField::SegmentOutputs), - "imageOutputs" | "image_outputs" => Ok(GeneratedField::ImageOutputs), - "file" => Ok(GeneratedField::File), - "stream" => Ok(GeneratedField::Stream), - "segments" => Ok(GeneratedField::Segments), - "preset" => Ok(GeneratedField::Preset), - "advanced" => Ok(GeneratedField::Advanced), + "room" => Ok(GeneratedField::Room), + "identity" => Ok(GeneratedField::Identity), _ => Ok(GeneratedField::__SkipField__), } } @@ -18686,141 +20927,47 @@ impl<'de> serde::Deserialize<'de> for RoomCompositeEgressRequest { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = RoomCompositeEgressRequest; + type Value = RoomParticipantIdentity; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.RoomCompositeEgressRequest") + formatter.write_str("struct livekit.RoomParticipantIdentity") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut room_name__ = None; - let mut layout__ = None; - let mut audio_only__ = None; - let mut video_only__ = None; - let mut custom_base_url__ = None; - let mut file_outputs__ = None; - let mut stream_outputs__ = None; - let mut segment_outputs__ = None; - let mut image_outputs__ = None; - let mut output__ = None; - let mut options__ = None; + let mut room__ = None; + let mut identity__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::RoomName => { - if room_name__.is_some() { - return Err(serde::de::Error::duplicate_field("roomName")); - } - room_name__ = Some(map_.next_value()?); - } - GeneratedField::Layout => { - if layout__.is_some() { - return Err(serde::de::Error::duplicate_field("layout")); - } - layout__ = Some(map_.next_value()?); - } - GeneratedField::AudioOnly => { - if audio_only__.is_some() { - return Err(serde::de::Error::duplicate_field("audioOnly")); - } - audio_only__ = Some(map_.next_value()?); - } - GeneratedField::VideoOnly => { - if video_only__.is_some() { - return Err(serde::de::Error::duplicate_field("videoOnly")); - } - video_only__ = Some(map_.next_value()?); - } - GeneratedField::CustomBaseUrl => { - if custom_base_url__.is_some() { - return Err(serde::de::Error::duplicate_field("customBaseUrl")); - } - custom_base_url__ = Some(map_.next_value()?); - } - GeneratedField::FileOutputs => { - if file_outputs__.is_some() { - return Err(serde::de::Error::duplicate_field("fileOutputs")); - } - file_outputs__ = Some(map_.next_value()?); - } - GeneratedField::StreamOutputs => { - if stream_outputs__.is_some() { - return Err(serde::de::Error::duplicate_field("streamOutputs")); - } - stream_outputs__ = Some(map_.next_value()?); - } - GeneratedField::SegmentOutputs => { - if segment_outputs__.is_some() { - return Err(serde::de::Error::duplicate_field("segmentOutputs")); - } - segment_outputs__ = Some(map_.next_value()?); - } - GeneratedField::ImageOutputs => { - if image_outputs__.is_some() { - return Err(serde::de::Error::duplicate_field("imageOutputs")); - } - image_outputs__ = Some(map_.next_value()?); - } - GeneratedField::File => { - if output__.is_some() { - return Err(serde::de::Error::duplicate_field("file")); - } - output__ = map_.next_value::<::std::option::Option<_>>()?.map(room_composite_egress_request::Output::File) -; - } - GeneratedField::Stream => { - if output__.is_some() { - return Err(serde::de::Error::duplicate_field("stream")); - } - output__ = map_.next_value::<::std::option::Option<_>>()?.map(room_composite_egress_request::Output::Stream) -; - } - GeneratedField::Segments => { - if output__.is_some() { - return Err(serde::de::Error::duplicate_field("segments")); - } - output__ = map_.next_value::<::std::option::Option<_>>()?.map(room_composite_egress_request::Output::Segments) -; - } - GeneratedField::Preset => { - if options__.is_some() { - return Err(serde::de::Error::duplicate_field("preset")); + GeneratedField::Room => { + if room__.is_some() { + return Err(serde::de::Error::duplicate_field("room")); } - options__ = map_.next_value::<::std::option::Option>()?.map(|x| room_composite_egress_request::Options::Preset(x as i32)); + room__ = Some(map_.next_value()?); } - GeneratedField::Advanced => { - if options__.is_some() { - return Err(serde::de::Error::duplicate_field("advanced")); + GeneratedField::Identity => { + if identity__.is_some() { + return Err(serde::de::Error::duplicate_field("identity")); } - options__ = map_.next_value::<::std::option::Option<_>>()?.map(room_composite_egress_request::Options::Advanced) -; + identity__ = Some(map_.next_value()?); } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } } } - Ok(RoomCompositeEgressRequest { - room_name: room_name__.unwrap_or_default(), - layout: layout__.unwrap_or_default(), - audio_only: audio_only__.unwrap_or_default(), - video_only: video_only__.unwrap_or_default(), - custom_base_url: custom_base_url__.unwrap_or_default(), - file_outputs: file_outputs__.unwrap_or_default(), - stream_outputs: stream_outputs__.unwrap_or_default(), - segment_outputs: segment_outputs__.unwrap_or_default(), - image_outputs: image_outputs__.unwrap_or_default(), - output: output__, - options: options__, + Ok(RoomParticipantIdentity { + room: room__.unwrap_or_default(), + identity: identity__.unwrap_or_default(), }) } } - deserializer.deserialize_struct("livekit.RoomCompositeEgressRequest", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.RoomParticipantIdentity", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for RoomConfiguration { +impl serde::Serialize for RoomUpdate { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -18828,99 +20975,125 @@ impl serde::Serialize for RoomConfiguration { { use serde::ser::SerializeStruct; let mut len = 0; - if !self.name.is_empty() { - len += 1; - } - if self.empty_timeout != 0 { - len += 1; - } - if self.departure_timeout != 0 { - len += 1; - } - if self.max_participants != 0 { - len += 1; - } - if self.egress.is_some() { - len += 1; - } - if self.agent.is_some() { - len += 1; - } - if self.min_playout_delay != 0 { - len += 1; - } - if self.max_playout_delay != 0 { - len += 1; - } - if self.sync_streams { + if self.room.is_some() { len += 1; } - let mut struct_ser = serializer.serialize_struct("livekit.RoomConfiguration", len)?; - if !self.name.is_empty() { - struct_ser.serialize_field("name", &self.name)?; - } - if self.empty_timeout != 0 { - struct_ser.serialize_field("emptyTimeout", &self.empty_timeout)?; - } - if self.departure_timeout != 0 { - struct_ser.serialize_field("departureTimeout", &self.departure_timeout)?; - } - if self.max_participants != 0 { - struct_ser.serialize_field("maxParticipants", &self.max_participants)?; + let mut struct_ser = serializer.serialize_struct("livekit.RoomUpdate", len)?; + if let Some(v) = self.room.as_ref() { + struct_ser.serialize_field("room", v)?; } - if let Some(v) = self.egress.as_ref() { - struct_ser.serialize_field("egress", v)?; + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for RoomUpdate { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "room", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Room, + __SkipField__, } - if let Some(v) = self.agent.as_ref() { - struct_ser.serialize_field("agent", v)?; + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "room" => Ok(GeneratedField::Room), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } } - if self.min_playout_delay != 0 { - struct_ser.serialize_field("minPlayoutDelay", &self.min_playout_delay)?; + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = RoomUpdate; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.RoomUpdate") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut room__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Room => { + if room__.is_some() { + return Err(serde::de::Error::duplicate_field("room")); + } + room__ = map_.next_value()?; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(RoomUpdate { + room: room__, + }) + } } - if self.max_playout_delay != 0 { - struct_ser.serialize_field("maxPlayoutDelay", &self.max_playout_delay)?; + deserializer.deserialize_struct("livekit.RoomUpdate", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for RpcAck { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.request_id.is_empty() { + len += 1; } - if self.sync_streams { - struct_ser.serialize_field("syncStreams", &self.sync_streams)?; + let mut struct_ser = serializer.serialize_struct("livekit.RpcAck", len)?; + if !self.request_id.is_empty() { + struct_ser.serialize_field("requestId", &self.request_id)?; } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for RoomConfiguration { +impl<'de> serde::Deserialize<'de> for RpcAck { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "name", - "empty_timeout", - "emptyTimeout", - "departure_timeout", - "departureTimeout", - "max_participants", - "maxParticipants", - "egress", - "agent", - "min_playout_delay", - "minPlayoutDelay", - "max_playout_delay", - "maxPlayoutDelay", - "sync_streams", - "syncStreams", + "request_id", + "requestId", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { - Name, - EmptyTimeout, - DepartureTimeout, - MaxParticipants, - Egress, - Agent, - MinPlayoutDelay, - MaxPlayoutDelay, - SyncStreams, + RequestId, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -18943,15 +21116,7 @@ impl<'de> serde::Deserialize<'de> for RoomConfiguration { E: serde::de::Error, { match value { - "name" => Ok(GeneratedField::Name), - "emptyTimeout" | "empty_timeout" => Ok(GeneratedField::EmptyTimeout), - "departureTimeout" | "departure_timeout" => Ok(GeneratedField::DepartureTimeout), - "maxParticipants" | "max_participants" => Ok(GeneratedField::MaxParticipants), - "egress" => Ok(GeneratedField::Egress), - "agent" => Ok(GeneratedField::Agent), - "minPlayoutDelay" | "min_playout_delay" => Ok(GeneratedField::MinPlayoutDelay), - "maxPlayoutDelay" | "max_playout_delay" => Ok(GeneratedField::MaxPlayoutDelay), - "syncStreams" | "sync_streams" => Ok(GeneratedField::SyncStreams), + "requestId" | "request_id" => Ok(GeneratedField::RequestId), _ => Ok(GeneratedField::__SkipField__), } } @@ -18961,113 +21126,39 @@ impl<'de> serde::Deserialize<'de> for RoomConfiguration { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = RoomConfiguration; + type Value = RpcAck; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.RoomConfiguration") + formatter.write_str("struct livekit.RpcAck") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut name__ = None; - let mut empty_timeout__ = None; - let mut departure_timeout__ = None; - let mut max_participants__ = None; - let mut egress__ = None; - let mut agent__ = None; - let mut min_playout_delay__ = None; - let mut max_playout_delay__ = None; - let mut sync_streams__ = None; + let mut request_id__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::Name => { - if name__.is_some() { - return Err(serde::de::Error::duplicate_field("name")); - } - name__ = Some(map_.next_value()?); - } - GeneratedField::EmptyTimeout => { - if empty_timeout__.is_some() { - return Err(serde::de::Error::duplicate_field("emptyTimeout")); - } - empty_timeout__ = - Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; - } - GeneratedField::DepartureTimeout => { - if departure_timeout__.is_some() { - return Err(serde::de::Error::duplicate_field("departureTimeout")); - } - departure_timeout__ = - Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; - } - GeneratedField::MaxParticipants => { - if max_participants__.is_some() { - return Err(serde::de::Error::duplicate_field("maxParticipants")); - } - max_participants__ = - Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; - } - GeneratedField::Egress => { - if egress__.is_some() { - return Err(serde::de::Error::duplicate_field("egress")); - } - egress__ = map_.next_value()?; - } - GeneratedField::Agent => { - if agent__.is_some() { - return Err(serde::de::Error::duplicate_field("agent")); - } - agent__ = map_.next_value()?; - } - GeneratedField::MinPlayoutDelay => { - if min_playout_delay__.is_some() { - return Err(serde::de::Error::duplicate_field("minPlayoutDelay")); - } - min_playout_delay__ = - Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; - } - GeneratedField::MaxPlayoutDelay => { - if max_playout_delay__.is_some() { - return Err(serde::de::Error::duplicate_field("maxPlayoutDelay")); - } - max_playout_delay__ = - Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; - } - GeneratedField::SyncStreams => { - if sync_streams__.is_some() { - return Err(serde::de::Error::duplicate_field("syncStreams")); + GeneratedField::RequestId => { + if request_id__.is_some() { + return Err(serde::de::Error::duplicate_field("requestId")); } - sync_streams__ = Some(map_.next_value()?); + request_id__ = Some(map_.next_value()?); } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } } } - Ok(RoomConfiguration { - name: name__.unwrap_or_default(), - empty_timeout: empty_timeout__.unwrap_or_default(), - departure_timeout: departure_timeout__.unwrap_or_default(), - max_participants: max_participants__.unwrap_or_default(), - egress: egress__, - agent: agent__, - min_playout_delay: min_playout_delay__.unwrap_or_default(), - max_playout_delay: max_playout_delay__.unwrap_or_default(), - sync_streams: sync_streams__.unwrap_or_default(), + Ok(RpcAck { + request_id: request_id__.unwrap_or_default(), }) } } - deserializer.deserialize_struct("livekit.RoomConfiguration", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.RpcAck", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for RoomEgress { +impl serde::Serialize for RpcError { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -19075,45 +21166,45 @@ impl serde::Serialize for RoomEgress { { use serde::ser::SerializeStruct; let mut len = 0; - if self.room.is_some() { + if self.code != 0 { len += 1; } - if self.participant.is_some() { + if !self.message.is_empty() { len += 1; } - if self.tracks.is_some() { + if !self.data.is_empty() { len += 1; } - let mut struct_ser = serializer.serialize_struct("livekit.RoomEgress", len)?; - if let Some(v) = self.room.as_ref() { - struct_ser.serialize_field("room", v)?; + let mut struct_ser = serializer.serialize_struct("livekit.RpcError", len)?; + if self.code != 0 { + struct_ser.serialize_field("code", &self.code)?; } - if let Some(v) = self.participant.as_ref() { - struct_ser.serialize_field("participant", v)?; + if !self.message.is_empty() { + struct_ser.serialize_field("message", &self.message)?; } - if let Some(v) = self.tracks.as_ref() { - struct_ser.serialize_field("tracks", v)?; + if !self.data.is_empty() { + struct_ser.serialize_field("data", &self.data)?; } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for RoomEgress { +impl<'de> serde::Deserialize<'de> for RpcError { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "room", - "participant", - "tracks", + "code", + "message", + "data", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { - Room, - Participant, - Tracks, + Code, + Message, + Data, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -19136,9 +21227,9 @@ impl<'de> serde::Deserialize<'de> for RoomEgress { E: serde::de::Error, { match value { - "room" => Ok(GeneratedField::Room), - "participant" => Ok(GeneratedField::Participant), - "tracks" => Ok(GeneratedField::Tracks), + "code" => Ok(GeneratedField::Code), + "message" => Ok(GeneratedField::Message), + "data" => Ok(GeneratedField::Data), _ => Ok(GeneratedField::__SkipField__), } } @@ -19148,55 +21239,57 @@ impl<'de> serde::Deserialize<'de> for RoomEgress { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = RoomEgress; + type Value = RpcError; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.RoomEgress") + formatter.write_str("struct livekit.RpcError") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut room__ = None; - let mut participant__ = None; - let mut tracks__ = None; + let mut code__ = None; + let mut message__ = None; + let mut data__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::Room => { - if room__.is_some() { - return Err(serde::de::Error::duplicate_field("room")); + GeneratedField::Code => { + if code__.is_some() { + return Err(serde::de::Error::duplicate_field("code")); } - room__ = map_.next_value()?; + code__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; } - GeneratedField::Participant => { - if participant__.is_some() { - return Err(serde::de::Error::duplicate_field("participant")); + GeneratedField::Message => { + if message__.is_some() { + return Err(serde::de::Error::duplicate_field("message")); } - participant__ = map_.next_value()?; + message__ = Some(map_.next_value()?); } - GeneratedField::Tracks => { - if tracks__.is_some() { - return Err(serde::de::Error::duplicate_field("tracks")); + GeneratedField::Data => { + if data__.is_some() { + return Err(serde::de::Error::duplicate_field("data")); } - tracks__ = map_.next_value()?; + data__ = Some(map_.next_value()?); } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } } } - Ok(RoomEgress { - room: room__, - participant: participant__, - tracks: tracks__, + Ok(RpcError { + code: code__.unwrap_or_default(), + message: message__.unwrap_or_default(), + data: data__.unwrap_or_default(), }) } } - deserializer.deserialize_struct("livekit.RoomEgress", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.RpcError", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for RoomParticipantIdentity { +impl serde::Serialize for RpcRequest { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -19204,37 +21297,62 @@ impl serde::Serialize for RoomParticipantIdentity { { use serde::ser::SerializeStruct; let mut len = 0; - if !self.room.is_empty() { + if !self.id.is_empty() { len += 1; } - if !self.identity.is_empty() { + if !self.method.is_empty() { len += 1; } - let mut struct_ser = serializer.serialize_struct("livekit.RoomParticipantIdentity", len)?; - if !self.room.is_empty() { - struct_ser.serialize_field("room", &self.room)?; + if !self.payload.is_empty() { + len += 1; + } + if self.response_timeout_ms != 0 { + len += 1; + } + if self.version != 0 { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.RpcRequest", len)?; + if !self.id.is_empty() { + struct_ser.serialize_field("id", &self.id)?; + } + if !self.method.is_empty() { + struct_ser.serialize_field("method", &self.method)?; + } + if !self.payload.is_empty() { + struct_ser.serialize_field("payload", &self.payload)?; + } + if self.response_timeout_ms != 0 { + struct_ser.serialize_field("responseTimeoutMs", &self.response_timeout_ms)?; } - if !self.identity.is_empty() { - struct_ser.serialize_field("identity", &self.identity)?; + if self.version != 0 { + struct_ser.serialize_field("version", &self.version)?; } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for RoomParticipantIdentity { +impl<'de> serde::Deserialize<'de> for RpcRequest { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "room", - "identity", + "id", + "method", + "payload", + "response_timeout_ms", + "responseTimeoutMs", + "version", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { - Room, - Identity, + Id, + Method, + Payload, + ResponseTimeoutMs, + Version, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -19257,8 +21375,11 @@ impl<'de> serde::Deserialize<'de> for RoomParticipantIdentity { E: serde::de::Error, { match value { - "room" => Ok(GeneratedField::Room), - "identity" => Ok(GeneratedField::Identity), + "id" => Ok(GeneratedField::Id), + "method" => Ok(GeneratedField::Method), + "payload" => Ok(GeneratedField::Payload), + "responseTimeoutMs" | "response_timeout_ms" => Ok(GeneratedField::ResponseTimeoutMs), + "version" => Ok(GeneratedField::Version), _ => Ok(GeneratedField::__SkipField__), } } @@ -19268,47 +21389,75 @@ impl<'de> serde::Deserialize<'de> for RoomParticipantIdentity { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = RoomParticipantIdentity; + type Value = RpcRequest; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.RoomParticipantIdentity") + formatter.write_str("struct livekit.RpcRequest") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut room__ = None; - let mut identity__ = None; + let mut id__ = None; + let mut method__ = None; + let mut payload__ = None; + let mut response_timeout_ms__ = None; + let mut version__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::Room => { - if room__.is_some() { - return Err(serde::de::Error::duplicate_field("room")); + GeneratedField::Id => { + if id__.is_some() { + return Err(serde::de::Error::duplicate_field("id")); } - room__ = Some(map_.next_value()?); + id__ = Some(map_.next_value()?); } - GeneratedField::Identity => { - if identity__.is_some() { - return Err(serde::de::Error::duplicate_field("identity")); + GeneratedField::Method => { + if method__.is_some() { + return Err(serde::de::Error::duplicate_field("method")); } - identity__ = Some(map_.next_value()?); + method__ = Some(map_.next_value()?); + } + GeneratedField::Payload => { + if payload__.is_some() { + return Err(serde::de::Error::duplicate_field("payload")); + } + payload__ = Some(map_.next_value()?); + } + GeneratedField::ResponseTimeoutMs => { + if response_timeout_ms__.is_some() { + return Err(serde::de::Error::duplicate_field("responseTimeoutMs")); + } + response_timeout_ms__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::Version => { + if version__.is_some() { + return Err(serde::de::Error::duplicate_field("version")); + } + version__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } } } - Ok(RoomParticipantIdentity { - room: room__.unwrap_or_default(), - identity: identity__.unwrap_or_default(), + Ok(RpcRequest { + id: id__.unwrap_or_default(), + method: method__.unwrap_or_default(), + payload: payload__.unwrap_or_default(), + response_timeout_ms: response_timeout_ms__.unwrap_or_default(), + version: version__.unwrap_or_default(), }) } } - deserializer.deserialize_struct("livekit.RoomParticipantIdentity", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.RpcRequest", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for RoomUpdate { +impl serde::Serialize for RpcResponse { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -19316,29 +21465,47 @@ impl serde::Serialize for RoomUpdate { { use serde::ser::SerializeStruct; let mut len = 0; - if self.room.is_some() { + if !self.request_id.is_empty() { len += 1; } - let mut struct_ser = serializer.serialize_struct("livekit.RoomUpdate", len)?; - if let Some(v) = self.room.as_ref() { - struct_ser.serialize_field("room", v)?; + if self.value.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.RpcResponse", len)?; + if !self.request_id.is_empty() { + struct_ser.serialize_field("requestId", &self.request_id)?; + } + if let Some(v) = self.value.as_ref() { + match v { + rpc_response::Value::Payload(v) => { + struct_ser.serialize_field("payload", v)?; + } + rpc_response::Value::Error(v) => { + struct_ser.serialize_field("error", v)?; + } + } } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for RoomUpdate { +impl<'de> serde::Deserialize<'de> for RpcResponse { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "room", + "request_id", + "requestId", + "payload", + "error", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { - Room, + RequestId, + Payload, + Error, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -19361,7 +21528,9 @@ impl<'de> serde::Deserialize<'de> for RoomUpdate { E: serde::de::Error, { match value { - "room" => Ok(GeneratedField::Room), + "requestId" | "request_id" => Ok(GeneratedField::RequestId), + "payload" => Ok(GeneratedField::Payload), + "error" => Ok(GeneratedField::Error), _ => Ok(GeneratedField::__SkipField__), } } @@ -19371,36 +21540,51 @@ impl<'de> serde::Deserialize<'de> for RoomUpdate { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = RoomUpdate; + type Value = RpcResponse; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.RoomUpdate") + formatter.write_str("struct livekit.RpcResponse") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut room__ = None; + let mut request_id__ = None; + let mut value__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::Room => { - if room__.is_some() { - return Err(serde::de::Error::duplicate_field("room")); + GeneratedField::RequestId => { + if request_id__.is_some() { + return Err(serde::de::Error::duplicate_field("requestId")); } - room__ = map_.next_value()?; + request_id__ = Some(map_.next_value()?); + } + GeneratedField::Payload => { + if value__.is_some() { + return Err(serde::de::Error::duplicate_field("payload")); + } + value__ = map_.next_value::<::std::option::Option<_>>()?.map(rpc_response::Value::Payload); + } + GeneratedField::Error => { + if value__.is_some() { + return Err(serde::de::Error::duplicate_field("error")); + } + value__ = map_.next_value::<::std::option::Option<_>>()?.map(rpc_response::Value::Error) +; } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } } } - Ok(RoomUpdate { - room: room__, + Ok(RpcResponse { + request_id: request_id__.unwrap_or_default(), + value: value__, }) } } - deserializer.deserialize_struct("livekit.RoomUpdate", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.RpcResponse", FIELDS, GeneratedVisitor) } } impl serde::Serialize for S3Upload { @@ -19694,6 +21878,9 @@ impl serde::Serialize for SipDispatchRule { sip_dispatch_rule::Rule::DispatchRuleIndividual(v) => { struct_ser.serialize_field("dispatchRuleIndividual", v)?; } + sip_dispatch_rule::Rule::DispatchRuleCallee(v) => { + struct_ser.serialize_field("dispatchRuleCallee", v)?; + } } } struct_ser.end() @@ -19710,12 +21897,15 @@ impl<'de> serde::Deserialize<'de> for SipDispatchRule { "dispatchRuleDirect", "dispatch_rule_individual", "dispatchRuleIndividual", + "dispatch_rule_callee", + "dispatchRuleCallee", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { DispatchRuleDirect, DispatchRuleIndividual, + DispatchRuleCallee, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -19740,6 +21930,7 @@ impl<'de> serde::Deserialize<'de> for SipDispatchRule { match value { "dispatchRuleDirect" | "dispatch_rule_direct" => Ok(GeneratedField::DispatchRuleDirect), "dispatchRuleIndividual" | "dispatch_rule_individual" => Ok(GeneratedField::DispatchRuleIndividual), + "dispatchRuleCallee" | "dispatch_rule_callee" => Ok(GeneratedField::DispatchRuleCallee), _ => Ok(GeneratedField::__SkipField__), } } @@ -19774,6 +21965,13 @@ impl<'de> serde::Deserialize<'de> for SipDispatchRule { return Err(serde::de::Error::duplicate_field("dispatchRuleIndividual")); } rule__ = map_.next_value::<::std::option::Option<_>>()?.map(sip_dispatch_rule::Rule::DispatchRuleIndividual) +; + } + GeneratedField::DispatchRuleCallee => { + if rule__.is_some() { + return Err(serde::de::Error::duplicate_field("dispatchRuleCallee")); + } + rule__ = map_.next_value::<::std::option::Option<_>>()?.map(sip_dispatch_rule::Rule::DispatchRuleCallee) ; } GeneratedField::__SkipField__ => { @@ -19789,6 +21987,136 @@ impl<'de> serde::Deserialize<'de> for SipDispatchRule { deserializer.deserialize_struct("livekit.SIPDispatchRule", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for SipDispatchRuleCallee { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.room_prefix.is_empty() { + len += 1; + } + if !self.pin.is_empty() { + len += 1; + } + if self.randomize { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.SIPDispatchRuleCallee", len)?; + if !self.room_prefix.is_empty() { + struct_ser.serialize_field("roomPrefix", &self.room_prefix)?; + } + if !self.pin.is_empty() { + struct_ser.serialize_field("pin", &self.pin)?; + } + if self.randomize { + struct_ser.serialize_field("randomize", &self.randomize)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for SipDispatchRuleCallee { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "room_prefix", + "roomPrefix", + "pin", + "randomize", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + RoomPrefix, + Pin, + Randomize, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "roomPrefix" | "room_prefix" => Ok(GeneratedField::RoomPrefix), + "pin" => Ok(GeneratedField::Pin), + "randomize" => Ok(GeneratedField::Randomize), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = SipDispatchRuleCallee; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.SIPDispatchRuleCallee") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut room_prefix__ = None; + let mut pin__ = None; + let mut randomize__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::RoomPrefix => { + if room_prefix__.is_some() { + return Err(serde::de::Error::duplicate_field("roomPrefix")); + } + room_prefix__ = Some(map_.next_value()?); + } + GeneratedField::Pin => { + if pin__.is_some() { + return Err(serde::de::Error::duplicate_field("pin")); + } + pin__ = Some(map_.next_value()?); + } + GeneratedField::Randomize => { + if randomize__.is_some() { + return Err(serde::de::Error::duplicate_field("randomize")); + } + randomize__ = Some(map_.next_value()?); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(SipDispatchRuleCallee { + room_prefix: room_prefix__.unwrap_or_default(), + pin: pin__.unwrap_or_default(), + randomize: randomize__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.SIPDispatchRuleCallee", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for SipDispatchRuleDirect { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -20267,6 +22595,12 @@ impl serde::Serialize for SipInboundTrunkInfo { if !self.auth_password.is_empty() { len += 1; } + if !self.headers.is_empty() { + len += 1; + } + if !self.headers_to_attributes.is_empty() { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.SIPInboundTrunkInfo", len)?; if !self.sip_trunk_id.is_empty() { struct_ser.serialize_field("sipTrunkId", &self.sip_trunk_id)?; @@ -20292,6 +22626,12 @@ impl serde::Serialize for SipInboundTrunkInfo { if !self.auth_password.is_empty() { struct_ser.serialize_field("authPassword", &self.auth_password)?; } + if !self.headers.is_empty() { + struct_ser.serialize_field("headers", &self.headers)?; + } + if !self.headers_to_attributes.is_empty() { + struct_ser.serialize_field("headersToAttributes", &self.headers_to_attributes)?; + } struct_ser.end() } } @@ -20315,6 +22655,9 @@ impl<'de> serde::Deserialize<'de> for SipInboundTrunkInfo { "authUsername", "auth_password", "authPassword", + "headers", + "headers_to_attributes", + "headersToAttributes", ]; #[allow(clippy::enum_variant_names)] @@ -20327,6 +22670,8 @@ impl<'de> serde::Deserialize<'de> for SipInboundTrunkInfo { AllowedNumbers, AuthUsername, AuthPassword, + Headers, + HeadersToAttributes, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -20357,6 +22702,8 @@ impl<'de> serde::Deserialize<'de> for SipInboundTrunkInfo { "allowedNumbers" | "allowed_numbers" => Ok(GeneratedField::AllowedNumbers), "authUsername" | "auth_username" => Ok(GeneratedField::AuthUsername), "authPassword" | "auth_password" => Ok(GeneratedField::AuthPassword), + "headers" => Ok(GeneratedField::Headers), + "headersToAttributes" | "headers_to_attributes" => Ok(GeneratedField::HeadersToAttributes), _ => Ok(GeneratedField::__SkipField__), } } @@ -20384,6 +22731,8 @@ impl<'de> serde::Deserialize<'de> for SipInboundTrunkInfo { let mut allowed_numbers__ = None; let mut auth_username__ = None; let mut auth_password__ = None; + let mut headers__ = None; + let mut headers_to_attributes__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::SipTrunkId => { @@ -20434,6 +22783,22 @@ impl<'de> serde::Deserialize<'de> for SipInboundTrunkInfo { } auth_password__ = Some(map_.next_value()?); } + GeneratedField::Headers => { + if headers__.is_some() { + return Err(serde::de::Error::duplicate_field("headers")); + } + headers__ = Some( + map_.next_value::>()? + ); + } + GeneratedField::HeadersToAttributes => { + if headers_to_attributes__.is_some() { + return Err(serde::de::Error::duplicate_field("headersToAttributes")); + } + headers_to_attributes__ = Some( + map_.next_value::>()? + ); + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -20448,6 +22813,8 @@ impl<'de> serde::Deserialize<'de> for SipInboundTrunkInfo { allowed_numbers: allowed_numbers__.unwrap_or_default(), auth_username: auth_username__.unwrap_or_default(), auth_password: auth_password__.unwrap_or_default(), + headers: headers__.unwrap_or_default(), + headers_to_attributes: headers_to_attributes__.unwrap_or_default(), }) } } @@ -20480,10 +22847,16 @@ impl serde::Serialize for SipOutboundTrunkInfo { if !self.numbers.is_empty() { len += 1; } - if !self.auth_username.is_empty() { + if !self.auth_username.is_empty() { + len += 1; + } + if !self.auth_password.is_empty() { + len += 1; + } + if !self.headers.is_empty() { len += 1; } - if !self.auth_password.is_empty() { + if !self.headers_to_attributes.is_empty() { len += 1; } let mut struct_ser = serializer.serialize_struct("livekit.SIPOutboundTrunkInfo", len)?; @@ -20513,6 +22886,12 @@ impl serde::Serialize for SipOutboundTrunkInfo { if !self.auth_password.is_empty() { struct_ser.serialize_field("authPassword", &self.auth_password)?; } + if !self.headers.is_empty() { + struct_ser.serialize_field("headers", &self.headers)?; + } + if !self.headers_to_attributes.is_empty() { + struct_ser.serialize_field("headersToAttributes", &self.headers_to_attributes)?; + } struct_ser.end() } } @@ -20534,6 +22913,9 @@ impl<'de> serde::Deserialize<'de> for SipOutboundTrunkInfo { "authUsername", "auth_password", "authPassword", + "headers", + "headers_to_attributes", + "headersToAttributes", ]; #[allow(clippy::enum_variant_names)] @@ -20546,6 +22928,8 @@ impl<'de> serde::Deserialize<'de> for SipOutboundTrunkInfo { Numbers, AuthUsername, AuthPassword, + Headers, + HeadersToAttributes, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -20576,6 +22960,8 @@ impl<'de> serde::Deserialize<'de> for SipOutboundTrunkInfo { "numbers" => Ok(GeneratedField::Numbers), "authUsername" | "auth_username" => Ok(GeneratedField::AuthUsername), "authPassword" | "auth_password" => Ok(GeneratedField::AuthPassword), + "headers" => Ok(GeneratedField::Headers), + "headersToAttributes" | "headers_to_attributes" => Ok(GeneratedField::HeadersToAttributes), _ => Ok(GeneratedField::__SkipField__), } } @@ -20603,6 +22989,8 @@ impl<'de> serde::Deserialize<'de> for SipOutboundTrunkInfo { let mut numbers__ = None; let mut auth_username__ = None; let mut auth_password__ = None; + let mut headers__ = None; + let mut headers_to_attributes__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::SipTrunkId => { @@ -20653,6 +23041,22 @@ impl<'de> serde::Deserialize<'de> for SipOutboundTrunkInfo { } auth_password__ = Some(map_.next_value()?); } + GeneratedField::Headers => { + if headers__.is_some() { + return Err(serde::de::Error::duplicate_field("headers")); + } + headers__ = Some( + map_.next_value::>()? + ); + } + GeneratedField::HeadersToAttributes => { + if headers_to_attributes__.is_some() { + return Err(serde::de::Error::duplicate_field("headersToAttributes")); + } + headers_to_attributes__ = Some( + map_.next_value::>()? + ); + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -20667,6 +23071,8 @@ impl<'de> serde::Deserialize<'de> for SipOutboundTrunkInfo { numbers: numbers__.unwrap_or_default(), auth_username: auth_username__.unwrap_or_default(), auth_password: auth_password__.unwrap_or_default(), + headers: headers__.unwrap_or_default(), + headers_to_attributes: headers_to_attributes__.unwrap_or_default(), }) } } @@ -26411,6 +28817,179 @@ impl<'de> serde::Deserialize<'de> for SyncState { deserializer.deserialize_struct("livekit.SyncState", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for TimeSeriesMetric { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.label != 0 { + len += 1; + } + if self.participant_identity != 0 { + len += 1; + } + if self.track_sid != 0 { + len += 1; + } + if !self.samples.is_empty() { + len += 1; + } + if self.rid != 0 { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.TimeSeriesMetric", len)?; + if self.label != 0 { + struct_ser.serialize_field("label", &self.label)?; + } + if self.participant_identity != 0 { + struct_ser.serialize_field("participantIdentity", &self.participant_identity)?; + } + if self.track_sid != 0 { + struct_ser.serialize_field("trackSid", &self.track_sid)?; + } + if !self.samples.is_empty() { + struct_ser.serialize_field("samples", &self.samples)?; + } + if self.rid != 0 { + struct_ser.serialize_field("rid", &self.rid)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for TimeSeriesMetric { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "label", + "participant_identity", + "participantIdentity", + "track_sid", + "trackSid", + "samples", + "rid", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Label, + ParticipantIdentity, + TrackSid, + Samples, + Rid, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "label" => Ok(GeneratedField::Label), + "participantIdentity" | "participant_identity" => Ok(GeneratedField::ParticipantIdentity), + "trackSid" | "track_sid" => Ok(GeneratedField::TrackSid), + "samples" => Ok(GeneratedField::Samples), + "rid" => Ok(GeneratedField::Rid), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = TimeSeriesMetric; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.TimeSeriesMetric") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut label__ = None; + let mut participant_identity__ = None; + let mut track_sid__ = None; + let mut samples__ = None; + let mut rid__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Label => { + if label__.is_some() { + return Err(serde::de::Error::duplicate_field("label")); + } + label__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::ParticipantIdentity => { + if participant_identity__.is_some() { + return Err(serde::de::Error::duplicate_field("participantIdentity")); + } + participant_identity__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::TrackSid => { + if track_sid__.is_some() { + return Err(serde::de::Error::duplicate_field("trackSid")); + } + track_sid__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::Samples => { + if samples__.is_some() { + return Err(serde::de::Error::duplicate_field("samples")); + } + samples__ = Some(map_.next_value()?); + } + GeneratedField::Rid => { + if rid__.is_some() { + return Err(serde::de::Error::duplicate_field("rid")); + } + rid__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(TimeSeriesMetric { + label: label__.unwrap_or_default(), + participant_identity: participant_identity__.unwrap_or_default(), + track_sid: track_sid__.unwrap_or_default(), + samples: samples__.unwrap_or_default(), + rid: rid__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.TimeSeriesMetric", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for TimedVersion { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -28315,6 +30894,138 @@ impl<'de> serde::Deserialize<'de> for TranscriptionSegment { deserializer.deserialize_struct("livekit.TranscriptionSegment", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for TransferSipParticipantRequest { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.participant_identity.is_empty() { + len += 1; + } + if !self.room_name.is_empty() { + len += 1; + } + if !self.transfer_to.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.TransferSIPParticipantRequest", len)?; + if !self.participant_identity.is_empty() { + struct_ser.serialize_field("participantIdentity", &self.participant_identity)?; + } + if !self.room_name.is_empty() { + struct_ser.serialize_field("roomName", &self.room_name)?; + } + if !self.transfer_to.is_empty() { + struct_ser.serialize_field("transferTo", &self.transfer_to)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for TransferSipParticipantRequest { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "participant_identity", + "participantIdentity", + "room_name", + "roomName", + "transfer_to", + "transferTo", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + ParticipantIdentity, + RoomName, + TransferTo, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "participantIdentity" | "participant_identity" => Ok(GeneratedField::ParticipantIdentity), + "roomName" | "room_name" => Ok(GeneratedField::RoomName), + "transferTo" | "transfer_to" => Ok(GeneratedField::TransferTo), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = TransferSipParticipantRequest; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.TransferSIPParticipantRequest") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut participant_identity__ = None; + let mut room_name__ = None; + let mut transfer_to__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::ParticipantIdentity => { + if participant_identity__.is_some() { + return Err(serde::de::Error::duplicate_field("participantIdentity")); + } + participant_identity__ = Some(map_.next_value()?); + } + GeneratedField::RoomName => { + if room_name__.is_some() { + return Err(serde::de::Error::duplicate_field("roomName")); + } + room_name__ = Some(map_.next_value()?); + } + GeneratedField::TransferTo => { + if transfer_to__.is_some() { + return Err(serde::de::Error::duplicate_field("transferTo")); + } + transfer_to__ = Some(map_.next_value()?); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(TransferSipParticipantRequest { + participant_identity: participant_identity__.unwrap_or_default(), + room_name: room_name__.unwrap_or_default(), + transfer_to: transfer_to__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.TransferSIPParticipantRequest", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for TrickleRequest { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result diff --git a/livekit/src/rtc_engine/rtc_session.rs b/livekit/src/rtc_engine/rtc_session.rs index 16dcf4e6d..100ef8390 100644 --- a/livekit/src/rtc_engine/rtc_session.rs +++ b/livekit/src/rtc_engine/rtc_session.rs @@ -657,6 +657,7 @@ impl SessionInner { segments, }); } + _ => {} } } } diff --git a/soxr-sys/src/lib.rs b/soxr-sys/src/lib.rs index e8f775907..545f21c78 100644 --- a/soxr-sys/src/lib.rs +++ b/soxr-sys/src/lib.rs @@ -6,6 +6,8 @@ include!("soxr.rs"); #[cfg(test)] mod tests { use super::*; + use std::fs::File; + use std::io::{Read, Seek, SeekFrom}; #[test] fn it_works() { @@ -24,14 +26,40 @@ mod tests { use hound::{WavReader, WavSpec, WavWriter}; - let input_wav_path = "input.wav"; - let output_wav_path = "output.wav"; + let input_wav_path = "test-input.wav"; + let output_wav_path = "test-output.wav"; + + let input_spec = WavSpec { + channels: 1, + sample_rate: 44100, + bits_per_sample: 16, + sample_format: hound::SampleFormat::Int, + }; + + let output_spec = WavSpec { + channels: 1, + sample_rate: 24000, + bits_per_sample: 16, + sample_format: hound::SampleFormat::Int, + }; + + let input_duration: u32 = 3; // 3 second of audio + + let mut writer = WavWriter::create(input_wav_path, input_spec) + .expect("Failed to create test input WAV file"); + for t in (0..input_spec.sample_rate * input_duration) + .map(|x| x as f32 / input_spec.sample_rate as f32) + { + let a4note = 440.0; // 440Hz = A4 + let sample = (t * a4note * 2.0 * std::f32::consts::PI).sin(); + let amplitude = i16::MAX as f32; + writer.write_sample((sample * amplitude) as i16).expect("Failed to write sample"); + } + writer.finalize().expect("Failed to finalize test input WAV file"); let mut reader = WavReader::open(input_wav_path).expect("Failed to open input WAV file"); let wav_spec = reader.spec(); - let input_rate = wav_spec.sample_rate as f64; - let output_rate = 24000.0; let num_channels = wav_spec.channels as u32; @@ -39,8 +67,9 @@ mod tests { reader.samples::().map(|s| s.expect("Failed to read sample")).collect(); let buf_total_len = samples.len(); - let olen = - ((output_rate * buf_total_len as f64) / (input_rate + output_rate) + 0.5) as usize; + let olen = ((output_spec.sample_rate as f64 * buf_total_len as f64) + / (input_spec.sample_rate as f64 + output_spec.sample_rate as f64) + + 0.5) as usize; let ilen = buf_total_len - olen; let mut obuf = vec![0i16; olen]; @@ -51,8 +80,8 @@ mod tests { let mut error: soxr_error_t = ptr::null(); let io_spec = soxr_io_spec { - itype: SOXR_INT16_I as u32, - otype: SOXR_INT16_I as u32, + itype: soxr_datatype_t_SOXR_INT16_I as u32, + otype: soxr_datatype_t_SOXR_INT16_I as u32, scale: 1.0, e: ptr::null_mut(), flags: 0, @@ -60,8 +89,8 @@ mod tests { let soxr = unsafe { soxr_create( - input_rate, - output_rate, + input_spec.sample_rate as f64, + output_spec.sample_rate as f64, num_channels, &mut error, &io_spec, @@ -121,15 +150,8 @@ mod tests { need_input = (odone < olen) && ibuf.is_some(); } - let spec = WavSpec { - channels: wav_spec.channels, - sample_rate: output_rate as u32, - bits_per_sample: 16, - sample_format: hound::SampleFormat::Int, - }; - - let mut writer = - WavWriter::create(output_wav_path, spec).expect("Failed to create output WAV file"); + let mut writer = WavWriter::create(output_wav_path, output_spec) + .expect("Failed to create output WAV file"); for sample in output_samples { writer.write_sample(sample).expect("Failed to write sample"); @@ -146,5 +168,8 @@ mod tests { unsafe { soxr_delete(soxr); } + + std::fs::remove_file(input_wav_path).expect("Failed to remove test input file"); + std::fs::remove_file(output_wav_path).expect("Failed to remove test output file"); } } From ef99aad425280d84f446d7cdb46e1e4f487bf10b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Fri, 4 Oct 2024 14:28:50 -0700 Subject: [PATCH 056/274] don't drop handles on dispose (#460) * Update mod.rs * Update mod.rs --- livekit-ffi/src/server/mod.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/livekit-ffi/src/server/mod.rs b/livekit-ffi/src/server/mod.rs index 725c9e961..a9ef15152 100644 --- a/livekit-ffi/src/server/mod.rs +++ b/livekit-ffi/src/server/mod.rs @@ -136,7 +136,6 @@ impl FfiServer { pub async fn dispose(&self) { self.logger.set_capture_logs(false); - log::info!("disposing ffi server"); // Close all rooms @@ -152,8 +151,6 @@ impl FfiServer { } // Drop all handles - self.ffi_handles.clear(); - self.handle_dropped_txs.clear(); *self.config.lock() = None; // Invalidate the config } From 82425709a129f0db281c37d597949042fd4933f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Wed, 9 Oct 2024 13:03:34 -0700 Subject: [PATCH 057/274] ffi 0.11.4 (#468) --- livekit-ffi/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 5f957816d..97b8a2885 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.11.3" +version = "0.11.4" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" From 9378fae40d07651dcf64566edd54588c18aed849 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 9 Oct 2024 23:05:28 +0300 Subject: [PATCH 058/274] Avoid extra dylib dependencies (#466) Neither X or GL related libraries are needed for livekit sdk to work in the client code. --- webrtc-sys/build.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/webrtc-sys/build.rs b/webrtc-sys/build.rs index 5e87b0c9c..66380274b 100644 --- a/webrtc-sys/build.rs +++ b/webrtc-sys/build.rs @@ -126,9 +126,6 @@ fn main() { builder.flag("/std:c++20").flag("/EHsc"); } "linux" => { - println!("cargo:rustc-link-lib=dylib=Xext"); - println!("cargo:rustc-link-lib=dylib=X11"); - println!("cargo:rustc-link-lib=dylib=GL"); println!("cargo:rustc-link-lib=dylib=rt"); println!("cargo:rustc-link-lib=dylib=dl"); println!("cargo:rustc-link-lib=dylib=pthread"); From 7044ba5a02561cf650143e76dc2d6780dacb46e6 Mon Sep 17 00:00:00 2001 From: lukasIO Date: Thu, 10 Oct 2024 12:58:10 +0200 Subject: [PATCH 059/274] Add chat API (#436) * Add chat APIs - wip * generated protobuf * updates * fix types and build * Add conversion traits and update API * fix unwrapping * Populate timestamps and add edit support * fix default values * generated protobuf * fix match * remove explicit resolver * move conversion * error handling * no imports --------- Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- Cargo.lock | 57 ++++++++++++- livekit-ffi/protocol/ffi.proto | 6 ++ livekit-ffi/protocol/room.proto | 37 ++++++++ livekit-ffi/src/conversion/room.rs | 29 ++++++- livekit-ffi/src/livekit.proto.rs | 84 ++++++++++++++++++- livekit-ffi/src/server/requests.rs | 28 +++++++ livekit-ffi/src/server/room.rs | 74 +++++++++++++++- livekit-protocol/Cargo.toml | 6 +- livekit/Cargo.toml | 1 + livekit/src/proto.rs | 30 ++++++- livekit/src/room/mod.rs | 32 +++++++ .../src/room/participant/local_participant.rs | 58 ++++++++++++- livekit/src/rtc_engine/mod.rs | 10 ++- livekit/src/rtc_engine/rtc_session.rs | 16 +++- 14 files changed, 452 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 34fcf8284..dc6e29d13 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,6 +37,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anyhow" version = "1.0.75" @@ -447,11 +462,16 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.31" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", "num-traits", + "wasm-bindgen", + "windows-targets 0.52.0", ] [[package]] @@ -1266,6 +1286,29 @@ dependencies = [ "tokio-native-tls", ] +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "idna" version = "0.5.0" @@ -1554,6 +1597,7 @@ checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" name = "livekit" version = "0.6.0" dependencies = [ + "chrono", "futures-util", "lazy_static", "libwebrtc", @@ -3315,6 +3359,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.0", +] + [[package]] name = "windows-sys" version = "0.45.0" diff --git a/livekit-ffi/protocol/ffi.proto b/livekit-ffi/protocol/ffi.proto index 5fead41c6..2c9f21815 100644 --- a/livekit-ffi/protocol/ffi.proto +++ b/livekit-ffi/protocol/ffi.proto @@ -96,6 +96,9 @@ message FfiRequest { NewSoxResamplerRequest new_sox_resampler = 33; PushSoxResamplerRequest push_sox_resampler = 34; FlushSoxResamplerRequest flush_sox_resampler = 35; + + SendChatMessageRequest send_chat_message = 36; + EditChatMessageRequest edit_chat_message = 37; } } @@ -144,6 +147,8 @@ message FfiResponse { NewSoxResamplerResponse new_sox_resampler = 33; PushSoxResamplerResponse push_sox_resampler = 34; FlushSoxResamplerResponse flush_sox_resampler = 35; + + SendChatMessageResponse send_chat_message = 36; } } @@ -172,6 +177,7 @@ message FfiEvent { GetSessionStatsCallback get_session_stats = 19; Panic panic = 20; PublishSipDtmfCallback publish_sip_dtmf = 21; + SendChatMessageCallback chat_message = 22; } } diff --git a/livekit-ffi/protocol/room.proto b/livekit-ffi/protocol/room.proto index 1cdbb977a..6c46e6d9a 100644 --- a/livekit-ffi/protocol/room.proto +++ b/livekit-ffi/protocol/room.proto @@ -144,6 +144,28 @@ message SetLocalMetadataCallback { optional string error = 2; } +message SendChatMessageRequest { + uint64 local_participant_handle = 1; + string message = 2; + repeated string destination_identities = 3; + optional string sender_identity = 4; +} +message EditChatMessageRequest { + uint64 local_participant_handle = 1; + string edit_text = 2; + ChatMessage original_message = 3; + repeated string destination_identities = 4; + optional string sender_identity = 5; +} +message SendChatMessageResponse { + uint64 async_id = 1; +} +message SendChatMessageCallback { + uint64 async_id = 1; + optional string error = 2; + optional ChatMessage chat_message = 3; +} + // Change the local participant's attributes message SetLocalAttributesRequest { uint64 local_participant_handle = 1; @@ -343,6 +365,7 @@ message RoomEvent { RoomEOS eos = 26; // The stream of room events has ended DataPacketReceived data_packet_received = 27; TranscriptionReceived transcription_received = 28; + ChatMessageReceived chat_message = 29; } } @@ -457,6 +480,20 @@ message UserPacket { optional string topic = 2; } +message ChatMessage { + string id = 1; + int64 timestamp = 2; + string message = 3; + optional int64 edit_timestamp = 4; + optional bool deleted = 5; + optional bool generated = 6; +} + +message ChatMessageReceived { + ChatMessage message = 1; + string participant_identity = 2; +} + message SipDTMF { uint32 code = 1; optional string digit = 2; diff --git a/livekit-ffi/src/conversion/room.rs b/livekit-ffi/src/conversion/room.rs index a42d58fcb..199fd7343 100644 --- a/livekit-ffi/src/conversion/room.rs +++ b/livekit-ffi/src/conversion/room.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::{proto, server::room::FfiRoom}; use livekit::{ e2ee::{ key_provider::{KeyProvider, KeyProviderOptions}, @@ -25,8 +26,6 @@ use livekit::{ }, }; -use crate::{proto, server::room::FfiRoom}; - impl From for proto::EncryptionState { fn from(value: EncryptionState) -> Self { match value { @@ -241,3 +240,29 @@ impl From<&FfiRoom> for proto::RoomInfo { } } } + +impl From for livekit::ChatMessage { + fn from(proto_msg: proto::ChatMessage) -> Self { + livekit::ChatMessage { + id: proto_msg.id, + message: proto_msg.message, + timestamp: proto_msg.timestamp, + edit_timestamp: proto_msg.edit_timestamp, + deleted: proto_msg.deleted, + generated: proto_msg.generated, + } + } +} + +impl From for proto::ChatMessage { + fn from(msg: livekit::ChatMessage) -> Self { + proto::ChatMessage { + id: msg.id, + message: msg.message, + timestamp: msg.timestamp, + edit_timestamp: msg.edit_timestamp, + deleted: msg.deleted.into(), + generated: msg.generated.into(), + } + } +} diff --git a/livekit-ffi/src/livekit.proto.rs b/livekit-ffi/src/livekit.proto.rs index 6f003c09f..b4cda5cdf 100644 --- a/livekit-ffi/src/livekit.proto.rs +++ b/livekit-ffi/src/livekit.proto.rs @@ -2210,6 +2210,48 @@ pub struct SetLocalMetadataCallback { #[prost(string, optional, tag="2")] pub error: ::core::option::Option<::prost::alloc::string::String>, } +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SendChatMessageRequest { + #[prost(uint64, tag="1")] + pub local_participant_handle: u64, + #[prost(string, tag="2")] + pub message: ::prost::alloc::string::String, + #[prost(string, repeated, tag="3")] + pub destination_identities: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(string, optional, tag="4")] + pub sender_identity: ::core::option::Option<::prost::alloc::string::String>, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EditChatMessageRequest { + #[prost(uint64, tag="1")] + pub local_participant_handle: u64, + #[prost(string, tag="2")] + pub edit_text: ::prost::alloc::string::String, + #[prost(message, optional, tag="3")] + pub original_message: ::core::option::Option, + #[prost(string, repeated, tag="4")] + pub destination_identities: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(string, optional, tag="5")] + pub sender_identity: ::core::option::Option<::prost::alloc::string::String>, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SendChatMessageResponse { + #[prost(uint64, tag="1")] + pub async_id: u64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SendChatMessageCallback { + #[prost(uint64, tag="1")] + pub async_id: u64, + #[prost(string, optional, tag="2")] + pub error: ::core::option::Option<::prost::alloc::string::String>, + #[prost(message, optional, tag="3")] + pub chat_message: ::core::option::Option, +} /// Change the local participant's attributes #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -2407,7 +2449,7 @@ pub struct OwnedBuffer { pub struct RoomEvent { #[prost(uint64, tag="1")] pub room_handle: u64, - #[prost(oneof="room_event::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28")] + #[prost(oneof="room_event::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29")] pub message: ::core::option::Option, } /// Nested message and enum types in `RoomEvent`. @@ -2471,6 +2513,8 @@ pub mod room_event { DataPacketReceived(super::DataPacketReceived), #[prost(message, tag="28")] TranscriptionReceived(super::TranscriptionReceived), + #[prost(message, tag="29")] + ChatMessage(super::ChatMessageReceived), } } #[allow(clippy::derive_partial_eq_without_eq)] @@ -2655,6 +2699,30 @@ pub struct UserPacket { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct ChatMessage { + #[prost(string, tag="1")] + pub id: ::prost::alloc::string::String, + #[prost(int64, tag="2")] + pub timestamp: i64, + #[prost(string, tag="3")] + pub message: ::prost::alloc::string::String, + #[prost(int64, optional, tag="4")] + pub edit_timestamp: ::core::option::Option, + #[prost(bool, optional, tag="5")] + pub deleted: ::core::option::Option, + #[prost(bool, optional, tag="6")] + pub generated: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ChatMessageReceived { + #[prost(message, optional, tag="1")] + pub message: ::core::option::Option, + #[prost(string, tag="2")] + pub participant_identity: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct SipDtmf { #[prost(uint32, tag="1")] pub code: u32, @@ -3443,7 +3511,7 @@ impl AudioSourceType { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiRequest { - #[prost(oneof="ffi_request::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35")] + #[prost(oneof="ffi_request::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37")] pub message: ::core::option::Option, } /// Nested message and enum types in `FfiRequest`. @@ -3523,13 +3591,17 @@ pub mod ffi_request { PushSoxResampler(super::PushSoxResamplerRequest), #[prost(message, tag="35")] FlushSoxResampler(super::FlushSoxResamplerRequest), + #[prost(message, tag="36")] + SendChatMessage(super::SendChatMessageRequest), + #[prost(message, tag="37")] + EditChatMessage(super::EditChatMessageRequest), } } /// This is the output of livekit_ffi_request function. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiResponse { - #[prost(oneof="ffi_response::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35")] + #[prost(oneof="ffi_response::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36")] pub message: ::core::option::Option, } /// Nested message and enum types in `FfiResponse`. @@ -3609,6 +3681,8 @@ pub mod ffi_response { PushSoxResampler(super::PushSoxResamplerResponse), #[prost(message, tag="35")] FlushSoxResampler(super::FlushSoxResamplerResponse), + #[prost(message, tag="36")] + SendChatMessage(super::SendChatMessageResponse), } } /// To minimize complexity, participant events are not included in the protocol. @@ -3617,7 +3691,7 @@ pub mod ffi_response { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiEvent { - #[prost(oneof="ffi_event::Message", tags="1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21")] + #[prost(oneof="ffi_event::Message", tags="1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22")] pub message: ::core::option::Option, } /// Nested message and enum types in `FfiEvent`. @@ -3665,6 +3739,8 @@ pub mod ffi_event { Panic(super::Panic), #[prost(message, tag="21")] PublishSipDtmf(super::PublishSipDtmfCallback), + #[prost(message, tag="22")] + ChatMessage(super::SendChatMessageCallback), } } /// Stop all rooms synchronously (Do we need async here?). diff --git a/livekit-ffi/src/server/requests.rs b/livekit-ffi/src/server/requests.rs index a1043c8c3..febb0cec3 100644 --- a/livekit-ffi/src/server/requests.rs +++ b/livekit-ffi/src/server/requests.rs @@ -182,6 +182,28 @@ fn on_set_local_attributes( Ok(ffi_participant.room.set_local_attributes(server, set_local_attributes)) } +fn on_send_chat_message( + server: &'static FfiServer, + send_chat_message: proto::SendChatMessageRequest, +) -> FfiResult { + let ffi_participant = server + .retrieve_handle::(send_chat_message.local_participant_handle)? + .clone(); + + Ok(ffi_participant.room.send_chat_message(server, send_chat_message)) +} + +fn on_edit_chat_message( + server: &'static FfiServer, + edit_chat_message: proto::EditChatMessageRequest, +) -> FfiResult { + let ffi_participant = server + .retrieve_handle::(edit_chat_message.local_participant_handle)? + .clone(); + + Ok(ffi_participant.room.edit_chat_message(server, edit_chat_message)) +} + /// Create a new video track from a source fn on_create_video_track( server: &'static FfiServer, @@ -813,6 +835,12 @@ pub fn handle_request( server, update, )?) } + proto::ffi_request::Message::SendChatMessage(update) => { + proto::ffi_response::Message::SendChatMessage(on_send_chat_message(server, update)?) + } + proto::ffi_request::Message::EditChatMessage(update) => { + proto::ffi_response::Message::SendChatMessage(on_edit_chat_message(server, update)?) + } proto::ffi_request::Message::CreateVideoTrack(create) => { proto::ffi_response::Message::CreateVideoTrack(on_create_video_track(server, create)?) } diff --git a/livekit-ffi/src/server/room.rs b/livekit-ffi/src/server/room.rs index cf6fe02f5..92a6fcb12 100644 --- a/livekit-ffi/src/server/room.rs +++ b/livekit-ffi/src/server/room.rs @@ -16,7 +16,7 @@ use std::collections::HashMap; use std::{collections::HashSet, slice, sync::Arc, time::Duration}; use livekit::prelude::*; -use livekit::{participant, track}; +use livekit::{participant, track, ChatMessage}; use parking_lot::Mutex; use tokio::sync::{broadcast, mpsc, oneshot, Mutex as AsyncMutex}; use tokio::task::JoinHandle; @@ -569,6 +569,67 @@ impl RoomInner { server.watch_panic(handle); proto::SetLocalAttributesResponse { async_id } } + + pub fn send_chat_message( + self: &Arc, + server: &'static FfiServer, + send_chat_message: proto::SendChatMessageRequest, + ) -> proto::SendChatMessageResponse { + let async_id = server.next_id(); + let inner = self.clone(); + let handle = server.async_runtime.spawn(async move { + let res = inner + .room + .local_participant() + .send_chat_message( + send_chat_message.message, + send_chat_message.destination_identities.into(), + send_chat_message.sender_identity, + ) + .await; + let sent_message = res.as_ref().unwrap().clone(); + let _ = server.send_event(proto::ffi_event::Message::ChatMessage( + proto::SendChatMessageCallback { + async_id, + error: res.err().map(|e| e.to_string()), + chat_message: proto::ChatMessage::from(sent_message).into(), + }, + )); + }); + server.watch_panic(handle); + proto::SendChatMessageResponse { async_id } + } + + pub fn edit_chat_message( + self: &Arc, + server: &'static FfiServer, + edit_chat_message: proto::EditChatMessageRequest, + ) -> proto::SendChatMessageResponse { + let async_id = server.next_id(); + let inner = self.clone(); + let handle = server.async_runtime.spawn(async move { + let res = inner + .room + .local_participant() + .edit_chat_message( + edit_chat_message.edit_text, + edit_chat_message.original_message.unwrap().into(), + edit_chat_message.destination_identities.into(), + edit_chat_message.sender_identity, + ) + .await; + let sent_message: ChatMessage = res.as_ref().unwrap().clone(); + let _ = server.send_event(proto::ffi_event::Message::ChatMessage( + proto::SendChatMessageCallback { + async_id, + error: res.err().map(|e| e.to_string()), + chat_message: proto::ChatMessage::from(sent_message).into(), + }, + )); + }); + server.watch_panic(handle); + proto::SendChatMessageResponse { async_id } + } } // Task used to publish data without blocking the client thread @@ -977,6 +1038,17 @@ async fn forward_event( }, )); } + RoomEvent::ChatMessage { message, participant } => { + let (sid, identity) = match participant { + Some(p) => (Some(p.sid().to_string()), p.identity().to_string()), + None => (None, String::new()), + }; + let _ = + send_event(proto::room_event::Message::ChatMessage(proto::ChatMessageReceived { + message: proto::ChatMessage::from(message).into(), + participant_identity: identity, + })); + } RoomEvent::ConnectionStateChanged(state) => { let _ = send_event(proto::room_event::Message::ConnectionStateChanged( proto::ConnectionStateChanged { state: proto::ConnectionState::from(state).into() }, diff --git a/livekit-protocol/Cargo.toml b/livekit-protocol/Cargo.toml index 7831959a8..7d44495a1 100644 --- a/livekit-protocol/Cargo.toml +++ b/livekit-protocol/Cargo.toml @@ -8,7 +8,11 @@ repository = "https://github.com/livekit/rust-sdks" [dependencies] livekit-runtime = { path = "../livekit-runtime", version = "0.3.0" } -tokio = { version = "1", default-features = false, features = [ "sync", "macros", "rt" ] } +tokio = { version = "1", default-features = false, features = [ + "sync", + "macros", + "rt", +] } futures-util = { version = "0.3", features = ["sink"] } parking_lot = "0.12" prost = "0.12" diff --git a/livekit/Cargo.toml b/livekit/Cargo.toml index be31f592d..269ffdf1a 100644 --- a/livekit/Cargo.toml +++ b/livekit/Cargo.toml @@ -40,3 +40,4 @@ futures-util = { version = "0.3", default-features = false, features = ["sink"] thiserror = "1.0" lazy_static = "1.4" log = "0.4" +chrono = "0.4.38" diff --git a/livekit/src/proto.rs b/livekit/src/proto.rs index ed6a4aaf3..b96ae2c57 100644 --- a/livekit/src/proto.rs +++ b/livekit/src/proto.rs @@ -14,7 +14,9 @@ use livekit_protocol::*; -use crate::{e2ee::EncryptionType, participant, track, DataPacketKind}; +use crate::{ + e2ee::EncryptionType, participant, room::ChatMessage as RoomChatMessage, track, DataPacketKind, +}; // Conversions impl From for participant::ConnectionQuality { @@ -122,3 +124,29 @@ impl From for participant::ParticipantKind { } } } + +impl From for RoomChatMessage { + fn from(proto_msg: ChatMessage) -> Self { + RoomChatMessage { + id: proto_msg.id, + message: proto_msg.message, + timestamp: proto_msg.timestamp, + edit_timestamp: proto_msg.edit_timestamp, + deleted: proto_msg.deleted.into(), + generated: proto_msg.generated.into(), + } + } +} + +impl From for ChatMessage { + fn from(msg: RoomChatMessage) -> Self { + ChatMessage { + id: msg.id, + message: msg.message, + timestamp: msg.timestamp, + edit_timestamp: msg.edit_timestamp, + deleted: msg.deleted.unwrap_or(false), + generated: msg.generated.unwrap_or(false), + } + } +} diff --git a/livekit/src/room/mod.rs b/livekit/src/room/mod.rs index 9b3e1bdc5..346d4145d 100644 --- a/livekit/src/room/mod.rs +++ b/livekit/src/room/mod.rs @@ -162,6 +162,10 @@ pub enum RoomEvent { digit: Option, participant: Option, }, + ChatMessage { + message: ChatMessage, + participant: Option, + }, E2eeStateChanged { participant: Participant, state: EncryptionState, @@ -236,6 +240,16 @@ pub struct SipDTMF { pub destination_identities: Vec, } +#[derive(Default, Debug, Clone)] +pub struct ChatMessage { + pub id: String, + pub message: String, + pub timestamp: i64, + pub edit_timestamp: Option, + pub deleted: Option, + pub generated: Option, +} + #[derive(Debug, Clone)] pub struct RoomOptions { pub auto_subscribe: bool, @@ -606,6 +620,9 @@ impl RoomSession { EngineEvent::Data { payload, topic, kind, participant_sid, participant_identity } => { self.handle_data(payload, topic, kind, participant_sid, participant_identity); } + EngineEvent::ChatMessage { participant_identity, message } => { + self.handle_chat_message(participant_identity, message); + } EngineEvent::Transcription { participant_identity, track_sid, segments } => { self.handle_transcription(participant_identity, track_sid, segments); } @@ -1068,6 +1085,21 @@ impl RoomSession { }); } + fn handle_chat_message( + &self, + participant_identity: ParticipantIdentity, + chat_message: ChatMessage, + ) { + let participant = self.get_participant_by_identity(&participant_identity); + + if participant.is_none() { + // We received a data packet from a participant that is not in the participants list + return; + } + + self.dispatcher.dispatch(&RoomEvent::ChatMessage { message: chat_message, participant }); + } + fn handle_dtmf( &self, code: u32, diff --git a/livekit/src/room/participant/local_participant.rs b/livekit/src/room/participant/local_participant.rs index b9dde45d1..6b334abb4 100644 --- a/livekit/src/room/participant/local_participant.rs +++ b/livekit/src/room/participant/local_participant.rs @@ -19,7 +19,9 @@ use std::{ time::Duration, }; -use libwebrtc::rtp_parameters::RtpEncodingParameters; +use chrono::{TimeZone, Utc}; + +use libwebrtc::{native::create_random_uuid, rtp_parameters::RtpEncodingParameters}; use livekit_api::signal_client::SignalError; use livekit_protocol as proto; use livekit_runtime::timeout; @@ -32,7 +34,7 @@ use crate::{ options::{self, compute_video_encodings, video_layers_from_encodings, TrackPublishOptions}, prelude::*, rtc_engine::{EngineError, RtcEngine}, - DataPacket, SipDTMF, Transcription, + ChatMessage, DataPacket, SipDTMF, Transcription, }; const REQUEST_TIMEOUT: Duration = Duration::from_secs(5); @@ -331,6 +333,58 @@ impl LocalParticipant { } } + pub async fn send_chat_message( + &self, + text: String, + destination_identities: Option>, + sender_identity: Option, + ) -> RoomResult { + let chat_message = proto::ChatMessage { + id: create_random_uuid(), + timestamp: Utc::now().timestamp_millis(), + message: text, + ..Default::default() + }; + + let data = proto::DataPacket { + value: Some(proto::data_packet::Value::ChatMessage(chat_message.clone())), + participant_identity: sender_identity.unwrap_or_default(), + destination_identities: destination_identities.unwrap_or_default(), + ..Default::default() + }; + + match self.inner.rtc_engine.publish_data(&data, DataPacketKind::Reliable).await { + Ok(_) => Ok(ChatMessage::from(chat_message)), + Err(e) => Err(Into::into(e)), + } + } + + pub async fn edit_chat_message( + &self, + edit_text: String, + original_message: ChatMessage, + destination_identities: Option>, + sender_identity: Option, + ) -> RoomResult { + let edited_message = ChatMessage { + message: edit_text, + edit_timestamp: Utc::now().timestamp_millis().into(), + ..original_message + }; + let proto_msg = proto::ChatMessage::from(edited_message); + let data = proto::DataPacket { + value: Some(proto::data_packet::Value::ChatMessage(proto_msg.clone())), + participant_identity: sender_identity.unwrap_or_default(), + destination_identities: destination_identities.unwrap_or_default(), + ..Default::default() + }; + + match self.inner.rtc_engine.publish_data(&data, DataPacketKind::Reliable).await { + Ok(_) => Ok(ChatMessage::from(proto_msg)), + Err(e) => Err(Into::into(e)), + } + } + pub async fn unpublish_track( &self, sid: &TrackSid, diff --git a/livekit/src/rtc_engine/mod.rs b/livekit/src/rtc_engine/mod.rs index d0a235510..efaf28740 100644 --- a/livekit/src/rtc_engine/mod.rs +++ b/livekit/src/rtc_engine/mod.rs @@ -27,7 +27,6 @@ use tokio::sync::{ pub use self::rtc_session::SessionStats; use crate::prelude::ParticipantIdentity; -use crate::TranscriptionSegment; use crate::{ id::ParticipantSid, options::TrackPublishOptions, @@ -39,6 +38,7 @@ use crate::{ }, DataPacketKind, }; +use crate::{ChatMessage, TranscriptionSegment}; pub mod lk_runtime; mod peer_transport; @@ -99,6 +99,10 @@ pub enum EngineEvent { topic: Option, kind: DataPacketKind, }, + ChatMessage { + participant_identity: ParticipantIdentity, + message: ChatMessage, + }, Transcription { participant_identity: ParticipantIdentity, track_sid: String, @@ -443,6 +447,10 @@ impl EngineInner { kind, }); } + SessionEvent::ChatMessage { participant_identity, message } => { + let _ = + self.engine_tx.send(EngineEvent::ChatMessage { participant_identity, message }); + } SessionEvent::SipDTMF { participant_identity, code, digit } => { let _ = self.engine_tx.send(EngineEvent::SipDTMF { participant_identity, code, digit }); diff --git a/livekit/src/rtc_engine/rtc_session.rs b/livekit/src/rtc_engine/rtc_session.rs index 100ef8390..58168359b 100644 --- a/livekit/src/rtc_engine/rtc_session.rs +++ b/livekit/src/rtc_engine/rtc_session.rs @@ -34,11 +34,11 @@ use proto::{ debouncer::{self, Debouncer}, SignalTarget, }; -use serde::{Deserialize, Serialize}; +use serde::{de::IntoDeserializer, Deserialize, Serialize}; use tokio::sync::{mpsc, oneshot, watch}; use super::{rtc_events, EngineError, EngineOptions, EngineResult, SimulateScenario}; -use crate::{id::ParticipantIdentity, TranscriptionSegment}; +use crate::{id::ParticipantIdentity, ChatMessage, TranscriptionSegment}; use crate::{ id::ParticipantSid, options::TrackPublishOptions, @@ -81,6 +81,10 @@ pub enum SessionEvent { topic: Option, kind: DataPacketKind, }, + ChatMessage { + participant_identity: ParticipantIdentity, + message: ChatMessage, + }, Transcription { participant_identity: ParticipantIdentity, track_sid: String, @@ -657,6 +661,14 @@ impl SessionInner { segments, }); } + proto::data_packet::Value::ChatMessage(message) => { + let _ = self.emitter.send(SessionEvent::ChatMessage { + participant_identity: ParticipantIdentity( + data.participant_identity, + ), + message: ChatMessage::from(message.clone()), + }); + } _ => {} } } From 0e4b9834598c32c52db0928f37e2d04db6c4cb45 Mon Sep 17 00:00:00 2001 From: lukasIO Date: Mon, 14 Oct 2024 13:46:20 +0200 Subject: [PATCH 060/274] ffi 0.11.5 (#469) * ffi 0.11.5 * no format --- livekit-ffi/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 97b8a2885..cf2656640 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.11.4" +version = "0.11.5" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" From e6f216419de174a04ee7726b024731903494ac44 Mon Sep 17 00:00:00 2001 From: lukasIO Date: Fri, 18 Oct 2024 07:17:27 +0200 Subject: [PATCH 061/274] Use proto2 syntax for FFI (#470) --- livekit-ffi/protocol/audio_frame.proto | 133 +-- livekit-ffi/protocol/e2ee.proto | 64 +- livekit-ffi/protocol/ffi.proto | 14 +- livekit-ffi/protocol/handle.proto | 4 +- livekit-ffi/protocol/participant.proto | 16 +- livekit-ffi/protocol/room.proto | 320 +++--- livekit-ffi/protocol/stats.proto | 456 ++++---- livekit-ffi/protocol/track.proto | 74 +- livekit-ffi/protocol/video_frame.proto | 100 +- livekit-ffi/src/conversion/room.rs | 10 +- livekit-ffi/src/conversion/stats.rs | 60 +- livekit-ffi/src/livekit.proto.rs | 1415 +++++++++++++----------- livekit-ffi/src/server/audio_source.rs | 9 +- livekit-ffi/src/server/audio_stream.rs | 18 +- livekit-ffi/src/server/requests.rs | 114 +- livekit-ffi/src/server/room.rs | 186 ++-- livekit-ffi/src/server/video_source.rs | 15 +- livekit-ffi/src/server/video_stream.rs | 20 +- 18 files changed, 1574 insertions(+), 1454 deletions(-) diff --git a/livekit-ffi/protocol/audio_frame.proto b/livekit-ffi/protocol/audio_frame.proto index e5bccc27b..d10f3a382 100644 --- a/livekit-ffi/protocol/audio_frame.proto +++ b/livekit-ffi/protocol/audio_frame.proto @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -syntax = "proto3"; +syntax = "proto2"; package livekit.proto; option csharp_namespace = "LiveKit.Proto"; @@ -23,106 +23,109 @@ import "track.proto"; // Create a new AudioStream // AudioStream is used to receive audio frames from a track message NewAudioStreamRequest { - uint64 track_handle = 1; - AudioStreamType type = 2; - uint32 sample_rate = 3; - uint32 num_channels = 4; + required uint64 track_handle = 1; + required AudioStreamType type = 2; + required uint32 sample_rate = 3; + required uint32 num_channels = 4; } -message NewAudioStreamResponse { OwnedAudioStream stream = 1; } +message NewAudioStreamResponse { required OwnedAudioStream stream = 1; } message AudioStreamFromParticipantRequest { - uint64 participant_handle = 1; - AudioStreamType type = 2; + required uint64 participant_handle = 1; + required AudioStreamType type = 2; optional TrackSource track_source = 3; - uint32 sample_rate = 5; - uint32 num_channels = 6; + required uint32 sample_rate = 5; + required uint32 num_channels = 6; } -message AudioStreamFromParticipantResponse { OwnedAudioStream stream = 1; } +message AudioStreamFromParticipantResponse { required OwnedAudioStream stream = 1; } // Create a new AudioSource message NewAudioSourceRequest { - AudioSourceType type = 1; + required AudioSourceType type = 1; optional AudioSourceOptions options = 2; - uint32 sample_rate = 3; - uint32 num_channels = 4; - uint32 queue_size_ms = 5; + required uint32 sample_rate = 3; + required uint32 num_channels = 4; + required uint32 queue_size_ms = 5; } -message NewAudioSourceResponse { OwnedAudioSource source = 1; } +message NewAudioSourceResponse { required OwnedAudioSource source = 1; } // Push a frame to an AudioSource // The data provided must be available as long as the client receive the callback. message CaptureAudioFrameRequest { - uint64 source_handle = 1; - AudioFrameBufferInfo buffer = 2; + required uint64 source_handle = 1; + required AudioFrameBufferInfo buffer = 2; } message CaptureAudioFrameResponse { - uint64 async_id = 1; + required uint64 async_id = 1; } message CaptureAudioFrameCallback { - uint64 async_id = 1; + required uint64 async_id = 1; optional string error = 2; } message ClearAudioBufferRequest { - uint64 source_handle = 1; + required uint64 source_handle = 1; } message ClearAudioBufferResponse {} // Create a new AudioResampler message NewAudioResamplerRequest {} message NewAudioResamplerResponse { - OwnedAudioResampler resampler = 1; + required OwnedAudioResampler resampler = 1; } // Remix and resample an audio frame message RemixAndResampleRequest { - uint64 resampler_handle = 1; - AudioFrameBufferInfo buffer = 2; - uint32 num_channels = 3; - uint32 sample_rate = 4; + required uint64 resampler_handle = 1; + required AudioFrameBufferInfo buffer = 2; + required uint32 num_channels = 3; + required uint32 sample_rate = 4; } message RemixAndResampleResponse { - OwnedAudioFrameBuffer buffer = 1; + required OwnedAudioFrameBuffer buffer = 1; } // New resampler using SoX (much better quality) message NewSoxResamplerRequest { - double input_rate = 1; - double output_rate = 2; - uint32 num_channels = 3; - SoxResamplerDataType input_data_type = 4; - SoxResamplerDataType output_data_type = 5; - SoxQualityRecipe quality_recipe = 6; - uint32 flags = 7; + required double input_rate = 1; + required double output_rate = 2; + required uint32 num_channels = 3; + required SoxResamplerDataType input_data_type = 4; + required SoxResamplerDataType output_data_type = 5; + required SoxQualityRecipe quality_recipe = 6; + required uint32 flags = 7; } message NewSoxResamplerResponse { - OwnedSoxResampler resampler = 1; - optional string error = 2; + oneof message { + OwnedSoxResampler resampler = 1; + string error = 2; + } + } message PushSoxResamplerRequest { - uint64 resampler_handle = 1; - uint64 data_ptr = 2; // *const i16 - uint32 size = 3; // in bytes + required uint64 resampler_handle = 1; + required uint64 data_ptr = 2; // *const i16 + required uint32 size = 3; // in bytes } message PushSoxResamplerResponse { - uint64 output_ptr = 1; // *const i16 (could be null) - uint32 size = 2; // in bytes + required uint64 output_ptr = 1; // *const i16 (could be null) + required uint32 size = 2; // in bytes optional string error = 3; } message FlushSoxResamplerRequest { - uint64 resampler_handle = 1; + required uint64 resampler_handle = 1; } message FlushSoxResamplerResponse { - uint64 output_ptr = 1; // *const i16 (could be null) - uint32 size = 2; // in bytes + required uint64 output_ptr = 1; // *const i16 (could be null) + required uint32 size = 2; // in bytes optional string error = 3; } @@ -156,15 +159,15 @@ enum SoxFlagBits { // message AudioFrameBufferInfo { - uint64 data_ptr = 1; // *const i16 - uint32 num_channels = 2; - uint32 sample_rate = 3; - uint32 samples_per_channel = 4; + required uint64 data_ptr = 1; // *const i16 + required uint32 num_channels = 2; + required uint32 sample_rate = 3; + required uint32 samples_per_channel = 4; } message OwnedAudioFrameBuffer { - FfiOwnedHandle handle = 1; - AudioFrameBufferInfo info = 2; + required FfiOwnedHandle handle = 1; + required AudioFrameBufferInfo info = 2; } // @@ -177,16 +180,16 @@ enum AudioStreamType { } message AudioStreamInfo { - AudioStreamType type = 1; + required AudioStreamType type = 1; } message OwnedAudioStream { - FfiOwnedHandle handle = 1; - AudioStreamInfo info = 2; + required FfiOwnedHandle handle = 1; + required AudioStreamInfo info = 2; } message AudioStreamEvent { - uint64 stream_handle = 1; + required uint64 stream_handle = 1; oneof message { AudioFrameReceived frame_received = 2; AudioStreamEOS eos = 3; @@ -194,7 +197,7 @@ message AudioStreamEvent { } message AudioFrameReceived { - OwnedAudioFrameBuffer frame = 1; + required OwnedAudioFrameBuffer frame = 1; } message AudioStreamEOS {} @@ -204,9 +207,9 @@ message AudioStreamEOS {} // message AudioSourceOptions { - bool echo_cancellation = 1; - bool noise_suppression = 2; - bool auto_gain_control = 3; + required bool echo_cancellation = 1; + required bool noise_suppression = 2; + required bool auto_gain_control = 3; } enum AudioSourceType { @@ -214,12 +217,12 @@ enum AudioSourceType { } message AudioSourceInfo { - AudioSourceType type = 2; + required AudioSourceType type = 2; } message OwnedAudioSource { - FfiOwnedHandle handle = 1; - AudioSourceInfo info = 2; + required FfiOwnedHandle handle = 1; + required AudioSourceInfo info = 2; } // @@ -229,8 +232,8 @@ message OwnedAudioSource { message AudioResamplerInfo { } message OwnedAudioResampler { - FfiOwnedHandle handle = 1; - AudioResamplerInfo info = 2; + required FfiOwnedHandle handle = 1; + required AudioResamplerInfo info = 2; } @@ -243,6 +246,6 @@ message OwnedAudioResampler { message SoxResamplerInfo {} message OwnedSoxResampler { - FfiOwnedHandle handle = 1; - SoxResamplerInfo info = 2; + required FfiOwnedHandle handle = 1; + required SoxResamplerInfo info = 2; } diff --git a/livekit-ffi/protocol/e2ee.proto b/livekit-ffi/protocol/e2ee.proto index 39f142bb4..bdc20140f 100644 --- a/livekit-ffi/protocol/e2ee.proto +++ b/livekit-ffi/protocol/e2ee.proto @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -syntax = "proto3"; +syntax = "proto2"; package livekit.proto; option csharp_namespace = "LiveKit.Proto"; @@ -26,23 +26,23 @@ enum EncryptionType { } message FrameCryptor { - string participant_identity = 1; - string track_sid = 2; - int32 key_index = 3; - bool enabled = 4; + required string participant_identity = 1; + required string track_sid = 2; + required int32 key_index = 3; + required bool enabled = 4; } message KeyProviderOptions { // Only specify if you want to use a shared_key optional bytes shared_key = 1; - int32 ratchet_window_size = 2; - bytes ratchet_salt = 3; - int32 failure_tolerance = 4; // -1 = no tolerence + required int32 ratchet_window_size = 2; + required bytes ratchet_salt = 3; + required int32 failure_tolerance = 4; // -1 = no tolerance } message E2eeOptions { - EncryptionType encryption_type = 1; - KeyProviderOptions key_provider_options = 2; + required EncryptionType encryption_type = 1; + required KeyProviderOptions key_provider_options = 2; } enum EncryptionState { @@ -56,7 +56,7 @@ enum EncryptionState { } message E2eeManagerSetEnabledRequest { - bool enabled = 1; + required bool enabled = 1; } message E2eeManagerSetEnabledResponse {} @@ -66,64 +66,64 @@ message E2eeManagerGetFrameCryptorsResponse { } message FrameCryptorSetEnabledRequest { - string participant_identity = 1; - string track_sid = 2; - bool enabled = 3; + required string participant_identity = 1; + required string track_sid = 2; + required bool enabled = 3; } -message FrameCryptorSetEnabledResponse { } +message FrameCryptorSetEnabledResponse {} message FrameCryptorSetKeyIndexRequest { - string participant_identity = 1; - string track_sid = 2; - int32 key_index = 3; + required string participant_identity = 1; + required string track_sid = 2; + required int32 key_index = 3; } -message FrameCryptorSetKeyIndexResponse { } +message FrameCryptorSetKeyIndexResponse {} message SetSharedKeyRequest { - bytes shared_key = 1; - int32 key_index = 2; + required bytes shared_key = 1; + required int32 key_index = 2; } -message SetSharedKeyResponse { } +message SetSharedKeyResponse {} message RatchetSharedKeyRequest { - int32 key_index = 1; + required int32 key_index = 1; } message RatchetSharedKeyResponse { optional bytes new_key = 1; } message GetSharedKeyRequest { - int32 key_index = 1; + required int32 key_index = 1; } message GetSharedKeyResponse { optional bytes key = 1; } message SetKeyRequest { - string participant_identity = 1; - bytes key = 2; - int32 key_index = 3; + required string participant_identity = 1; + required bytes key = 2; + required int32 key_index = 3; } message SetKeyResponse {} message RatchetKeyRequest { - string participant_identity = 1; - int32 key_index = 2; + required string participant_identity = 1; + required int32 key_index = 2; } message RatchetKeyResponse { optional bytes new_key = 1; } message GetKeyRequest { - string participant_identity = 1; - int32 key_index = 2; + required string participant_identity = 1; + required int32 key_index = 2; } message GetKeyResponse { optional bytes key = 1; } message E2eeRequest { - uint64 room_handle = 1; + required uint64 room_handle = 1; oneof message { E2eeManagerSetEnabledRequest manager_set_enabled = 2; E2eeManagerGetFrameCryptorsRequest manager_get_frame_cryptors = 3; diff --git a/livekit-ffi/protocol/ffi.proto b/livekit-ffi/protocol/ffi.proto index 2c9f21815..32bfe4416 100644 --- a/livekit-ffi/protocol/ffi.proto +++ b/livekit-ffi/protocol/ffi.proto @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -syntax = "proto3"; +syntax = "proto2"; package livekit.proto; option csharp_namespace = "LiveKit.Proto"; @@ -185,14 +185,14 @@ message FfiEvent { // e.g: This is used for the Unity Editor after each assemblies reload. // TODO(theomonnom): Implement a debug mode where we can find all leaked handles? message DisposeRequest { - bool async = 1; + required bool async = 1; } message DisposeResponse { optional uint64 async_id = 1; // None if sync } message DisposeCallback { - uint64 async_id = 1; + required uint64 async_id = 1; } enum LogLevel { @@ -204,12 +204,12 @@ enum LogLevel { } message LogRecord { - LogLevel level = 1; - string target = 2; // e.g "livekit", "libwebrtc", "tokio-tungstenite", etc... + required LogLevel level = 1; + required string target = 2; // e.g "livekit", "libwebrtc", "tokio-tungstenite", etc... optional string module_path = 3; optional string file = 4; optional uint32 line = 5; - string message = 6; + required string message = 6; } message LogBatch { @@ -217,7 +217,7 @@ message LogBatch { } message Panic { - string message = 1; + required string message = 1; } // TODO(theomonnom): Debug messages (Print handles). diff --git a/livekit-ffi/protocol/handle.proto b/livekit-ffi/protocol/handle.proto index f86ee9d30..90c74671e 100644 --- a/livekit-ffi/protocol/handle.proto +++ b/livekit-ffi/protocol/handle.proto @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -syntax = "proto3"; +syntax = "proto2"; package livekit.proto; option csharp_namespace = "LiveKit.Proto"; @@ -27,5 +27,5 @@ option csharp_namespace = "LiveKit.Proto"; // When refering to a handle without owning it, we just use a uint32 without this message. // (the variable name is suffixed with "_handle") message FfiOwnedHandle { - uint64 id = 1; + required uint64 id = 1; } diff --git a/livekit-ffi/protocol/participant.proto b/livekit-ffi/protocol/participant.proto index a089d86e8..c0a480f2a 100644 --- a/livekit-ffi/protocol/participant.proto +++ b/livekit-ffi/protocol/participant.proto @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -syntax = "proto3"; +syntax = "proto2"; package livekit.proto; option csharp_namespace = "LiveKit.Proto"; @@ -20,17 +20,17 @@ option csharp_namespace = "LiveKit.Proto"; import "handle.proto"; message ParticipantInfo { - string sid = 1; - string name = 2; - string identity = 3; - string metadata = 4; + required string sid = 1; + required string name = 2; + required string identity = 3; + required string metadata = 4; map attributes = 5; - ParticipantKind kind = 6; + required ParticipantKind kind = 6; } message OwnedParticipant { - FfiOwnedHandle handle = 1; - ParticipantInfo info = 2; + required FfiOwnedHandle handle = 1; + required ParticipantInfo info = 2; } enum ParticipantKind { diff --git a/livekit-ffi/protocol/room.proto b/livekit-ffi/protocol/room.proto index 6c46e6d9a..b2b295d08 100644 --- a/livekit-ffi/protocol/room.proto +++ b/livekit-ffi/protocol/room.proto @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -syntax = "proto3"; +syntax = "proto2"; package livekit.proto; option csharp_namespace = "LiveKit.Proto"; @@ -26,190 +26,215 @@ import "stats.proto"; // Connect to a new LiveKit room message ConnectRequest { - string url = 1; - string token = 2; - RoomOptions options = 3; + required string url = 1; + required string token = 2; + required RoomOptions options = 3; } message ConnectResponse { - uint64 async_id = 1; + required uint64 async_id = 1; } message ConnectCallback { message ParticipantWithTracks { - OwnedParticipant participant = 1; + required OwnedParticipant participant = 1; // TrackInfo are not needed here, if we're subscribed to a track, the FfiServer will send // a TrackSubscribed event repeated OwnedTrackPublication publications = 2; } - uint64 async_id = 1; - optional string error = 2; - OwnedRoom room = 3; - OwnedParticipant local_participant = 4; - repeated ParticipantWithTracks participants = 5; + message Result { + required OwnedRoom room = 1; + required OwnedParticipant local_participant = 2; + repeated ParticipantWithTracks participants = 3; + } + + required uint64 async_id = 1; + oneof message { + string error = 2; + Result result = 3; + } + } // Disconnect from the a room -message DisconnectRequest { uint64 room_handle = 1; } -message DisconnectResponse { uint64 async_id = 1; } -message DisconnectCallback { uint64 async_id = 1; } +message DisconnectRequest { required uint64 room_handle = 1; } +message DisconnectResponse { required uint64 async_id = 1; } +message DisconnectCallback { required uint64 async_id = 1; } // Publish a track to the room message PublishTrackRequest { - uint64 local_participant_handle = 1; - uint64 track_handle = 2; - TrackPublishOptions options = 3; + required uint64 local_participant_handle = 1; + required uint64 track_handle = 2; + required TrackPublishOptions options = 3; } message PublishTrackResponse { - uint64 async_id = 1; + required uint64 async_id = 1; } message PublishTrackCallback { - uint64 async_id = 1; - optional string error = 2; - OwnedTrackPublication publication = 3; + required uint64 async_id = 1; + oneof message { + string error = 2; + OwnedTrackPublication publication = 3; + } + } // Unpublish a track from the room message UnpublishTrackRequest { - uint64 local_participant_handle = 1; - string track_sid = 2; - bool stop_on_unpublish = 3; + required uint64 local_participant_handle = 1; + required string track_sid = 2; + required bool stop_on_unpublish = 3; } message UnpublishTrackResponse { - uint64 async_id = 1; + required uint64 async_id = 1; } message UnpublishTrackCallback { - uint64 async_id = 1; + required uint64 async_id = 1; optional string error = 2; } // Publish data to other participants message PublishDataRequest { - uint64 local_participant_handle = 1; - uint64 data_ptr = 2; - uint64 data_len = 3; - bool reliable = 4; + required uint64 local_participant_handle = 1; + required uint64 data_ptr = 2; + required uint64 data_len = 3; + required bool reliable = 4; repeated string destination_sids = 5 [deprecated=true]; optional string topic = 6; repeated string destination_identities = 7; } message PublishDataResponse { - uint64 async_id = 1; + required uint64 async_id = 1; } message PublishDataCallback { - uint64 async_id = 1; + required uint64 async_id = 1; optional string error = 2; } // Publish transcription messages to room message PublishTranscriptionRequest { - uint64 local_participant_handle = 1; - string participant_identity = 2; - string track_id = 3; + required uint64 local_participant_handle = 1; + required string participant_identity = 2; + required string track_id = 3; repeated TranscriptionSegment segments = 4; } message PublishTranscriptionResponse { - uint64 async_id = 1; + required uint64 async_id = 1; } message PublishTranscriptionCallback { - uint64 async_id = 1; + required uint64 async_id = 1; optional string error = 2; } // Publish Sip DTMF messages to other participants message PublishSipDtmfRequest { - uint64 local_participant_handle = 1; - uint32 code = 2; - string digit = 3; + required uint64 local_participant_handle = 1; + required uint32 code = 2; + required string digit = 3; repeated string destination_identities = 4; } message PublishSipDtmfResponse { - uint64 async_id = 1; + required uint64 async_id = 1; } message PublishSipDtmfCallback { - uint64 async_id = 1; + required uint64 async_id = 1; optional string error = 2; } // Change the local participant's metadata message SetLocalMetadataRequest { - uint64 local_participant_handle = 1; - string metadata = 2; + required uint64 local_participant_handle = 1; + required string metadata = 2; } message SetLocalMetadataResponse { - uint64 async_id = 1; + required uint64 async_id = 1; } message SetLocalMetadataCallback { - uint64 async_id = 1; + required uint64 async_id = 1; optional string error = 2; } message SendChatMessageRequest { - uint64 local_participant_handle = 1; - string message = 2; + required uint64 local_participant_handle = 1; + required string message = 2; repeated string destination_identities = 3; optional string sender_identity = 4; } message EditChatMessageRequest { - uint64 local_participant_handle = 1; - string edit_text = 2; - ChatMessage original_message = 3; + required uint64 local_participant_handle = 1; + required string edit_text = 2; + required ChatMessage original_message = 3; repeated string destination_identities = 4; optional string sender_identity = 5; } message SendChatMessageResponse { - uint64 async_id = 1; + required uint64 async_id = 1; } message SendChatMessageCallback { - uint64 async_id = 1; - optional string error = 2; - optional ChatMessage chat_message = 3; + required uint64 async_id = 1; + oneof message { + string error = 2; + ChatMessage chat_message = 3; + } } // Change the local participant's attributes message SetLocalAttributesRequest { - uint64 local_participant_handle = 1; - map attributes = 2; + required uint64 local_participant_handle = 1; + repeated AttributesEntry attributes = 2; } + +message AttributesEntry { + required string key = 1; + required string value = 2; +} + message SetLocalAttributesResponse { - uint64 async_id = 1; + required uint64 async_id = 1; } message SetLocalAttributesCallback { - uint64 async_id = 1; + required uint64 async_id = 1; optional string error = 2; } // Change the local participant's name message SetLocalNameRequest { - uint64 local_participant_handle = 1; - string name = 2; + required uint64 local_participant_handle = 1; + required string name = 2; } message SetLocalNameResponse { - uint64 async_id = 1; + required uint64 async_id = 1; } message SetLocalNameCallback { - uint64 async_id = 1; + required uint64 async_id = 1; optional string error = 2; } // Change the "desire" to subs2ribe to a track message SetSubscribedRequest { - bool subscribe = 1; - uint64 publication_handle = 2; + required bool subscribe = 1; + required uint64 publication_handle = 2; } message SetSubscribedResponse {} message GetSessionStatsRequest { - uint64 room_handle = 1; + required uint64 room_handle = 1; } message GetSessionStatsResponse { - uint64 async_id = 1; + required uint64 async_id = 1; } message GetSessionStatsCallback { - uint64 async_id = 1; - optional string error = 2; - repeated RtcStats publisher_stats = 3; - repeated RtcStats subscriber_stats = 4; + message Result { + repeated RtcStats publisher_stats = 1; + repeated RtcStats subscriber_stats = 2; + } + + required uint64 async_id = 1; + + oneof message { + string error = 2; + Result result = 3; + } } // @@ -217,24 +242,24 @@ message GetSessionStatsCallback { // message VideoEncoding { - uint64 max_bitrate = 1; - double max_framerate = 2; + required uint64 max_bitrate = 1; + required double max_framerate = 2; } message AudioEncoding { - uint64 max_bitrate = 1; + required uint64 max_bitrate = 1; } message TrackPublishOptions { // encodings are optional - VideoEncoding video_encoding = 1; - AudioEncoding audio_encoding = 2; - VideoCodec video_codec = 3; - bool dtx = 4; - bool red = 5; - bool simulcast = 6; - TrackSource source = 7; - string stream = 8; + optional VideoEncoding video_encoding = 1; + optional AudioEncoding audio_encoding = 2; + required VideoCodec video_codec = 3; + required bool dtx = 4; + required bool red = 5; + required bool simulcast = 6; + required TrackSource source = 7; + required string stream = 8; } enum IceTransportType { @@ -250,8 +275,8 @@ enum ContinualGatheringPolicy { message IceServer { repeated string urls = 1; - string username = 2; - string password = 3; + optional string username = 2; + optional string password = 3; } message RtcConfig { @@ -261,12 +286,12 @@ message RtcConfig { } message RoomOptions { - bool auto_subscribe = 1; - bool adaptive_stream = 2; - bool dynacast = 3; + required bool auto_subscribe = 1; + required bool adaptive_stream = 2; + required bool dynacast = 3; optional E2eeOptions e2ee = 4; optional RtcConfig rtc_config = 5; // allow to setup a custom RtcConfiguration - uint32 join_retries = 6; + required uint32 join_retries = 6; } // @@ -316,26 +341,26 @@ enum DisconnectReason { } message TranscriptionSegment { - string id = 1; - string text = 2; - uint64 start_time = 3; - uint64 end_time = 4; - bool final = 5; - string language = 6; + required string id = 1; + required string text = 2; + required uint64 start_time = 3; + required uint64 end_time = 4; + required bool final = 5; + required string language = 6; } message BufferInfo { - uint64 data_ptr = 1; - uint64 data_len = 2; + required uint64 data_ptr = 1; + required uint64 data_len = 2; } message OwnedBuffer { - FfiOwnedHandle handle = 1; - BufferInfo data = 2; + required FfiOwnedHandle handle = 1; + required BufferInfo data = 2; } message RoomEvent { - uint64 room_handle = 1; + required uint64 room_handle = 1; oneof message { ParticipantConnected participant_connected = 2; ParticipantDisconnected participant_disconnected = 3; @@ -371,137 +396,137 @@ message RoomEvent { message RoomInfo { optional string sid = 1; - string name = 2; - string metadata = 3; + required string name = 2; + required string metadata = 3; } message OwnedRoom { - FfiOwnedHandle handle = 1; - RoomInfo info = 2; + required FfiOwnedHandle handle = 1; + required RoomInfo info = 2; } -message ParticipantConnected { OwnedParticipant info = 1; } +message ParticipantConnected { required OwnedParticipant info = 1; } message ParticipantDisconnected { - string participant_identity = 1; + required string participant_identity = 1; } message LocalTrackPublished { // The TrackPublicationInfo comes from the PublishTrack response // and the FfiClient musts wait for it before firing this event - string track_sid = 1; + required string track_sid = 1; } message LocalTrackUnpublished { - string publication_sid = 1; + required string publication_sid = 1; } message LocalTrackSubscribed { - string track_sid = 2; + required string track_sid = 2; } message TrackPublished { - string participant_identity = 1; - OwnedTrackPublication publication = 2; + required string participant_identity = 1; + required OwnedTrackPublication publication = 2; } message TrackUnpublished { - string participant_identity = 1; - string publication_sid = 2; + required string participant_identity = 1; + required string publication_sid = 2; } // Publication isn't needed for subscription events on the FFI // The FFI will retrieve the publication using the Track sid message TrackSubscribed { - string participant_identity = 1; - OwnedTrack track = 2; + required string participant_identity = 1; + required OwnedTrack track = 2; } message TrackUnsubscribed { // The FFI language can dispose/remove the VideoSink here - string participant_identity = 1; - string track_sid = 2; + required string participant_identity = 1; + required string track_sid = 2; } message TrackSubscriptionFailed { - string participant_identity = 1; - string track_sid = 2; - string error = 3; + required string participant_identity = 1; + required string track_sid = 2; + required string error = 3; } message TrackMuted { - string participant_identity = 1; - string track_sid = 2; + required string participant_identity = 1; + required string track_sid = 2; } message TrackUnmuted { - string participant_identity = 1; - string track_sid = 2; + required string participant_identity = 1; + required string track_sid = 2; } message E2eeStateChanged { - string participant_identity = 1; // Using sid instead of identity for ffi communication - EncryptionState state = 2; + required string participant_identity = 1; // Using sid instead of identity for ffi communication + required EncryptionState state = 2; } message ActiveSpeakersChanged { repeated string participant_identities = 1; } message RoomMetadataChanged { - string metadata = 1; + required string metadata = 1; } message RoomSidChanged { - string sid = 1; + required string sid = 1; } message ParticipantMetadataChanged { - string participant_identity = 1; - string metadata = 2; + required string participant_identity = 1; + required string metadata = 2; } message ParticipantAttributesChanged { - string participant_identity = 1; - map attributes = 2; - map changed_attributes = 3; + required string participant_identity = 1; + repeated AttributesEntry attributes = 2; + repeated AttributesEntry changed_attributes = 3; } message ParticipantNameChanged { - string participant_identity = 1; - string name = 2; + required string participant_identity = 1; + required string name = 2; } message ConnectionQualityChanged { - string participant_identity = 1; - ConnectionQuality quality = 2; + required string participant_identity = 1; + required ConnectionQuality quality = 2; } message UserPacket { - OwnedBuffer data = 1; + required OwnedBuffer data = 1; optional string topic = 2; } message ChatMessage { - string id = 1; - int64 timestamp = 2; - string message = 3; + required string id = 1; + required int64 timestamp = 2; + required string message = 3; optional int64 edit_timestamp = 4; optional bool deleted = 5; optional bool generated = 6; } message ChatMessageReceived { - ChatMessage message = 1; - string participant_identity = 2; + required ChatMessage message = 1; + required string participant_identity = 2; } message SipDTMF { - uint32 code = 1; + required uint32 code = 1; optional string digit = 2; } message DataPacketReceived { - DataPacketKind kind = 1; - string participant_identity = 2; // Can be empty if the data is sent a server SDK + required DataPacketKind kind = 1; + required string participant_identity = 2; // Can be empty if the data is sent a server SDK oneof value { UserPacket user = 4; SipDTMF sip_dtmf = 5; @@ -514,13 +539,14 @@ message TranscriptionReceived { repeated TranscriptionSegment segments = 3; } -message ConnectionStateChanged { ConnectionState state = 1; } +message ConnectionStateChanged { required ConnectionState state = 1; } message Connected {} message Disconnected { - DisconnectReason reason = 1; + required DisconnectReason reason = 1; } message Reconnecting {} message Reconnected {} message RoomEOS {} + diff --git a/livekit-ffi/protocol/stats.proto b/livekit-ffi/protocol/stats.proto index 45df5c9b2..f4771f3e1 100644 --- a/livekit-ffi/protocol/stats.proto +++ b/livekit-ffi/protocol/stats.proto @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -syntax = "proto3"; +syntax = "proto2"; package livekit.proto; option csharp_namespace = "LiveKit.Proto"; @@ -91,83 +91,83 @@ enum IceTcpCandidateType { message RtcStats { message Codec { - RtcStatsData rtc = 1; - CodecStats codec = 2; + required RtcStatsData rtc = 1; + required CodecStats codec = 2; } message InboundRtp { - RtcStatsData rtc = 1; - RtpStreamStats stream = 2; - ReceivedRtpStreamStats received = 3; - InboundRtpStreamStats inbound = 4; + required RtcStatsData rtc = 1; + required RtpStreamStats stream = 2; + required ReceivedRtpStreamStats received = 3; + required InboundRtpStreamStats inbound = 4; } message OutboundRtp { - RtcStatsData rtc = 1; - RtpStreamStats stream = 2; - SentRtpStreamStats sent = 3; - OutboundRtpStreamStats outbound = 4; + required RtcStatsData rtc = 1; + required RtpStreamStats stream = 2; + required SentRtpStreamStats sent = 3; + required OutboundRtpStreamStats outbound = 4; } message RemoteInboundRtp { - RtcStatsData rtc = 1; - RtpStreamStats stream = 2; - ReceivedRtpStreamStats received = 3; - RemoteInboundRtpStreamStats remote_inbound = 4; + required RtcStatsData rtc = 1; + required RtpStreamStats stream = 2; + required ReceivedRtpStreamStats received = 3; + required RemoteInboundRtpStreamStats remote_inbound = 4; } message RemoteOutboundRtp { - RtcStatsData rtc = 1; - RtpStreamStats stream = 2; - SentRtpStreamStats sent = 3; - RemoteOutboundRtpStreamStats remote_outbound = 4; + required RtcStatsData rtc = 1; + required RtpStreamStats stream = 2; + required SentRtpStreamStats sent = 3; + required RemoteOutboundRtpStreamStats remote_outbound = 4; } message MediaSource { - RtcStatsData rtc = 1; - MediaSourceStats source = 2; - AudioSourceStats audio = 3; - VideoSourceStats video = 4; + required RtcStatsData rtc = 1; + required MediaSourceStats source = 2; + required AudioSourceStats audio = 3; + required VideoSourceStats video = 4; } message MediaPlayout { - RtcStatsData rtc = 1; - AudioPlayoutStats audio_playout = 2; + required RtcStatsData rtc = 1; + required AudioPlayoutStats audio_playout = 2; } message PeerConnection { - RtcStatsData rtc = 1; - PeerConnectionStats pc = 2; + required RtcStatsData rtc = 1; + required PeerConnectionStats pc = 2; } message DataChannel { - RtcStatsData rtc = 1; - DataChannelStats dc = 2; + required RtcStatsData rtc = 1; + required DataChannelStats dc = 2; } message Transport { - RtcStatsData rtc = 1; - TransportStats transport = 2; + required RtcStatsData rtc = 1; + required TransportStats transport = 2; } message CandidatePair { - RtcStatsData rtc = 1; - CandidatePairStats candidate_pair = 2; + required RtcStatsData rtc = 1; + required CandidatePairStats candidate_pair = 2; } message LocalCandidate { - RtcStatsData rtc = 1; - IceCandidateStats candidate = 2; + required RtcStatsData rtc = 1; + required IceCandidateStats candidate = 2; } message RemoteCandidate { - RtcStatsData rtc = 1; - IceCandidateStats candidate = 2; + required RtcStatsData rtc = 1; + required IceCandidateStats candidate = 2; } message Certificate { - RtcStatsData rtc = 1; - CertificateStats certificate = 2; + required RtcStatsData rtc = 1; + required CertificateStats certificate = 2; } message Track { @@ -194,256 +194,256 @@ message RtcStats { } message RtcStatsData { - string id = 1; - int64 timestamp = 2; + required string id = 1; + required int64 timestamp = 2; } message CodecStats { - uint32 payload_type = 1; - string transport_id = 2; - string mime_type = 3; - uint32 clock_rate = 4; - uint32 channels = 5; - string sdp_fmtp_line = 6; + required uint32 payload_type = 1; + required string transport_id = 2; + required string mime_type = 3; + required uint32 clock_rate = 4; + required uint32 channels = 5; + required string sdp_fmtp_line = 6; } message RtpStreamStats { - uint32 ssrc = 1; - string kind = 2; - string transport_id = 3; - string codec_id = 4; + required uint32 ssrc = 1; + required string kind = 2; + required string transport_id = 3; + required string codec_id = 4; } message ReceivedRtpStreamStats { - uint64 packets_received = 1; - int64 packets_lost = 2; - double jitter = 3; + required uint64 packets_received = 1; + required int64 packets_lost = 2; + required double jitter = 3; } message InboundRtpStreamStats { - string track_identifier = 1; - string mid = 2; - string remote_id = 3; - uint32 frames_decoded = 4; - uint32 key_frames_decoded = 5; - uint32 frames_rendered = 6; - uint32 frames_dropped = 7; - uint32 frame_width = 8; - uint32 frame_height = 9; - double frames_per_second = 10; - uint64 qp_sum = 11; - double total_decode_time = 12; - double total_inter_frame_delay = 13; - double total_squared_inter_frame_delay = 14; - uint32 pause_count = 15; - double total_pause_duration = 16; - uint32 freeze_count = 17; - double total_freeze_duration = 18; - double last_packet_received_timestamp = 19; - uint64 header_bytes_received = 20; - uint64 packets_discarded = 21; - uint64 fec_bytes_received = 22; - uint64 fec_packets_received = 23; - uint64 fec_packets_discarded = 24; - uint64 bytes_received = 25; - uint32 nack_count = 26; - uint32 fir_count = 27; - uint32 pli_count = 28; - double total_processing_delay = 29; - double estimated_playout_timestamp = 30; - double jitter_buffer_delay = 31; - double jitter_buffer_target_delay = 32; - uint64 jitter_buffer_emitted_count = 33; - double jitter_buffer_minimum_delay = 34; - uint64 total_samples_received = 35; - uint64 concealed_samples = 36; - uint64 silent_concealed_samples = 37; - uint64 concealment_events = 38; - uint64 inserted_samples_for_deceleration = 39; - uint64 removed_samples_for_acceleration = 40; - double audio_level = 41; - double total_audio_energy = 42; - double total_samples_duration = 43; - uint64 frames_received = 44; - string decoder_implementation = 45; - string playout_id = 46; - bool power_efficient_decoder = 47; - uint64 frames_assembled_from_multiple_packets = 48; - double total_assembly_time = 49; - uint64 retransmitted_packets_received = 50; - uint64 retransmitted_bytes_received = 51; - uint32 rtx_ssrc = 52; - uint32 fec_ssrc = 53; + required string track_identifier = 1; + required string mid = 2; + required string remote_id = 3; + required uint32 frames_decoded = 4; + required uint32 key_frames_decoded = 5; + required uint32 frames_rendered = 6; + required uint32 frames_dropped = 7; + required uint32 frame_width = 8; + required uint32 frame_height = 9; + required double frames_per_second = 10; + required uint64 qp_sum = 11; + required double total_decode_time = 12; + required double total_inter_frame_delay = 13; + required double total_squared_inter_frame_delay = 14; + required uint32 pause_count = 15; + required double total_pause_duration = 16; + required uint32 freeze_count = 17; + required double total_freeze_duration = 18; + required double last_packet_received_timestamp = 19; + required uint64 header_bytes_received = 20; + required uint64 packets_discarded = 21; + required uint64 fec_bytes_received = 22; + required uint64 fec_packets_received = 23; + required uint64 fec_packets_discarded = 24; + required uint64 bytes_received = 25; + required uint32 nack_count = 26; + required uint32 fir_count = 27; + required uint32 pli_count = 28; + required double total_processing_delay = 29; + required double estimated_playout_timestamp = 30; + required double jitter_buffer_delay = 31; + required double jitter_buffer_target_delay = 32; + required uint64 jitter_buffer_emitted_count = 33; + required double jitter_buffer_minimum_delay = 34; + required uint64 total_samples_received = 35; + required uint64 concealed_samples = 36; + required uint64 silent_concealed_samples = 37; + required uint64 concealment_events = 38; + required uint64 inserted_samples_for_deceleration = 39; + required uint64 removed_samples_for_acceleration = 40; + required double audio_level = 41; + required double total_audio_energy = 42; + required double total_samples_duration = 43; + required uint64 frames_received = 44; + required string decoder_implementation = 45; + required string playout_id = 46; + required bool power_efficient_decoder = 47; + required uint64 frames_assembled_from_multiple_packets = 48; + required double total_assembly_time = 49; + required uint64 retransmitted_packets_received = 50; + required uint64 retransmitted_bytes_received = 51; + required uint32 rtx_ssrc = 52; + required uint32 fec_ssrc = 53; } message SentRtpStreamStats { - uint64 packets_sent = 1; - uint64 bytes_sent = 2; + required uint64 packets_sent = 1; + required uint64 bytes_sent = 2; } message OutboundRtpStreamStats { - string mid = 1; - string media_source_id = 2; - string remote_id = 3; - string rid = 4; - uint64 header_bytes_sent = 5; - uint64 retransmitted_packets_sent = 6; - uint64 retransmitted_bytes_sent = 7; - uint32 rtx_ssrc = 8; - double target_bitrate = 9; - uint64 total_encoded_bytes_target = 10; - uint32 frame_width = 11; - uint32 frame_height = 12; - double frames_per_second = 13; - uint32 frames_sent = 14; - uint32 huge_frames_sent = 15; - uint32 frames_encoded = 16; - uint32 key_frames_encoded = 17; - uint64 qp_sum = 18; - double total_encode_time = 19; - double total_packet_send_delay = 20; - QualityLimitationReason quality_limitation_reason = 21; + required string mid = 1; + required string media_source_id = 2; + required string remote_id = 3; + required string rid = 4; + required uint64 header_bytes_sent = 5; + required uint64 retransmitted_packets_sent = 6; + required uint64 retransmitted_bytes_sent = 7; + required uint32 rtx_ssrc = 8; + required double target_bitrate = 9; + required uint64 total_encoded_bytes_target = 10; + required uint32 frame_width = 11; + required uint32 frame_height = 12; + required double frames_per_second = 13; + required uint32 frames_sent = 14; + required uint32 huge_frames_sent = 15; + required uint32 frames_encoded = 16; + required uint32 key_frames_encoded = 17; + required uint64 qp_sum = 18; + required double total_encode_time = 19; + required double total_packet_send_delay = 20; + required QualityLimitationReason quality_limitation_reason = 21; map quality_limitation_durations = 22; - uint32 quality_limitation_resolution_changes = 23; - uint32 nack_count = 24; - uint32 fir_count = 25; - uint32 pli_count = 26; - string encoder_implementation = 27; - bool power_efficient_encoder = 28; - bool active = 29; - string scalibility_mode = 30; + required uint32 quality_limitation_resolution_changes = 23; + required uint32 nack_count = 24; + required uint32 fir_count = 25; + required uint32 pli_count = 26; + required string encoder_implementation = 27; + required bool power_efficient_encoder = 28; + required bool active = 29; + required string scalability_mode = 30; } message RemoteInboundRtpStreamStats { - string local_id = 1; - double round_trip_time = 2; - double total_round_trip_time = 3; - double fraction_lost = 4; - uint64 round_trip_time_measurements = 5; + required string local_id = 1; + required double round_trip_time = 2; + required double total_round_trip_time = 3; + required double fraction_lost = 4; + required uint64 round_trip_time_measurements = 5; } message RemoteOutboundRtpStreamStats { - string local_id = 1; - double remote_timestamp = 2; - uint64 reports_sent = 3; - double round_trip_time = 4; - double total_round_trip_time = 5; - uint64 round_trip_time_measurements = 6; + required string local_id = 1; + required double remote_timestamp = 2; + required uint64 reports_sent = 3; + required double round_trip_time = 4; + required double total_round_trip_time = 5; + required uint64 round_trip_time_measurements = 6; } message MediaSourceStats { - string track_identifier = 1; - string kind = 2; + required string track_identifier = 1; + required string kind = 2; } message AudioSourceStats { - double audio_level = 1; - double total_audio_energy = 2; - double total_samples_duration = 3; - double echo_return_loss = 4; - double echo_return_loss_enhancement = 5; - double dropped_samples_duration = 6; - uint32 dropped_samples_events = 7; - double total_capture_delay = 8; - uint64 total_samples_captured = 9; + required double audio_level = 1; + required double total_audio_energy = 2; + required double total_samples_duration = 3; + required double echo_return_loss = 4; + required double echo_return_loss_enhancement = 5; + required double dropped_samples_duration = 6; + required uint32 dropped_samples_events = 7; + required double total_capture_delay = 8; + required uint64 total_samples_captured = 9; } message VideoSourceStats { - uint32 width = 1; - uint32 height = 2; - uint32 frames = 3; - double frames_per_second = 4; + required uint32 width = 1; + required uint32 height = 2; + required uint32 frames = 3; + required double frames_per_second = 4; } message AudioPlayoutStats { - string kind = 1; - double synthesized_samples_duration = 2; - uint32 synthesized_samples_events = 3; - double total_samples_duration = 4; - double total_playout_delay = 5; - uint64 total_samples_count = 6; + required string kind = 1; + required double synthesized_samples_duration = 2; + required uint32 synthesized_samples_events = 3; + required double total_samples_duration = 4; + required double total_playout_delay = 5; + required uint64 total_samples_count = 6; } message PeerConnectionStats { - uint32 data_channels_opened = 1; - uint32 data_channels_closed = 2; + required uint32 data_channels_opened = 1; + required uint32 data_channels_closed = 2; } message DataChannelStats { - string label = 1; - string protocol = 2; - int32 data_channel_identifier = 3; + required string label = 1; + required string protocol = 2; + required int32 data_channel_identifier = 3; optional DataChannelState state = 4; - uint32 messages_sent = 5; - uint64 bytes_sent = 6; - uint32 messages_received = 7; - uint64 bytes_received = 8; + required uint32 messages_sent = 5; + required uint64 bytes_sent = 6; + required uint32 messages_received = 7; + required uint64 bytes_received = 8; } message TransportStats { - uint64 packets_sent = 1; - uint64 packets_received = 2; - uint64 bytes_sent = 3; - uint64 bytes_received = 4; - IceRole ice_role = 5; - string ice_local_username_fragment = 6; + required uint64 packets_sent = 1; + required uint64 packets_received = 2; + required uint64 bytes_sent = 3; + required uint64 bytes_received = 4; + required IceRole ice_role = 5; + required string ice_local_username_fragment = 6; optional DtlsTransportState dtls_state = 7; optional IceTransportState ice_state = 8; - string selected_candidate_pair_id = 9; - string local_certificate_id = 10; - string remote_certificate_id = 11; - string tls_version = 12; - string dtls_cipher = 13; - DtlsRole dtls_role = 14; - string srtp_cipher = 15; - uint32 selected_candidate_pair_changes = 16; + required string selected_candidate_pair_id = 9; + required string local_certificate_id = 10; + required string remote_certificate_id = 11; + required string tls_version = 12; + required string dtls_cipher = 13; + required DtlsRole dtls_role = 14; + required string srtp_cipher = 15; + required uint32 selected_candidate_pair_changes = 16; } message CandidatePairStats { - string transport_id = 1; - string local_candidate_id = 2; - string remote_candidate_id = 3; + required string transport_id = 1; + required string local_candidate_id = 2; + required string remote_candidate_id = 3; optional IceCandidatePairState state = 4; - bool nominated = 5; - uint64 packets_sent = 6; - uint64 packets_received = 7; - uint64 bytes_sent = 8; - uint64 bytes_received = 9; - double last_packet_sent_timestamp = 10; - double last_packet_received_timestamp = 11; - double total_round_trip_time = 12; - double current_round_trip_time = 13; - double available_outgoing_bitrate = 14; - double available_incoming_bitrate = 15; - uint64 requests_received = 16; - uint64 requests_sent = 17; - uint64 responses_received = 18; - uint64 responses_sent = 19; - uint64 consent_requests_sent = 20; - uint32 packets_discarded_on_send = 21; - uint64 bytes_discarded_on_send = 22; + required bool nominated = 5; + required uint64 packets_sent = 6; + required uint64 packets_received = 7; + required uint64 bytes_sent = 8; + required uint64 bytes_received = 9; + required double last_packet_sent_timestamp = 10; + required double last_packet_received_timestamp = 11; + required double total_round_trip_time = 12; + required double current_round_trip_time = 13; + required double available_outgoing_bitrate = 14; + required double available_incoming_bitrate = 15; + required uint64 requests_received = 16; + required uint64 requests_sent = 17; + required uint64 responses_received = 18; + required uint64 responses_sent = 19; + required uint64 consent_requests_sent = 20; + required uint32 packets_discarded_on_send = 21; + required uint64 bytes_discarded_on_send = 22; } message IceCandidateStats { - string transport_id = 1; - string address = 2; - int32 port = 3; - string protocol = 4; + required string transport_id = 1; + required string address = 2; + required int32 port = 3; + required string protocol = 4; optional IceCandidateType candidate_type = 5; - int32 priority = 6; - string url = 7; + required int32 priority = 6; + required string url = 7; optional IceServerTransportProtocol relay_protocol = 8; - string foundation = 9; - string related_address = 10; - int32 related_port = 11; - string username_fragment = 12; + required string foundation = 9; + required string related_address = 10; + required int32 related_port = 11; + required string username_fragment = 12; optional IceTcpCandidateType tcp_type = 13; } message CertificateStats { - string fingerprint = 1; - string fingerprint_algorithm = 2; - string base64_certificate = 3; - string issuer_certificate_id = 4; + required string fingerprint = 1; + required string fingerprint_algorithm = 2; + required string base64_certificate = 3; + required string issuer_certificate_id = 4; } diff --git a/livekit-ffi/protocol/track.proto b/livekit-ffi/protocol/track.proto index ee9e1ff04..4bebd399c 100644 --- a/livekit-ffi/protocol/track.proto +++ b/livekit-ffi/protocol/track.proto @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -syntax = "proto3"; +syntax = "proto2"; package livekit.proto; option csharp_namespace = "LiveKit.Proto"; @@ -23,30 +23,30 @@ import "stats.proto"; // Create a new VideoTrack from a VideoSource message CreateVideoTrackRequest { - string name = 1; - uint64 source_handle = 2; + required string name = 1; + required uint64 source_handle = 2; } message CreateVideoTrackResponse { - OwnedTrack track = 1; + required OwnedTrack track = 1; } // Create a new AudioTrack from a AudioSource message CreateAudioTrackRequest { - string name = 1; - uint64 source_handle = 2; + required string name = 1; + required uint64 source_handle = 2; } message CreateAudioTrackResponse { - OwnedTrack track = 1; + required OwnedTrack track = 1; } message GetStatsRequest { - uint64 track_handle = 1; + required uint64 track_handle = 1; } message GetStatsResponse { - uint64 async_id = 1; + required uint64 async_id = 1; } message GetStatsCallback { - uint64 async_id = 1; + required uint64 async_id = 1; optional string error = 2; repeated RtcStats stats = 3; } @@ -78,54 +78,54 @@ enum StreamState { } message TrackPublicationInfo { - string sid = 1; - string name = 2; - TrackKind kind = 3; - TrackSource source = 4; - bool simulcasted = 5; - uint32 width = 6; - uint32 height = 7; - string mime_type = 8; - bool muted = 9; - bool remote = 10; - EncryptionType encryption_type = 11; + required string sid = 1; + required string name = 2; + required TrackKind kind = 3; + required TrackSource source = 4; + required bool simulcasted = 5; + required uint32 width = 6; + required uint32 height = 7; + required string mime_type = 8; + required bool muted = 9; + required bool remote = 10; + required EncryptionType encryption_type = 11; } message OwnedTrackPublication { - FfiOwnedHandle handle = 1; - TrackPublicationInfo info = 2; + required FfiOwnedHandle handle = 1; + required TrackPublicationInfo info = 2; } message TrackInfo { - string sid = 1; - string name = 2; - TrackKind kind = 3; - StreamState stream_state = 4; - bool muted = 5; - bool remote = 6; + required string sid = 1; + required string name = 2; + required TrackKind kind = 3; + required StreamState stream_state = 4; + required bool muted = 5; + required bool remote = 6; } message OwnedTrack { - FfiOwnedHandle handle = 1; - TrackInfo info = 2; + required FfiOwnedHandle handle = 1; + required TrackInfo info = 2; } // Mute/UnMute a track message LocalTrackMuteRequest { - uint64 track_handle = 1; - bool mute = 2; + required uint64 track_handle = 1; + required bool mute = 2; } message LocalTrackMuteResponse { - bool muted = 1; + required bool muted = 1; } // Enable/Disable a remote track message EnableRemoteTrackRequest { - uint64 track_handle = 1; - bool enabled = 2; + required uint64 track_handle = 1; + required bool enabled = 2; } message EnableRemoteTrackResponse { - bool enabled = 1; + required bool enabled = 1; } diff --git a/livekit-ffi/protocol/video_frame.proto b/livekit-ffi/protocol/video_frame.proto index 090518b5f..1291ccc48 100644 --- a/livekit-ffi/protocol/video_frame.proto +++ b/livekit-ffi/protocol/video_frame.proto @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -syntax = "proto3"; +syntax = "proto2"; package livekit.proto; option csharp_namespace = "LiveKit.Proto"; @@ -23,53 +23,55 @@ import "track.proto"; // Create a new VideoStream // VideoStream is used to receive video frames from a track message NewVideoStreamRequest { - uint64 track_handle = 1; - VideoStreamType type = 2; + required uint64 track_handle = 1; + required VideoStreamType type = 2; // Get the frame on a specific format optional VideoBufferType format = 3; - bool normalize_stride = 4; // if true, stride will be set to width/chroma_width + required bool normalize_stride = 4; // if true, stride will be set to width/chroma_width } -message NewVideoStreamResponse { OwnedVideoStream stream = 1; } +message NewVideoStreamResponse { required OwnedVideoStream stream = 1; } // Request a video stream from a participant message VideoStreamFromParticipantRequest { - uint64 participant_handle = 1; - VideoStreamType type = 2; - TrackSource track_source = 3; + required uint64 participant_handle = 1; + required VideoStreamType type = 2; + required TrackSource track_source = 3; optional VideoBufferType format = 4; - bool normalize_stride = 5; + required bool normalize_stride = 5; } -message VideoStreamFromParticipantResponse { OwnedVideoStream stream = 1;} +message VideoStreamFromParticipantResponse { required OwnedVideoStream stream = 1;} // Create a new VideoSource // VideoSource is used to send video frame to a track message NewVideoSourceRequest { - VideoSourceType type = 1; + required VideoSourceType type = 1; // Used to determine which encodings to use + simulcast layers // Most of the time it corresponds to the source resolution - VideoSourceResolution resolution = 2; + required VideoSourceResolution resolution = 2; } -message NewVideoSourceResponse { OwnedVideoSource source = 1; } +message NewVideoSourceResponse { required OwnedVideoSource source = 1; } // Push a frame to a VideoSource message CaptureVideoFrameRequest { - uint64 source_handle = 1; - VideoBufferInfo buffer = 2; - int64 timestamp_us = 3; // In microseconds - VideoRotation rotation = 4; + required uint64 source_handle = 1; + required VideoBufferInfo buffer = 2; + required int64 timestamp_us = 3; // In microseconds + required VideoRotation rotation = 4; } message CaptureVideoFrameResponse {} message VideoConvertRequest { - bool flip_y = 1; - VideoBufferInfo buffer = 2; - VideoBufferType dst_type = 3; -} -message VideoConvertResponse { - optional string error = 1; - OwnedVideoBuffer buffer = 2; + required bool flip_y = 1; + required VideoBufferInfo buffer = 2; + required VideoBufferType dst_type = 3; +} +message VideoConvertResponse { + oneof message { + string error = 1; + OwnedVideoBuffer buffer = 2; + } } // @@ -77,9 +79,9 @@ message VideoConvertResponse { // message VideoResolution { - uint32 width = 1; - uint32 height = 2; - double frame_rate = 3; + required uint32 width = 1; + required uint32 height = 2; + required double frame_rate = 3; } enum VideoCodec { @@ -112,21 +114,21 @@ enum VideoBufferType { message VideoBufferInfo { message ComponentInfo { - uint64 data_ptr = 1; - uint32 stride = 2; - uint32 size = 3; + required uint64 data_ptr = 1; + required uint32 stride = 2; + required uint32 size = 3; } - VideoBufferType type = 1; - uint32 width = 2; - uint32 height = 3; - uint64 data_ptr = 4; - uint32 stride = 6; // only for packed formats + required VideoBufferType type = 1; + required uint32 width = 2; + required uint32 height = 3; + required uint64 data_ptr = 4; + required uint32 stride = 6; // only for packed formats repeated ComponentInfo components = 7; } message OwnedVideoBuffer { - FfiOwnedHandle handle = 1; - VideoBufferInfo info = 2; + required FfiOwnedHandle handle = 1; + required VideoBufferInfo info = 2; } // @@ -140,16 +142,16 @@ enum VideoStreamType { } message VideoStreamInfo { - VideoStreamType type = 1; + required VideoStreamType type = 1; } message OwnedVideoStream { - FfiOwnedHandle handle = 1; - VideoStreamInfo info = 2; + required FfiOwnedHandle handle = 1; + required VideoStreamInfo info = 2; } message VideoStreamEvent { - uint64 stream_handle = 1; + required uint64 stream_handle = 1; oneof message { VideoFrameReceived frame_received = 2; VideoStreamEOS eos = 3; @@ -157,9 +159,9 @@ message VideoStreamEvent { } message VideoFrameReceived { - OwnedVideoBuffer buffer = 1; - int64 timestamp_us = 2; // In microseconds - VideoRotation rotation = 3; + required OwnedVideoBuffer buffer = 1; + required int64 timestamp_us = 2; // In microseconds + required VideoRotation rotation = 3; } message VideoStreamEOS {} @@ -169,8 +171,8 @@ message VideoStreamEOS {} // message VideoSourceResolution { - uint32 width = 1; - uint32 height = 2; + required uint32 width = 1; + required uint32 height = 2; } enum VideoSourceType { @@ -178,10 +180,10 @@ enum VideoSourceType { } message VideoSourceInfo { - VideoSourceType type = 1; + required VideoSourceType type = 1; } message OwnedVideoSource { - FfiOwnedHandle handle = 1; - VideoSourceInfo info = 2; + required FfiOwnedHandle handle = 1; + required VideoSourceInfo info = 2; } diff --git a/livekit-ffi/src/conversion/room.rs b/livekit-ffi/src/conversion/room.rs index 199fd7343..514396ab7 100644 --- a/livekit-ffi/src/conversion/room.rs +++ b/livekit-ffi/src/conversion/room.rs @@ -130,7 +130,11 @@ impl From for ContinualGatheringPolicy { impl From for IceServer { fn from(value: proto::IceServer) -> Self { - Self { urls: value.urls, username: value.username, password: value.password } + Self { + urls: value.urls, + username: value.username.unwrap_or_default(), + password: value.password.unwrap_or_default(), + } } } @@ -156,9 +160,7 @@ impl From for RoomOptions { fn from(value: proto::RoomOptions) -> Self { let e2ee = value.e2ee.and_then(|opts| { let encryption_type = opts.encryption_type(); - let Some(provider_opts) = opts.key_provider_options else { - return None; - }; + let provider_opts = opts.key_provider_options; Some(E2eeOptions { encryption_type: encryption_type.into(), diff --git a/livekit-ffi/src/conversion/stats.rs b/livekit-ffi/src/conversion/stats.rs index 56d97258c..0ee214958 100644 --- a/livekit-ffi/src/conversion/stats.rs +++ b/livekit-ffi/src/conversion/stats.rs @@ -174,17 +174,17 @@ impl From for proto::RtcStats { impl From for proto::rtc_stats::Codec { fn from(value: rtc::CodecStats) -> Self { - Self { rtc: Some(value.rtc.into()), codec: Some(value.codec.into()) } + Self { rtc: value.rtc.into(), codec: value.codec.into() } } } impl From for proto::rtc_stats::InboundRtp { fn from(value: rtc::InboundRtpStats) -> Self { Self { - rtc: Some(value.rtc.into()), - stream: Some(value.stream.into()), - received: Some(value.received.into()), - inbound: Some(value.inbound.into()), + rtc: value.rtc.into(), + stream: value.stream.into(), + received: value.received.into(), + inbound: value.inbound.into(), } } } @@ -192,10 +192,10 @@ impl From for proto::rtc_stats::InboundRtp { impl From for proto::rtc_stats::OutboundRtp { fn from(value: rtc::OutboundRtpStats) -> Self { Self { - rtc: Some(value.rtc.into()), - stream: Some(value.stream.into()), - sent: Some(value.sent.into()), - outbound: Some(value.outbound.into()), + rtc: value.rtc.into(), + stream: value.stream.into(), + sent: value.sent.into(), + outbound: value.outbound.into(), } } } @@ -203,10 +203,10 @@ impl From for proto::rtc_stats::OutboundRtp { impl From for proto::rtc_stats::RemoteInboundRtp { fn from(value: rtc::RemoteInboundRtpStats) -> Self { Self { - rtc: Some(value.rtc.into()), - stream: Some(value.stream.into()), - received: Some(value.received.into()), - remote_inbound: Some(value.remote_inbound.into()), + rtc: value.rtc.into(), + stream: value.stream.into(), + received: value.received.into(), + remote_inbound: value.remote_inbound.into(), } } } @@ -214,10 +214,10 @@ impl From for proto::rtc_stats::RemoteInboundRtp { impl From for proto::rtc_stats::RemoteOutboundRtp { fn from(value: rtc::RemoteOutboundRtpStats) -> Self { Self { - rtc: Some(value.rtc.into()), - stream: Some(value.stream.into()), - sent: Some(value.sent.into()), - remote_outbound: Some(value.remote_outbound.into()), + rtc: value.rtc.into(), + stream: value.stream.into(), + sent: value.sent.into(), + remote_outbound: value.remote_outbound.into(), } } } @@ -225,59 +225,59 @@ impl From for proto::rtc_stats::RemoteOutboundRtp { impl From for proto::rtc_stats::MediaSource { fn from(value: rtc::MediaSourceStats) -> Self { Self { - rtc: Some(value.rtc.into()), - source: Some(value.source.into()), - audio: Some(value.audio.into()), - video: Some(value.video.into()), + rtc: value.rtc.into(), + source: value.source.into(), + audio: value.audio.into(), + video: value.video.into(), } } } impl From for proto::rtc_stats::MediaPlayout { fn from(value: rtc::MediaPlayoutStats) -> Self { - Self { rtc: Some(value.rtc.into()), audio_playout: Some(value.audio_playout.into()) } + Self { rtc: value.rtc.into(), audio_playout: value.audio_playout.into() } } } impl From for proto::rtc_stats::PeerConnection { fn from(value: rtc::PeerConnectionStats) -> Self { - Self { rtc: Some(value.rtc.into()), pc: Some(value.pc.into()) } + Self { rtc: value.rtc.into(), pc: value.pc.into() } } } impl From for proto::rtc_stats::DataChannel { fn from(value: rtc::DataChannelStats) -> Self { - Self { rtc: Some(value.rtc.into()), dc: Some(value.dc.into()) } + Self { rtc: value.rtc.into(), dc: value.dc.into() } } } impl From for proto::rtc_stats::Transport { fn from(value: rtc::TransportStats) -> Self { - Self { rtc: Some(value.rtc.into()), transport: Some(value.transport.into()) } + Self { rtc: value.rtc.into(), transport: value.transport.into() } } } impl From for proto::rtc_stats::CandidatePair { fn from(value: rtc::CandidatePairStats) -> Self { - Self { rtc: Some(value.rtc.into()), candidate_pair: Some(value.candidate_pair.into()) } + Self { rtc: value.rtc.into(), candidate_pair: value.candidate_pair.into() } } } impl From for proto::rtc_stats::LocalCandidate { fn from(value: rtc::LocalCandidateStats) -> Self { - Self { rtc: Some(value.rtc.into()), candidate: Some(value.local_candidate.into()) } + Self { rtc: value.rtc.into(), candidate: value.local_candidate.into() } } } impl From for proto::rtc_stats::RemoteCandidate { fn from(value: rtc::RemoteCandidateStats) -> Self { - Self { rtc: Some(value.rtc.into()), candidate: Some(value.remote_candidate.into()) } + Self { rtc: value.rtc.into(), candidate: value.remote_candidate.into() } } } impl From for proto::rtc_stats::Certificate { fn from(value: rtc::CertificateStats) -> Self { - Self { rtc: Some(value.rtc.into()), certificate: Some(value.certificate.into()) } + Self { rtc: value.rtc.into(), certificate: value.certificate.into() } } } @@ -423,7 +423,7 @@ impl From for proto::OutboundRtpStrea encoder_implementation: value.encoder_implementation, power_efficient_encoder: value.power_efficient_encoder, active: value.active, - scalibility_mode: value.scalibility_mode, + scalability_mode: value.scalibility_mode, } } } diff --git a/livekit-ffi/src/livekit.proto.rs b/livekit-ffi/src/livekit.proto.rs index b4cda5cdf..367e02741 100644 --- a/livekit-ffi/src/livekit.proto.rs +++ b/livekit-ffi/src/livekit.proto.rs @@ -3,13 +3,13 @@ #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FrameCryptor { - #[prost(string, tag="1")] + #[prost(string, required, tag="1")] pub participant_identity: ::prost::alloc::string::String, - #[prost(string, tag="2")] + #[prost(string, required, tag="2")] pub track_sid: ::prost::alloc::string::String, - #[prost(int32, tag="3")] + #[prost(int32, required, tag="3")] pub key_index: i32, - #[prost(bool, tag="4")] + #[prost(bool, required, tag="4")] pub enabled: bool, } #[allow(clippy::derive_partial_eq_without_eq)] @@ -18,26 +18,26 @@ pub struct KeyProviderOptions { /// Only specify if you want to use a shared_key #[prost(bytes="vec", optional, tag="1")] pub shared_key: ::core::option::Option<::prost::alloc::vec::Vec>, - #[prost(int32, tag="2")] + #[prost(int32, required, tag="2")] pub ratchet_window_size: i32, - #[prost(bytes="vec", tag="3")] + #[prost(bytes="vec", required, tag="3")] pub ratchet_salt: ::prost::alloc::vec::Vec, - /// -1 = no tolerence - #[prost(int32, tag="4")] + /// -1 = no tolerance + #[prost(int32, required, tag="4")] pub failure_tolerance: i32, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct E2eeOptions { - #[prost(enumeration="EncryptionType", tag="1")] + #[prost(enumeration="EncryptionType", required, tag="1")] pub encryption_type: i32, - #[prost(message, optional, tag="2")] - pub key_provider_options: ::core::option::Option, + #[prost(message, required, tag="2")] + pub key_provider_options: KeyProviderOptions, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct E2eeManagerSetEnabledRequest { - #[prost(bool, tag="1")] + #[prost(bool, required, tag="1")] pub enabled: bool, } #[allow(clippy::derive_partial_eq_without_eq)] @@ -57,11 +57,11 @@ pub struct E2eeManagerGetFrameCryptorsResponse { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FrameCryptorSetEnabledRequest { - #[prost(string, tag="1")] + #[prost(string, required, tag="1")] pub participant_identity: ::prost::alloc::string::String, - #[prost(string, tag="2")] + #[prost(string, required, tag="2")] pub track_sid: ::prost::alloc::string::String, - #[prost(bool, tag="3")] + #[prost(bool, required, tag="3")] pub enabled: bool, } #[allow(clippy::derive_partial_eq_without_eq)] @@ -71,11 +71,11 @@ pub struct FrameCryptorSetEnabledResponse { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FrameCryptorSetKeyIndexRequest { - #[prost(string, tag="1")] + #[prost(string, required, tag="1")] pub participant_identity: ::prost::alloc::string::String, - #[prost(string, tag="2")] + #[prost(string, required, tag="2")] pub track_sid: ::prost::alloc::string::String, - #[prost(int32, tag="3")] + #[prost(int32, required, tag="3")] pub key_index: i32, } #[allow(clippy::derive_partial_eq_without_eq)] @@ -85,9 +85,9 @@ pub struct FrameCryptorSetKeyIndexResponse { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SetSharedKeyRequest { - #[prost(bytes="vec", tag="1")] + #[prost(bytes="vec", required, tag="1")] pub shared_key: ::prost::alloc::vec::Vec, - #[prost(int32, tag="2")] + #[prost(int32, required, tag="2")] pub key_index: i32, } #[allow(clippy::derive_partial_eq_without_eq)] @@ -97,7 +97,7 @@ pub struct SetSharedKeyResponse { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RatchetSharedKeyRequest { - #[prost(int32, tag="1")] + #[prost(int32, required, tag="1")] pub key_index: i32, } #[allow(clippy::derive_partial_eq_without_eq)] @@ -109,7 +109,7 @@ pub struct RatchetSharedKeyResponse { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetSharedKeyRequest { - #[prost(int32, tag="1")] + #[prost(int32, required, tag="1")] pub key_index: i32, } #[allow(clippy::derive_partial_eq_without_eq)] @@ -121,11 +121,11 @@ pub struct GetSharedKeyResponse { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SetKeyRequest { - #[prost(string, tag="1")] + #[prost(string, required, tag="1")] pub participant_identity: ::prost::alloc::string::String, - #[prost(bytes="vec", tag="2")] + #[prost(bytes="vec", required, tag="2")] pub key: ::prost::alloc::vec::Vec, - #[prost(int32, tag="3")] + #[prost(int32, required, tag="3")] pub key_index: i32, } #[allow(clippy::derive_partial_eq_without_eq)] @@ -135,9 +135,9 @@ pub struct SetKeyResponse { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RatchetKeyRequest { - #[prost(string, tag="1")] + #[prost(string, required, tag="1")] pub participant_identity: ::prost::alloc::string::String, - #[prost(int32, tag="2")] + #[prost(int32, required, tag="2")] pub key_index: i32, } #[allow(clippy::derive_partial_eq_without_eq)] @@ -149,9 +149,9 @@ pub struct RatchetKeyResponse { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetKeyRequest { - #[prost(string, tag="1")] + #[prost(string, required, tag="1")] pub participant_identity: ::prost::alloc::string::String, - #[prost(int32, tag="2")] + #[prost(int32, required, tag="2")] pub key_index: i32, } #[allow(clippy::derive_partial_eq_without_eq)] @@ -163,7 +163,7 @@ pub struct GetKeyResponse { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct E2eeRequest { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub room_handle: u64, #[prost(oneof="e2ee_request::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11")] pub message: ::core::option::Option, @@ -312,7 +312,7 @@ impl EncryptionState { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiOwnedHandle { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub id: u64, } #[allow(clippy::derive_partial_eq_without_eq)] @@ -326,134 +326,134 @@ pub mod rtc_stats { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct Codec { - #[prost(message, optional, tag="1")] - pub rtc: ::core::option::Option, - #[prost(message, optional, tag="2")] - pub codec: ::core::option::Option, + #[prost(message, required, tag="1")] + pub rtc: super::RtcStatsData, + #[prost(message, required, tag="2")] + pub codec: super::CodecStats, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct InboundRtp { - #[prost(message, optional, tag="1")] - pub rtc: ::core::option::Option, - #[prost(message, optional, tag="2")] - pub stream: ::core::option::Option, - #[prost(message, optional, tag="3")] - pub received: ::core::option::Option, - #[prost(message, optional, tag="4")] - pub inbound: ::core::option::Option, + #[prost(message, required, tag="1")] + pub rtc: super::RtcStatsData, + #[prost(message, required, tag="2")] + pub stream: super::RtpStreamStats, + #[prost(message, required, tag="3")] + pub received: super::ReceivedRtpStreamStats, + #[prost(message, required, tag="4")] + pub inbound: super::InboundRtpStreamStats, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct OutboundRtp { - #[prost(message, optional, tag="1")] - pub rtc: ::core::option::Option, - #[prost(message, optional, tag="2")] - pub stream: ::core::option::Option, - #[prost(message, optional, tag="3")] - pub sent: ::core::option::Option, - #[prost(message, optional, tag="4")] - pub outbound: ::core::option::Option, + #[prost(message, required, tag="1")] + pub rtc: super::RtcStatsData, + #[prost(message, required, tag="2")] + pub stream: super::RtpStreamStats, + #[prost(message, required, tag="3")] + pub sent: super::SentRtpStreamStats, + #[prost(message, required, tag="4")] + pub outbound: super::OutboundRtpStreamStats, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RemoteInboundRtp { - #[prost(message, optional, tag="1")] - pub rtc: ::core::option::Option, - #[prost(message, optional, tag="2")] - pub stream: ::core::option::Option, - #[prost(message, optional, tag="3")] - pub received: ::core::option::Option, - #[prost(message, optional, tag="4")] - pub remote_inbound: ::core::option::Option, + #[prost(message, required, tag="1")] + pub rtc: super::RtcStatsData, + #[prost(message, required, tag="2")] + pub stream: super::RtpStreamStats, + #[prost(message, required, tag="3")] + pub received: super::ReceivedRtpStreamStats, + #[prost(message, required, tag="4")] + pub remote_inbound: super::RemoteInboundRtpStreamStats, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RemoteOutboundRtp { - #[prost(message, optional, tag="1")] - pub rtc: ::core::option::Option, - #[prost(message, optional, tag="2")] - pub stream: ::core::option::Option, - #[prost(message, optional, tag="3")] - pub sent: ::core::option::Option, - #[prost(message, optional, tag="4")] - pub remote_outbound: ::core::option::Option, + #[prost(message, required, tag="1")] + pub rtc: super::RtcStatsData, + #[prost(message, required, tag="2")] + pub stream: super::RtpStreamStats, + #[prost(message, required, tag="3")] + pub sent: super::SentRtpStreamStats, + #[prost(message, required, tag="4")] + pub remote_outbound: super::RemoteOutboundRtpStreamStats, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct MediaSource { - #[prost(message, optional, tag="1")] - pub rtc: ::core::option::Option, - #[prost(message, optional, tag="2")] - pub source: ::core::option::Option, - #[prost(message, optional, tag="3")] - pub audio: ::core::option::Option, - #[prost(message, optional, tag="4")] - pub video: ::core::option::Option, + #[prost(message, required, tag="1")] + pub rtc: super::RtcStatsData, + #[prost(message, required, tag="2")] + pub source: super::MediaSourceStats, + #[prost(message, required, tag="3")] + pub audio: super::AudioSourceStats, + #[prost(message, required, tag="4")] + pub video: super::VideoSourceStats, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct MediaPlayout { - #[prost(message, optional, tag="1")] - pub rtc: ::core::option::Option, - #[prost(message, optional, tag="2")] - pub audio_playout: ::core::option::Option, + #[prost(message, required, tag="1")] + pub rtc: super::RtcStatsData, + #[prost(message, required, tag="2")] + pub audio_playout: super::AudioPlayoutStats, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PeerConnection { - #[prost(message, optional, tag="1")] - pub rtc: ::core::option::Option, - #[prost(message, optional, tag="2")] - pub pc: ::core::option::Option, + #[prost(message, required, tag="1")] + pub rtc: super::RtcStatsData, + #[prost(message, required, tag="2")] + pub pc: super::PeerConnectionStats, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct DataChannel { - #[prost(message, optional, tag="1")] - pub rtc: ::core::option::Option, - #[prost(message, optional, tag="2")] - pub dc: ::core::option::Option, + #[prost(message, required, tag="1")] + pub rtc: super::RtcStatsData, + #[prost(message, required, tag="2")] + pub dc: super::DataChannelStats, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct Transport { - #[prost(message, optional, tag="1")] - pub rtc: ::core::option::Option, - #[prost(message, optional, tag="2")] - pub transport: ::core::option::Option, + #[prost(message, required, tag="1")] + pub rtc: super::RtcStatsData, + #[prost(message, required, tag="2")] + pub transport: super::TransportStats, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CandidatePair { - #[prost(message, optional, tag="1")] - pub rtc: ::core::option::Option, - #[prost(message, optional, tag="2")] - pub candidate_pair: ::core::option::Option, + #[prost(message, required, tag="1")] + pub rtc: super::RtcStatsData, + #[prost(message, required, tag="2")] + pub candidate_pair: super::CandidatePairStats, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct LocalCandidate { - #[prost(message, optional, tag="1")] - pub rtc: ::core::option::Option, - #[prost(message, optional, tag="2")] - pub candidate: ::core::option::Option, + #[prost(message, required, tag="1")] + pub rtc: super::RtcStatsData, + #[prost(message, required, tag="2")] + pub candidate: super::IceCandidateStats, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RemoteCandidate { - #[prost(message, optional, tag="1")] - pub rtc: ::core::option::Option, - #[prost(message, optional, tag="2")] - pub candidate: ::core::option::Option, + #[prost(message, required, tag="1")] + pub rtc: super::RtcStatsData, + #[prost(message, required, tag="2")] + pub candidate: super::IceCandidateStats, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct Certificate { - #[prost(message, optional, tag="1")] - pub rtc: ::core::option::Option, - #[prost(message, optional, tag="2")] - pub certificate: ::core::option::Option, + #[prost(message, required, tag="1")] + pub rtc: super::RtcStatsData, + #[prost(message, required, tag="2")] + pub certificate: super::CertificateStats, } /// Deprecated #[allow(clippy::derive_partial_eq_without_eq)] @@ -498,457 +498,457 @@ pub mod rtc_stats { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RtcStatsData { - #[prost(string, tag="1")] + #[prost(string, required, tag="1")] pub id: ::prost::alloc::string::String, - #[prost(int64, tag="2")] + #[prost(int64, required, tag="2")] pub timestamp: i64, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CodecStats { - #[prost(uint32, tag="1")] + #[prost(uint32, required, tag="1")] pub payload_type: u32, - #[prost(string, tag="2")] + #[prost(string, required, tag="2")] pub transport_id: ::prost::alloc::string::String, - #[prost(string, tag="3")] + #[prost(string, required, tag="3")] pub mime_type: ::prost::alloc::string::String, - #[prost(uint32, tag="4")] + #[prost(uint32, required, tag="4")] pub clock_rate: u32, - #[prost(uint32, tag="5")] + #[prost(uint32, required, tag="5")] pub channels: u32, - #[prost(string, tag="6")] + #[prost(string, required, tag="6")] pub sdp_fmtp_line: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RtpStreamStats { - #[prost(uint32, tag="1")] + #[prost(uint32, required, tag="1")] pub ssrc: u32, - #[prost(string, tag="2")] + #[prost(string, required, tag="2")] pub kind: ::prost::alloc::string::String, - #[prost(string, tag="3")] + #[prost(string, required, tag="3")] pub transport_id: ::prost::alloc::string::String, - #[prost(string, tag="4")] + #[prost(string, required, tag="4")] pub codec_id: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ReceivedRtpStreamStats { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub packets_received: u64, - #[prost(int64, tag="2")] + #[prost(int64, required, tag="2")] pub packets_lost: i64, - #[prost(double, tag="3")] + #[prost(double, required, tag="3")] pub jitter: f64, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct InboundRtpStreamStats { - #[prost(string, tag="1")] + #[prost(string, required, tag="1")] pub track_identifier: ::prost::alloc::string::String, - #[prost(string, tag="2")] + #[prost(string, required, tag="2")] pub mid: ::prost::alloc::string::String, - #[prost(string, tag="3")] + #[prost(string, required, tag="3")] pub remote_id: ::prost::alloc::string::String, - #[prost(uint32, tag="4")] + #[prost(uint32, required, tag="4")] pub frames_decoded: u32, - #[prost(uint32, tag="5")] + #[prost(uint32, required, tag="5")] pub key_frames_decoded: u32, - #[prost(uint32, tag="6")] + #[prost(uint32, required, tag="6")] pub frames_rendered: u32, - #[prost(uint32, tag="7")] + #[prost(uint32, required, tag="7")] pub frames_dropped: u32, - #[prost(uint32, tag="8")] + #[prost(uint32, required, tag="8")] pub frame_width: u32, - #[prost(uint32, tag="9")] + #[prost(uint32, required, tag="9")] pub frame_height: u32, - #[prost(double, tag="10")] + #[prost(double, required, tag="10")] pub frames_per_second: f64, - #[prost(uint64, tag="11")] + #[prost(uint64, required, tag="11")] pub qp_sum: u64, - #[prost(double, tag="12")] + #[prost(double, required, tag="12")] pub total_decode_time: f64, - #[prost(double, tag="13")] + #[prost(double, required, tag="13")] pub total_inter_frame_delay: f64, - #[prost(double, tag="14")] + #[prost(double, required, tag="14")] pub total_squared_inter_frame_delay: f64, - #[prost(uint32, tag="15")] + #[prost(uint32, required, tag="15")] pub pause_count: u32, - #[prost(double, tag="16")] + #[prost(double, required, tag="16")] pub total_pause_duration: f64, - #[prost(uint32, tag="17")] + #[prost(uint32, required, tag="17")] pub freeze_count: u32, - #[prost(double, tag="18")] + #[prost(double, required, tag="18")] pub total_freeze_duration: f64, - #[prost(double, tag="19")] + #[prost(double, required, tag="19")] pub last_packet_received_timestamp: f64, - #[prost(uint64, tag="20")] + #[prost(uint64, required, tag="20")] pub header_bytes_received: u64, - #[prost(uint64, tag="21")] + #[prost(uint64, required, tag="21")] pub packets_discarded: u64, - #[prost(uint64, tag="22")] + #[prost(uint64, required, tag="22")] pub fec_bytes_received: u64, - #[prost(uint64, tag="23")] + #[prost(uint64, required, tag="23")] pub fec_packets_received: u64, - #[prost(uint64, tag="24")] + #[prost(uint64, required, tag="24")] pub fec_packets_discarded: u64, - #[prost(uint64, tag="25")] + #[prost(uint64, required, tag="25")] pub bytes_received: u64, - #[prost(uint32, tag="26")] + #[prost(uint32, required, tag="26")] pub nack_count: u32, - #[prost(uint32, tag="27")] + #[prost(uint32, required, tag="27")] pub fir_count: u32, - #[prost(uint32, tag="28")] + #[prost(uint32, required, tag="28")] pub pli_count: u32, - #[prost(double, tag="29")] + #[prost(double, required, tag="29")] pub total_processing_delay: f64, - #[prost(double, tag="30")] + #[prost(double, required, tag="30")] pub estimated_playout_timestamp: f64, - #[prost(double, tag="31")] + #[prost(double, required, tag="31")] pub jitter_buffer_delay: f64, - #[prost(double, tag="32")] + #[prost(double, required, tag="32")] pub jitter_buffer_target_delay: f64, - #[prost(uint64, tag="33")] + #[prost(uint64, required, tag="33")] pub jitter_buffer_emitted_count: u64, - #[prost(double, tag="34")] + #[prost(double, required, tag="34")] pub jitter_buffer_minimum_delay: f64, - #[prost(uint64, tag="35")] + #[prost(uint64, required, tag="35")] pub total_samples_received: u64, - #[prost(uint64, tag="36")] + #[prost(uint64, required, tag="36")] pub concealed_samples: u64, - #[prost(uint64, tag="37")] + #[prost(uint64, required, tag="37")] pub silent_concealed_samples: u64, - #[prost(uint64, tag="38")] + #[prost(uint64, required, tag="38")] pub concealment_events: u64, - #[prost(uint64, tag="39")] + #[prost(uint64, required, tag="39")] pub inserted_samples_for_deceleration: u64, - #[prost(uint64, tag="40")] + #[prost(uint64, required, tag="40")] pub removed_samples_for_acceleration: u64, - #[prost(double, tag="41")] + #[prost(double, required, tag="41")] pub audio_level: f64, - #[prost(double, tag="42")] + #[prost(double, required, tag="42")] pub total_audio_energy: f64, - #[prost(double, tag="43")] + #[prost(double, required, tag="43")] pub total_samples_duration: f64, - #[prost(uint64, tag="44")] + #[prost(uint64, required, tag="44")] pub frames_received: u64, - #[prost(string, tag="45")] + #[prost(string, required, tag="45")] pub decoder_implementation: ::prost::alloc::string::String, - #[prost(string, tag="46")] + #[prost(string, required, tag="46")] pub playout_id: ::prost::alloc::string::String, - #[prost(bool, tag="47")] + #[prost(bool, required, tag="47")] pub power_efficient_decoder: bool, - #[prost(uint64, tag="48")] + #[prost(uint64, required, tag="48")] pub frames_assembled_from_multiple_packets: u64, - #[prost(double, tag="49")] + #[prost(double, required, tag="49")] pub total_assembly_time: f64, - #[prost(uint64, tag="50")] + #[prost(uint64, required, tag="50")] pub retransmitted_packets_received: u64, - #[prost(uint64, tag="51")] + #[prost(uint64, required, tag="51")] pub retransmitted_bytes_received: u64, - #[prost(uint32, tag="52")] + #[prost(uint32, required, tag="52")] pub rtx_ssrc: u32, - #[prost(uint32, tag="53")] + #[prost(uint32, required, tag="53")] pub fec_ssrc: u32, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SentRtpStreamStats { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub packets_sent: u64, - #[prost(uint64, tag="2")] + #[prost(uint64, required, tag="2")] pub bytes_sent: u64, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct OutboundRtpStreamStats { - #[prost(string, tag="1")] + #[prost(string, required, tag="1")] pub mid: ::prost::alloc::string::String, - #[prost(string, tag="2")] + #[prost(string, required, tag="2")] pub media_source_id: ::prost::alloc::string::String, - #[prost(string, tag="3")] + #[prost(string, required, tag="3")] pub remote_id: ::prost::alloc::string::String, - #[prost(string, tag="4")] + #[prost(string, required, tag="4")] pub rid: ::prost::alloc::string::String, - #[prost(uint64, tag="5")] + #[prost(uint64, required, tag="5")] pub header_bytes_sent: u64, - #[prost(uint64, tag="6")] + #[prost(uint64, required, tag="6")] pub retransmitted_packets_sent: u64, - #[prost(uint64, tag="7")] + #[prost(uint64, required, tag="7")] pub retransmitted_bytes_sent: u64, - #[prost(uint32, tag="8")] + #[prost(uint32, required, tag="8")] pub rtx_ssrc: u32, - #[prost(double, tag="9")] + #[prost(double, required, tag="9")] pub target_bitrate: f64, - #[prost(uint64, tag="10")] + #[prost(uint64, required, tag="10")] pub total_encoded_bytes_target: u64, - #[prost(uint32, tag="11")] + #[prost(uint32, required, tag="11")] pub frame_width: u32, - #[prost(uint32, tag="12")] + #[prost(uint32, required, tag="12")] pub frame_height: u32, - #[prost(double, tag="13")] + #[prost(double, required, tag="13")] pub frames_per_second: f64, - #[prost(uint32, tag="14")] + #[prost(uint32, required, tag="14")] pub frames_sent: u32, - #[prost(uint32, tag="15")] + #[prost(uint32, required, tag="15")] pub huge_frames_sent: u32, - #[prost(uint32, tag="16")] + #[prost(uint32, required, tag="16")] pub frames_encoded: u32, - #[prost(uint32, tag="17")] + #[prost(uint32, required, tag="17")] pub key_frames_encoded: u32, - #[prost(uint64, tag="18")] + #[prost(uint64, required, tag="18")] pub qp_sum: u64, - #[prost(double, tag="19")] + #[prost(double, required, tag="19")] pub total_encode_time: f64, - #[prost(double, tag="20")] + #[prost(double, required, tag="20")] pub total_packet_send_delay: f64, - #[prost(enumeration="QualityLimitationReason", tag="21")] + #[prost(enumeration="QualityLimitationReason", required, tag="21")] pub quality_limitation_reason: i32, #[prost(map="string, double", tag="22")] pub quality_limitation_durations: ::std::collections::HashMap<::prost::alloc::string::String, f64>, - #[prost(uint32, tag="23")] + #[prost(uint32, required, tag="23")] pub quality_limitation_resolution_changes: u32, - #[prost(uint32, tag="24")] + #[prost(uint32, required, tag="24")] pub nack_count: u32, - #[prost(uint32, tag="25")] + #[prost(uint32, required, tag="25")] pub fir_count: u32, - #[prost(uint32, tag="26")] + #[prost(uint32, required, tag="26")] pub pli_count: u32, - #[prost(string, tag="27")] + #[prost(string, required, tag="27")] pub encoder_implementation: ::prost::alloc::string::String, - #[prost(bool, tag="28")] + #[prost(bool, required, tag="28")] pub power_efficient_encoder: bool, - #[prost(bool, tag="29")] + #[prost(bool, required, tag="29")] pub active: bool, - #[prost(string, tag="30")] - pub scalibility_mode: ::prost::alloc::string::String, + #[prost(string, required, tag="30")] + pub scalability_mode: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RemoteInboundRtpStreamStats { - #[prost(string, tag="1")] + #[prost(string, required, tag="1")] pub local_id: ::prost::alloc::string::String, - #[prost(double, tag="2")] + #[prost(double, required, tag="2")] pub round_trip_time: f64, - #[prost(double, tag="3")] + #[prost(double, required, tag="3")] pub total_round_trip_time: f64, - #[prost(double, tag="4")] + #[prost(double, required, tag="4")] pub fraction_lost: f64, - #[prost(uint64, tag="5")] + #[prost(uint64, required, tag="5")] pub round_trip_time_measurements: u64, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RemoteOutboundRtpStreamStats { - #[prost(string, tag="1")] + #[prost(string, required, tag="1")] pub local_id: ::prost::alloc::string::String, - #[prost(double, tag="2")] + #[prost(double, required, tag="2")] pub remote_timestamp: f64, - #[prost(uint64, tag="3")] + #[prost(uint64, required, tag="3")] pub reports_sent: u64, - #[prost(double, tag="4")] + #[prost(double, required, tag="4")] pub round_trip_time: f64, - #[prost(double, tag="5")] + #[prost(double, required, tag="5")] pub total_round_trip_time: f64, - #[prost(uint64, tag="6")] + #[prost(uint64, required, tag="6")] pub round_trip_time_measurements: u64, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct MediaSourceStats { - #[prost(string, tag="1")] + #[prost(string, required, tag="1")] pub track_identifier: ::prost::alloc::string::String, - #[prost(string, tag="2")] + #[prost(string, required, tag="2")] pub kind: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct AudioSourceStats { - #[prost(double, tag="1")] + #[prost(double, required, tag="1")] pub audio_level: f64, - #[prost(double, tag="2")] + #[prost(double, required, tag="2")] pub total_audio_energy: f64, - #[prost(double, tag="3")] + #[prost(double, required, tag="3")] pub total_samples_duration: f64, - #[prost(double, tag="4")] + #[prost(double, required, tag="4")] pub echo_return_loss: f64, - #[prost(double, tag="5")] + #[prost(double, required, tag="5")] pub echo_return_loss_enhancement: f64, - #[prost(double, tag="6")] + #[prost(double, required, tag="6")] pub dropped_samples_duration: f64, - #[prost(uint32, tag="7")] + #[prost(uint32, required, tag="7")] pub dropped_samples_events: u32, - #[prost(double, tag="8")] + #[prost(double, required, tag="8")] pub total_capture_delay: f64, - #[prost(uint64, tag="9")] + #[prost(uint64, required, tag="9")] pub total_samples_captured: u64, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct VideoSourceStats { - #[prost(uint32, tag="1")] + #[prost(uint32, required, tag="1")] pub width: u32, - #[prost(uint32, tag="2")] + #[prost(uint32, required, tag="2")] pub height: u32, - #[prost(uint32, tag="3")] + #[prost(uint32, required, tag="3")] pub frames: u32, - #[prost(double, tag="4")] + #[prost(double, required, tag="4")] pub frames_per_second: f64, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct AudioPlayoutStats { - #[prost(string, tag="1")] + #[prost(string, required, tag="1")] pub kind: ::prost::alloc::string::String, - #[prost(double, tag="2")] + #[prost(double, required, tag="2")] pub synthesized_samples_duration: f64, - #[prost(uint32, tag="3")] + #[prost(uint32, required, tag="3")] pub synthesized_samples_events: u32, - #[prost(double, tag="4")] + #[prost(double, required, tag="4")] pub total_samples_duration: f64, - #[prost(double, tag="5")] + #[prost(double, required, tag="5")] pub total_playout_delay: f64, - #[prost(uint64, tag="6")] + #[prost(uint64, required, tag="6")] pub total_samples_count: u64, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PeerConnectionStats { - #[prost(uint32, tag="1")] + #[prost(uint32, required, tag="1")] pub data_channels_opened: u32, - #[prost(uint32, tag="2")] + #[prost(uint32, required, tag="2")] pub data_channels_closed: u32, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct DataChannelStats { - #[prost(string, tag="1")] + #[prost(string, required, tag="1")] pub label: ::prost::alloc::string::String, - #[prost(string, tag="2")] + #[prost(string, required, tag="2")] pub protocol: ::prost::alloc::string::String, - #[prost(int32, tag="3")] + #[prost(int32, required, tag="3")] pub data_channel_identifier: i32, #[prost(enumeration="DataChannelState", optional, tag="4")] pub state: ::core::option::Option, - #[prost(uint32, tag="5")] + #[prost(uint32, required, tag="5")] pub messages_sent: u32, - #[prost(uint64, tag="6")] + #[prost(uint64, required, tag="6")] pub bytes_sent: u64, - #[prost(uint32, tag="7")] + #[prost(uint32, required, tag="7")] pub messages_received: u32, - #[prost(uint64, tag="8")] + #[prost(uint64, required, tag="8")] pub bytes_received: u64, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TransportStats { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub packets_sent: u64, - #[prost(uint64, tag="2")] + #[prost(uint64, required, tag="2")] pub packets_received: u64, - #[prost(uint64, tag="3")] + #[prost(uint64, required, tag="3")] pub bytes_sent: u64, - #[prost(uint64, tag="4")] + #[prost(uint64, required, tag="4")] pub bytes_received: u64, - #[prost(enumeration="IceRole", tag="5")] + #[prost(enumeration="IceRole", required, tag="5")] pub ice_role: i32, - #[prost(string, tag="6")] + #[prost(string, required, tag="6")] pub ice_local_username_fragment: ::prost::alloc::string::String, #[prost(enumeration="DtlsTransportState", optional, tag="7")] pub dtls_state: ::core::option::Option, #[prost(enumeration="IceTransportState", optional, tag="8")] pub ice_state: ::core::option::Option, - #[prost(string, tag="9")] + #[prost(string, required, tag="9")] pub selected_candidate_pair_id: ::prost::alloc::string::String, - #[prost(string, tag="10")] + #[prost(string, required, tag="10")] pub local_certificate_id: ::prost::alloc::string::String, - #[prost(string, tag="11")] + #[prost(string, required, tag="11")] pub remote_certificate_id: ::prost::alloc::string::String, - #[prost(string, tag="12")] + #[prost(string, required, tag="12")] pub tls_version: ::prost::alloc::string::String, - #[prost(string, tag="13")] + #[prost(string, required, tag="13")] pub dtls_cipher: ::prost::alloc::string::String, - #[prost(enumeration="DtlsRole", tag="14")] + #[prost(enumeration="DtlsRole", required, tag="14")] pub dtls_role: i32, - #[prost(string, tag="15")] + #[prost(string, required, tag="15")] pub srtp_cipher: ::prost::alloc::string::String, - #[prost(uint32, tag="16")] + #[prost(uint32, required, tag="16")] pub selected_candidate_pair_changes: u32, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CandidatePairStats { - #[prost(string, tag="1")] + #[prost(string, required, tag="1")] pub transport_id: ::prost::alloc::string::String, - #[prost(string, tag="2")] + #[prost(string, required, tag="2")] pub local_candidate_id: ::prost::alloc::string::String, - #[prost(string, tag="3")] + #[prost(string, required, tag="3")] pub remote_candidate_id: ::prost::alloc::string::String, #[prost(enumeration="IceCandidatePairState", optional, tag="4")] pub state: ::core::option::Option, - #[prost(bool, tag="5")] + #[prost(bool, required, tag="5")] pub nominated: bool, - #[prost(uint64, tag="6")] + #[prost(uint64, required, tag="6")] pub packets_sent: u64, - #[prost(uint64, tag="7")] + #[prost(uint64, required, tag="7")] pub packets_received: u64, - #[prost(uint64, tag="8")] + #[prost(uint64, required, tag="8")] pub bytes_sent: u64, - #[prost(uint64, tag="9")] + #[prost(uint64, required, tag="9")] pub bytes_received: u64, - #[prost(double, tag="10")] + #[prost(double, required, tag="10")] pub last_packet_sent_timestamp: f64, - #[prost(double, tag="11")] + #[prost(double, required, tag="11")] pub last_packet_received_timestamp: f64, - #[prost(double, tag="12")] + #[prost(double, required, tag="12")] pub total_round_trip_time: f64, - #[prost(double, tag="13")] + #[prost(double, required, tag="13")] pub current_round_trip_time: f64, - #[prost(double, tag="14")] + #[prost(double, required, tag="14")] pub available_outgoing_bitrate: f64, - #[prost(double, tag="15")] + #[prost(double, required, tag="15")] pub available_incoming_bitrate: f64, - #[prost(uint64, tag="16")] + #[prost(uint64, required, tag="16")] pub requests_received: u64, - #[prost(uint64, tag="17")] + #[prost(uint64, required, tag="17")] pub requests_sent: u64, - #[prost(uint64, tag="18")] + #[prost(uint64, required, tag="18")] pub responses_received: u64, - #[prost(uint64, tag="19")] + #[prost(uint64, required, tag="19")] pub responses_sent: u64, - #[prost(uint64, tag="20")] + #[prost(uint64, required, tag="20")] pub consent_requests_sent: u64, - #[prost(uint32, tag="21")] + #[prost(uint32, required, tag="21")] pub packets_discarded_on_send: u32, - #[prost(uint64, tag="22")] + #[prost(uint64, required, tag="22")] pub bytes_discarded_on_send: u64, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct IceCandidateStats { - #[prost(string, tag="1")] + #[prost(string, required, tag="1")] pub transport_id: ::prost::alloc::string::String, - #[prost(string, tag="2")] + #[prost(string, required, tag="2")] pub address: ::prost::alloc::string::String, - #[prost(int32, tag="3")] + #[prost(int32, required, tag="3")] pub port: i32, - #[prost(string, tag="4")] + #[prost(string, required, tag="4")] pub protocol: ::prost::alloc::string::String, #[prost(enumeration="IceCandidateType", optional, tag="5")] pub candidate_type: ::core::option::Option, - #[prost(int32, tag="6")] + #[prost(int32, required, tag="6")] pub priority: i32, - #[prost(string, tag="7")] + #[prost(string, required, tag="7")] pub url: ::prost::alloc::string::String, #[prost(enumeration="IceServerTransportProtocol", optional, tag="8")] pub relay_protocol: ::core::option::Option, - #[prost(string, tag="9")] + #[prost(string, required, tag="9")] pub foundation: ::prost::alloc::string::String, - #[prost(string, tag="10")] + #[prost(string, required, tag="10")] pub related_address: ::prost::alloc::string::String, - #[prost(int32, tag="11")] + #[prost(int32, required, tag="11")] pub related_port: i32, - #[prost(string, tag="12")] + #[prost(string, required, tag="12")] pub username_fragment: ::prost::alloc::string::String, #[prost(enumeration="IceTcpCandidateType", optional, tag="13")] pub tcp_type: ::core::option::Option, @@ -956,13 +956,13 @@ pub struct IceCandidateStats { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CertificateStats { - #[prost(string, tag="1")] + #[prost(string, required, tag="1")] pub fingerprint: ::prost::alloc::string::String, - #[prost(string, tag="2")] + #[prost(string, required, tag="2")] pub fingerprint_algorithm: ::prost::alloc::string::String, - #[prost(string, tag="3")] + #[prost(string, required, tag="3")] pub base64_certificate: ::prost::alloc::string::String, - #[prost(string, tag="4")] + #[prost(string, required, tag="4")] pub issuer_certificate_id: ::prost::alloc::string::String, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] @@ -1292,48 +1292,48 @@ impl IceTcpCandidateType { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CreateVideoTrackRequest { - #[prost(string, tag="1")] + #[prost(string, required, tag="1")] pub name: ::prost::alloc::string::String, - #[prost(uint64, tag="2")] + #[prost(uint64, required, tag="2")] pub source_handle: u64, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CreateVideoTrackResponse { - #[prost(message, optional, tag="1")] - pub track: ::core::option::Option, + #[prost(message, required, tag="1")] + pub track: OwnedTrack, } /// Create a new AudioTrack from a AudioSource #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CreateAudioTrackRequest { - #[prost(string, tag="1")] + #[prost(string, required, tag="1")] pub name: ::prost::alloc::string::String, - #[prost(uint64, tag="2")] + #[prost(uint64, required, tag="2")] pub source_handle: u64, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CreateAudioTrackResponse { - #[prost(message, optional, tag="1")] - pub track: ::core::option::Option, + #[prost(message, required, tag="1")] + pub track: OwnedTrack, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetStatsRequest { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub track_handle: u64, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetStatsResponse { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub async_id: u64, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetStatsCallback { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub async_id: u64, #[prost(string, optional, tag="2")] pub error: ::core::option::Option<::prost::alloc::string::String>, @@ -1351,89 +1351,89 @@ pub struct TrackEvent { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackPublicationInfo { - #[prost(string, tag="1")] + #[prost(string, required, tag="1")] pub sid: ::prost::alloc::string::String, - #[prost(string, tag="2")] + #[prost(string, required, tag="2")] pub name: ::prost::alloc::string::String, - #[prost(enumeration="TrackKind", tag="3")] + #[prost(enumeration="TrackKind", required, tag="3")] pub kind: i32, - #[prost(enumeration="TrackSource", tag="4")] + #[prost(enumeration="TrackSource", required, tag="4")] pub source: i32, - #[prost(bool, tag="5")] + #[prost(bool, required, tag="5")] pub simulcasted: bool, - #[prost(uint32, tag="6")] + #[prost(uint32, required, tag="6")] pub width: u32, - #[prost(uint32, tag="7")] + #[prost(uint32, required, tag="7")] pub height: u32, - #[prost(string, tag="8")] + #[prost(string, required, tag="8")] pub mime_type: ::prost::alloc::string::String, - #[prost(bool, tag="9")] + #[prost(bool, required, tag="9")] pub muted: bool, - #[prost(bool, tag="10")] + #[prost(bool, required, tag="10")] pub remote: bool, - #[prost(enumeration="EncryptionType", tag="11")] + #[prost(enumeration="EncryptionType", required, tag="11")] pub encryption_type: i32, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct OwnedTrackPublication { - #[prost(message, optional, tag="1")] - pub handle: ::core::option::Option, - #[prost(message, optional, tag="2")] - pub info: ::core::option::Option, + #[prost(message, required, tag="1")] + pub handle: FfiOwnedHandle, + #[prost(message, required, tag="2")] + pub info: TrackPublicationInfo, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackInfo { - #[prost(string, tag="1")] + #[prost(string, required, tag="1")] pub sid: ::prost::alloc::string::String, - #[prost(string, tag="2")] + #[prost(string, required, tag="2")] pub name: ::prost::alloc::string::String, - #[prost(enumeration="TrackKind", tag="3")] + #[prost(enumeration="TrackKind", required, tag="3")] pub kind: i32, - #[prost(enumeration="StreamState", tag="4")] + #[prost(enumeration="StreamState", required, tag="4")] pub stream_state: i32, - #[prost(bool, tag="5")] + #[prost(bool, required, tag="5")] pub muted: bool, - #[prost(bool, tag="6")] + #[prost(bool, required, tag="6")] pub remote: bool, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct OwnedTrack { - #[prost(message, optional, tag="1")] - pub handle: ::core::option::Option, - #[prost(message, optional, tag="2")] - pub info: ::core::option::Option, + #[prost(message, required, tag="1")] + pub handle: FfiOwnedHandle, + #[prost(message, required, tag="2")] + pub info: TrackInfo, } /// Mute/UnMute a track #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct LocalTrackMuteRequest { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub track_handle: u64, - #[prost(bool, tag="2")] + #[prost(bool, required, tag="2")] pub mute: bool, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct LocalTrackMuteResponse { - #[prost(bool, tag="1")] + #[prost(bool, required, tag="1")] pub muted: bool, } /// Enable/Disable a remote track #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct EnableRemoteTrackRequest { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub track_handle: u64, - #[prost(bool, tag="2")] + #[prost(bool, required, tag="2")] pub enabled: bool, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct EnableRemoteTrackResponse { - #[prost(bool, tag="1")] + #[prost(bool, required, tag="1")] pub enabled: bool, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] @@ -1532,26 +1532,26 @@ impl StreamState { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ParticipantInfo { - #[prost(string, tag="1")] + #[prost(string, required, tag="1")] pub sid: ::prost::alloc::string::String, - #[prost(string, tag="2")] + #[prost(string, required, tag="2")] pub name: ::prost::alloc::string::String, - #[prost(string, tag="3")] + #[prost(string, required, tag="3")] pub identity: ::prost::alloc::string::String, - #[prost(string, tag="4")] + #[prost(string, required, tag="4")] pub metadata: ::prost::alloc::string::String, #[prost(map="string, string", tag="5")] pub attributes: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, - #[prost(enumeration="ParticipantKind", tag="6")] + #[prost(enumeration="ParticipantKind", required, tag="6")] pub kind: i32, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct OwnedParticipant { - #[prost(message, optional, tag="1")] - pub handle: ::core::option::Option, - #[prost(message, optional, tag="2")] - pub info: ::core::option::Option, + #[prost(message, required, tag="1")] + pub handle: FfiOwnedHandle, + #[prost(message, required, tag="2")] + pub info: ParticipantInfo, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] @@ -1593,74 +1593,74 @@ impl ParticipantKind { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct NewVideoStreamRequest { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub track_handle: u64, - #[prost(enumeration="VideoStreamType", tag="2")] + #[prost(enumeration="VideoStreamType", required, tag="2")] pub r#type: i32, /// Get the frame on a specific format #[prost(enumeration="VideoBufferType", optional, tag="3")] pub format: ::core::option::Option, /// if true, stride will be set to width/chroma_width - #[prost(bool, tag="4")] + #[prost(bool, required, tag="4")] pub normalize_stride: bool, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct NewVideoStreamResponse { - #[prost(message, optional, tag="1")] - pub stream: ::core::option::Option, + #[prost(message, required, tag="1")] + pub stream: OwnedVideoStream, } /// Request a video stream from a participant #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct VideoStreamFromParticipantRequest { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub participant_handle: u64, - #[prost(enumeration="VideoStreamType", tag="2")] + #[prost(enumeration="VideoStreamType", required, tag="2")] pub r#type: i32, - #[prost(enumeration="TrackSource", tag="3")] + #[prost(enumeration="TrackSource", required, tag="3")] pub track_source: i32, #[prost(enumeration="VideoBufferType", optional, tag="4")] pub format: ::core::option::Option, - #[prost(bool, tag="5")] + #[prost(bool, required, tag="5")] pub normalize_stride: bool, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct VideoStreamFromParticipantResponse { - #[prost(message, optional, tag="1")] - pub stream: ::core::option::Option, + #[prost(message, required, tag="1")] + pub stream: OwnedVideoStream, } /// Create a new VideoSource /// VideoSource is used to send video frame to a track #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct NewVideoSourceRequest { - #[prost(enumeration="VideoSourceType", tag="1")] + #[prost(enumeration="VideoSourceType", required, tag="1")] pub r#type: i32, /// Used to determine which encodings to use + simulcast layers /// Most of the time it corresponds to the source resolution - #[prost(message, optional, tag="2")] - pub resolution: ::core::option::Option, + #[prost(message, required, tag="2")] + pub resolution: VideoSourceResolution, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct NewVideoSourceResponse { - #[prost(message, optional, tag="1")] - pub source: ::core::option::Option, + #[prost(message, required, tag="1")] + pub source: OwnedVideoSource, } /// Push a frame to a VideoSource #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CaptureVideoFrameRequest { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub source_handle: u64, - #[prost(message, optional, tag="2")] - pub buffer: ::core::option::Option, + #[prost(message, required, tag="2")] + pub buffer: VideoBufferInfo, /// In microseconds - #[prost(int64, tag="3")] + #[prost(int64, required, tag="3")] pub timestamp_us: i64, - #[prost(enumeration="VideoRotation", tag="4")] + #[prost(enumeration="VideoRotation", required, tag="4")] pub rotation: i32, } #[allow(clippy::derive_partial_eq_without_eq)] @@ -1670,20 +1670,29 @@ pub struct CaptureVideoFrameResponse { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct VideoConvertRequest { - #[prost(bool, tag="1")] + #[prost(bool, required, tag="1")] pub flip_y: bool, - #[prost(message, optional, tag="2")] - pub buffer: ::core::option::Option, - #[prost(enumeration="VideoBufferType", tag="3")] + #[prost(message, required, tag="2")] + pub buffer: VideoBufferInfo, + #[prost(enumeration="VideoBufferType", required, tag="3")] pub dst_type: i32, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct VideoConvertResponse { - #[prost(string, optional, tag="1")] - pub error: ::core::option::Option<::prost::alloc::string::String>, - #[prost(message, optional, tag="2")] - pub buffer: ::core::option::Option, + #[prost(oneof="video_convert_response::Message", tags="1, 2")] + pub message: ::core::option::Option, +} +/// Nested message and enum types in `VideoConvertResponse`. +pub mod video_convert_response { + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Message { + #[prost(string, tag="1")] + Error(::prost::alloc::string::String), + #[prost(message, tag="2")] + Buffer(super::OwnedVideoBuffer), + } } // // VideoFrame buffers @@ -1692,26 +1701,26 @@ pub struct VideoConvertResponse { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct VideoResolution { - #[prost(uint32, tag="1")] + #[prost(uint32, required, tag="1")] pub width: u32, - #[prost(uint32, tag="2")] + #[prost(uint32, required, tag="2")] pub height: u32, - #[prost(double, tag="3")] + #[prost(double, required, tag="3")] pub frame_rate: f64, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct VideoBufferInfo { - #[prost(enumeration="VideoBufferType", tag="1")] + #[prost(enumeration="VideoBufferType", required, tag="1")] pub r#type: i32, - #[prost(uint32, tag="2")] + #[prost(uint32, required, tag="2")] pub width: u32, - #[prost(uint32, tag="3")] + #[prost(uint32, required, tag="3")] pub height: u32, - #[prost(uint64, tag="4")] + #[prost(uint64, required, tag="4")] pub data_ptr: u64, /// only for packed formats - #[prost(uint32, tag="6")] + #[prost(uint32, required, tag="6")] pub stride: u32, #[prost(message, repeated, tag="7")] pub components: ::prost::alloc::vec::Vec, @@ -1721,40 +1730,40 @@ pub mod video_buffer_info { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ComponentInfo { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub data_ptr: u64, - #[prost(uint32, tag="2")] + #[prost(uint32, required, tag="2")] pub stride: u32, - #[prost(uint32, tag="3")] + #[prost(uint32, required, tag="3")] pub size: u32, } } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct OwnedVideoBuffer { - #[prost(message, optional, tag="1")] - pub handle: ::core::option::Option, - #[prost(message, optional, tag="2")] - pub info: ::core::option::Option, + #[prost(message, required, tag="1")] + pub handle: FfiOwnedHandle, + #[prost(message, required, tag="2")] + pub info: VideoBufferInfo, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct VideoStreamInfo { - #[prost(enumeration="VideoStreamType", tag="1")] + #[prost(enumeration="VideoStreamType", required, tag="1")] pub r#type: i32, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct OwnedVideoStream { - #[prost(message, optional, tag="1")] - pub handle: ::core::option::Option, - #[prost(message, optional, tag="2")] - pub info: ::core::option::Option, + #[prost(message, required, tag="1")] + pub handle: FfiOwnedHandle, + #[prost(message, required, tag="2")] + pub info: VideoStreamInfo, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct VideoStreamEvent { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub stream_handle: u64, #[prost(oneof="video_stream_event::Message", tags="2, 3")] pub message: ::core::option::Option, @@ -1773,12 +1782,12 @@ pub mod video_stream_event { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct VideoFrameReceived { - #[prost(message, optional, tag="1")] - pub buffer: ::core::option::Option, + #[prost(message, required, tag="1")] + pub buffer: OwnedVideoBuffer, /// In microseconds - #[prost(int64, tag="2")] + #[prost(int64, required, tag="2")] pub timestamp_us: i64, - #[prost(enumeration="VideoRotation", tag="3")] + #[prost(enumeration="VideoRotation", required, tag="3")] pub rotation: i32, } #[allow(clippy::derive_partial_eq_without_eq)] @@ -1792,24 +1801,24 @@ pub struct VideoStreamEos { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct VideoSourceResolution { - #[prost(uint32, tag="1")] + #[prost(uint32, required, tag="1")] pub width: u32, - #[prost(uint32, tag="2")] + #[prost(uint32, required, tag="2")] pub height: u32, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct VideoSourceInfo { - #[prost(enumeration="VideoSourceType", tag="1")] + #[prost(enumeration="VideoSourceType", required, tag="1")] pub r#type: i32, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct OwnedVideoSource { - #[prost(message, optional, tag="1")] - pub handle: ::core::option::Option, - #[prost(message, optional, tag="2")] - pub info: ::core::option::Option, + #[prost(message, required, tag="1")] + pub handle: FfiOwnedHandle, + #[prost(message, required, tag="2")] + pub info: VideoSourceInfo, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] @@ -1988,113 +1997,134 @@ impl VideoSourceType { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ConnectRequest { - #[prost(string, tag="1")] + #[prost(string, required, tag="1")] pub url: ::prost::alloc::string::String, - #[prost(string, tag="2")] + #[prost(string, required, tag="2")] pub token: ::prost::alloc::string::String, - #[prost(message, optional, tag="3")] - pub options: ::core::option::Option, + #[prost(message, required, tag="3")] + pub options: RoomOptions, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ConnectResponse { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub async_id: u64, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ConnectCallback { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub async_id: u64, - #[prost(string, optional, tag="2")] - pub error: ::core::option::Option<::prost::alloc::string::String>, - #[prost(message, optional, tag="3")] - pub room: ::core::option::Option, - #[prost(message, optional, tag="4")] - pub local_participant: ::core::option::Option, - #[prost(message, repeated, tag="5")] - pub participants: ::prost::alloc::vec::Vec, + #[prost(oneof="connect_callback::Message", tags="2, 3")] + pub message: ::core::option::Option, } /// Nested message and enum types in `ConnectCallback`. pub mod connect_callback { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ParticipantWithTracks { - #[prost(message, optional, tag="1")] - pub participant: ::core::option::Option, + #[prost(message, required, tag="1")] + pub participant: super::OwnedParticipant, /// TrackInfo are not needed here, if we're subscribed to a track, the FfiServer will send /// a TrackSubscribed event #[prost(message, repeated, tag="2")] pub publications: ::prost::alloc::vec::Vec, } + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] + pub struct Result { + #[prost(message, required, tag="1")] + pub room: super::OwnedRoom, + #[prost(message, required, tag="2")] + pub local_participant: super::OwnedParticipant, + #[prost(message, repeated, tag="3")] + pub participants: ::prost::alloc::vec::Vec, + } + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Message { + #[prost(string, tag="2")] + Error(::prost::alloc::string::String), + #[prost(message, tag="3")] + Result(Result), + } } /// Disconnect from the a room #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct DisconnectRequest { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub room_handle: u64, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct DisconnectResponse { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub async_id: u64, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct DisconnectCallback { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub async_id: u64, } /// Publish a track to the room #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PublishTrackRequest { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub local_participant_handle: u64, - #[prost(uint64, tag="2")] + #[prost(uint64, required, tag="2")] pub track_handle: u64, - #[prost(message, optional, tag="3")] - pub options: ::core::option::Option, + #[prost(message, required, tag="3")] + pub options: TrackPublishOptions, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PublishTrackResponse { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub async_id: u64, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PublishTrackCallback { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub async_id: u64, - #[prost(string, optional, tag="2")] - pub error: ::core::option::Option<::prost::alloc::string::String>, - #[prost(message, optional, tag="3")] - pub publication: ::core::option::Option, + #[prost(oneof="publish_track_callback::Message", tags="2, 3")] + pub message: ::core::option::Option, +} +/// Nested message and enum types in `PublishTrackCallback`. +pub mod publish_track_callback { + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Message { + #[prost(string, tag="2")] + Error(::prost::alloc::string::String), + #[prost(message, tag="3")] + Publication(super::OwnedTrackPublication), + } } /// Unpublish a track from the room #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct UnpublishTrackRequest { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub local_participant_handle: u64, - #[prost(string, tag="2")] + #[prost(string, required, tag="2")] pub track_sid: ::prost::alloc::string::String, - #[prost(bool, tag="3")] + #[prost(bool, required, tag="3")] pub stop_on_unpublish: bool, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct UnpublishTrackResponse { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub async_id: u64, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct UnpublishTrackCallback { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub async_id: u64, #[prost(string, optional, tag="2")] pub error: ::core::option::Option<::prost::alloc::string::String>, @@ -2103,13 +2133,13 @@ pub struct UnpublishTrackCallback { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PublishDataRequest { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub local_participant_handle: u64, - #[prost(uint64, tag="2")] + #[prost(uint64, required, tag="2")] pub data_ptr: u64, - #[prost(uint64, tag="3")] + #[prost(uint64, required, tag="3")] pub data_len: u64, - #[prost(bool, tag="4")] + #[prost(bool, required, tag="4")] pub reliable: bool, #[deprecated] #[prost(string, repeated, tag="5")] @@ -2122,13 +2152,13 @@ pub struct PublishDataRequest { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PublishDataResponse { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub async_id: u64, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PublishDataCallback { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub async_id: u64, #[prost(string, optional, tag="2")] pub error: ::core::option::Option<::prost::alloc::string::String>, @@ -2137,11 +2167,11 @@ pub struct PublishDataCallback { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PublishTranscriptionRequest { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub local_participant_handle: u64, - #[prost(string, tag="2")] + #[prost(string, required, tag="2")] pub participant_identity: ::prost::alloc::string::String, - #[prost(string, tag="3")] + #[prost(string, required, tag="3")] pub track_id: ::prost::alloc::string::String, #[prost(message, repeated, tag="4")] pub segments: ::prost::alloc::vec::Vec, @@ -2149,13 +2179,13 @@ pub struct PublishTranscriptionRequest { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PublishTranscriptionResponse { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub async_id: u64, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PublishTranscriptionCallback { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub async_id: u64, #[prost(string, optional, tag="2")] pub error: ::core::option::Option<::prost::alloc::string::String>, @@ -2164,11 +2194,11 @@ pub struct PublishTranscriptionCallback { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PublishSipDtmfRequest { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub local_participant_handle: u64, - #[prost(uint32, tag="2")] + #[prost(uint32, required, tag="2")] pub code: u32, - #[prost(string, tag="3")] + #[prost(string, required, tag="3")] pub digit: ::prost::alloc::string::String, #[prost(string, repeated, tag="4")] pub destination_identities: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, @@ -2176,13 +2206,13 @@ pub struct PublishSipDtmfRequest { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PublishSipDtmfResponse { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub async_id: u64, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PublishSipDtmfCallback { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub async_id: u64, #[prost(string, optional, tag="2")] pub error: ::core::option::Option<::prost::alloc::string::String>, @@ -2191,21 +2221,21 @@ pub struct PublishSipDtmfCallback { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SetLocalMetadataRequest { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub local_participant_handle: u64, - #[prost(string, tag="2")] + #[prost(string, required, tag="2")] pub metadata: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SetLocalMetadataResponse { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub async_id: u64, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SetLocalMetadataCallback { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub async_id: u64, #[prost(string, optional, tag="2")] pub error: ::core::option::Option<::prost::alloc::string::String>, @@ -2213,9 +2243,9 @@ pub struct SetLocalMetadataCallback { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SendChatMessageRequest { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub local_participant_handle: u64, - #[prost(string, tag="2")] + #[prost(string, required, tag="2")] pub message: ::prost::alloc::string::String, #[prost(string, repeated, tag="3")] pub destination_identities: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, @@ -2225,12 +2255,12 @@ pub struct SendChatMessageRequest { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct EditChatMessageRequest { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub local_participant_handle: u64, - #[prost(string, tag="2")] + #[prost(string, required, tag="2")] pub edit_text: ::prost::alloc::string::String, - #[prost(message, optional, tag="3")] - pub original_message: ::core::option::Option, + #[prost(message, required, tag="3")] + pub original_message: ChatMessage, #[prost(string, repeated, tag="4")] pub destination_identities: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, #[prost(string, optional, tag="5")] @@ -2239,38 +2269,55 @@ pub struct EditChatMessageRequest { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SendChatMessageResponse { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub async_id: u64, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SendChatMessageCallback { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub async_id: u64, - #[prost(string, optional, tag="2")] - pub error: ::core::option::Option<::prost::alloc::string::String>, - #[prost(message, optional, tag="3")] - pub chat_message: ::core::option::Option, + #[prost(oneof="send_chat_message_callback::Message", tags="2, 3")] + pub message: ::core::option::Option, +} +/// Nested message and enum types in `SendChatMessageCallback`. +pub mod send_chat_message_callback { + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Message { + #[prost(string, tag="2")] + Error(::prost::alloc::string::String), + #[prost(message, tag="3")] + ChatMessage(super::ChatMessage), + } } /// Change the local participant's attributes #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SetLocalAttributesRequest { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub local_participant_handle: u64, - #[prost(map="string, string", tag="2")] - pub attributes: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + #[prost(message, repeated, tag="2")] + pub attributes: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct AttributesEntry { + #[prost(string, required, tag="1")] + pub key: ::prost::alloc::string::String, + #[prost(string, required, tag="2")] + pub value: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SetLocalAttributesResponse { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub async_id: u64, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SetLocalAttributesCallback { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub async_id: u64, #[prost(string, optional, tag="2")] pub error: ::core::option::Option<::prost::alloc::string::String>, @@ -2279,21 +2326,21 @@ pub struct SetLocalAttributesCallback { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SetLocalNameRequest { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub local_participant_handle: u64, - #[prost(string, tag="2")] + #[prost(string, required, tag="2")] pub name: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SetLocalNameResponse { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub async_id: u64, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SetLocalNameCallback { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub async_id: u64, #[prost(string, optional, tag="2")] pub error: ::core::option::Option<::prost::alloc::string::String>, @@ -2302,9 +2349,9 @@ pub struct SetLocalNameCallback { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SetSubscribedRequest { - #[prost(bool, tag="1")] + #[prost(bool, required, tag="1")] pub subscribe: bool, - #[prost(uint64, tag="2")] + #[prost(uint64, required, tag="2")] pub publication_handle: u64, } #[allow(clippy::derive_partial_eq_without_eq)] @@ -2314,26 +2361,41 @@ pub struct SetSubscribedResponse { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetSessionStatsRequest { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub room_handle: u64, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetSessionStatsResponse { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub async_id: u64, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetSessionStatsCallback { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub async_id: u64, - #[prost(string, optional, tag="2")] - pub error: ::core::option::Option<::prost::alloc::string::String>, - #[prost(message, repeated, tag="3")] - pub publisher_stats: ::prost::alloc::vec::Vec, - #[prost(message, repeated, tag="4")] - pub subscriber_stats: ::prost::alloc::vec::Vec, + #[prost(oneof="get_session_stats_callback::Message", tags="2, 3")] + pub message: ::core::option::Option, +} +/// Nested message and enum types in `GetSessionStatsCallback`. +pub mod get_session_stats_callback { + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] + pub struct Result { + #[prost(message, repeated, tag="1")] + pub publisher_stats: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="2")] + pub subscriber_stats: ::prost::alloc::vec::Vec, + } + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Message { + #[prost(string, tag="2")] + Error(::prost::alloc::string::String), + #[prost(message, tag="3")] + Result(Result), + } } // // Options @@ -2342,15 +2404,15 @@ pub struct GetSessionStatsCallback { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct VideoEncoding { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub max_bitrate: u64, - #[prost(double, tag="2")] + #[prost(double, required, tag="2")] pub max_framerate: f64, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct AudioEncoding { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub max_bitrate: u64, } #[allow(clippy::derive_partial_eq_without_eq)] @@ -2361,17 +2423,17 @@ pub struct TrackPublishOptions { pub video_encoding: ::core::option::Option, #[prost(message, optional, tag="2")] pub audio_encoding: ::core::option::Option, - #[prost(enumeration="VideoCodec", tag="3")] + #[prost(enumeration="VideoCodec", required, tag="3")] pub video_codec: i32, - #[prost(bool, tag="4")] + #[prost(bool, required, tag="4")] pub dtx: bool, - #[prost(bool, tag="5")] + #[prost(bool, required, tag="5")] pub red: bool, - #[prost(bool, tag="6")] + #[prost(bool, required, tag="6")] pub simulcast: bool, - #[prost(enumeration="TrackSource", tag="7")] + #[prost(enumeration="TrackSource", required, tag="7")] pub source: i32, - #[prost(string, tag="8")] + #[prost(string, required, tag="8")] pub stream: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] @@ -2379,10 +2441,10 @@ pub struct TrackPublishOptions { pub struct IceServer { #[prost(string, repeated, tag="1")] pub urls: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, - #[prost(string, tag="2")] - pub username: ::prost::alloc::string::String, - #[prost(string, tag="3")] - pub password: ::prost::alloc::string::String, + #[prost(string, optional, tag="2")] + pub username: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="3")] + pub password: ::core::option::Option<::prost::alloc::string::String>, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -2398,56 +2460,56 @@ pub struct RtcConfig { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RoomOptions { - #[prost(bool, tag="1")] + #[prost(bool, required, tag="1")] pub auto_subscribe: bool, - #[prost(bool, tag="2")] + #[prost(bool, required, tag="2")] pub adaptive_stream: bool, - #[prost(bool, tag="3")] + #[prost(bool, required, tag="3")] pub dynacast: bool, #[prost(message, optional, tag="4")] pub e2ee: ::core::option::Option, /// allow to setup a custom RtcConfiguration #[prost(message, optional, tag="5")] pub rtc_config: ::core::option::Option, - #[prost(uint32, tag="6")] + #[prost(uint32, required, tag="6")] pub join_retries: u32, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TranscriptionSegment { - #[prost(string, tag="1")] + #[prost(string, required, tag="1")] pub id: ::prost::alloc::string::String, - #[prost(string, tag="2")] + #[prost(string, required, tag="2")] pub text: ::prost::alloc::string::String, - #[prost(uint64, tag="3")] + #[prost(uint64, required, tag="3")] pub start_time: u64, - #[prost(uint64, tag="4")] + #[prost(uint64, required, tag="4")] pub end_time: u64, - #[prost(bool, tag="5")] + #[prost(bool, required, tag="5")] pub r#final: bool, - #[prost(string, tag="6")] + #[prost(string, required, tag="6")] pub language: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct BufferInfo { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub data_ptr: u64, - #[prost(uint64, tag="2")] + #[prost(uint64, required, tag="2")] pub data_len: u64, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct OwnedBuffer { - #[prost(message, optional, tag="1")] - pub handle: ::core::option::Option, - #[prost(message, optional, tag="2")] - pub data: ::core::option::Option, + #[prost(message, required, tag="1")] + pub handle: FfiOwnedHandle, + #[prost(message, required, tag="2")] + pub data: BufferInfo, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RoomEvent { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub room_handle: u64, #[prost(oneof="room_event::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29")] pub message: ::core::option::Option, @@ -2522,29 +2584,29 @@ pub mod room_event { pub struct RoomInfo { #[prost(string, optional, tag="1")] pub sid: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, tag="2")] + #[prost(string, required, tag="2")] pub name: ::prost::alloc::string::String, - #[prost(string, tag="3")] + #[prost(string, required, tag="3")] pub metadata: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct OwnedRoom { - #[prost(message, optional, tag="1")] - pub handle: ::core::option::Option, - #[prost(message, optional, tag="2")] - pub info: ::core::option::Option, + #[prost(message, required, tag="1")] + pub handle: FfiOwnedHandle, + #[prost(message, required, tag="2")] + pub info: RoomInfo, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ParticipantConnected { - #[prost(message, optional, tag="1")] - pub info: ::core::option::Option, + #[prost(message, required, tag="1")] + pub info: OwnedParticipant, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ParticipantDisconnected { - #[prost(string, tag="1")] + #[prost(string, required, tag="1")] pub participant_identity: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] @@ -2552,35 +2614,35 @@ pub struct ParticipantDisconnected { pub struct LocalTrackPublished { /// The TrackPublicationInfo comes from the PublishTrack response /// and the FfiClient musts wait for it before firing this event - #[prost(string, tag="1")] + #[prost(string, required, tag="1")] pub track_sid: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct LocalTrackUnpublished { - #[prost(string, tag="1")] + #[prost(string, required, tag="1")] pub publication_sid: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct LocalTrackSubscribed { - #[prost(string, tag="2")] + #[prost(string, required, tag="2")] pub track_sid: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackPublished { - #[prost(string, tag="1")] + #[prost(string, required, tag="1")] pub participant_identity: ::prost::alloc::string::String, - #[prost(message, optional, tag="2")] - pub publication: ::core::option::Option, + #[prost(message, required, tag="2")] + pub publication: OwnedTrackPublication, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackUnpublished { - #[prost(string, tag="1")] + #[prost(string, required, tag="1")] pub participant_identity: ::prost::alloc::string::String, - #[prost(string, tag="2")] + #[prost(string, required, tag="2")] pub publication_sid: ::prost::alloc::string::String, } /// Publication isn't needed for subscription events on the FFI @@ -2588,53 +2650,53 @@ pub struct TrackUnpublished { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackSubscribed { - #[prost(string, tag="1")] + #[prost(string, required, tag="1")] pub participant_identity: ::prost::alloc::string::String, - #[prost(message, optional, tag="2")] - pub track: ::core::option::Option, + #[prost(message, required, tag="2")] + pub track: OwnedTrack, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackUnsubscribed { /// The FFI language can dispose/remove the VideoSink here - #[prost(string, tag="1")] + #[prost(string, required, tag="1")] pub participant_identity: ::prost::alloc::string::String, - #[prost(string, tag="2")] + #[prost(string, required, tag="2")] pub track_sid: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackSubscriptionFailed { - #[prost(string, tag="1")] + #[prost(string, required, tag="1")] pub participant_identity: ::prost::alloc::string::String, - #[prost(string, tag="2")] + #[prost(string, required, tag="2")] pub track_sid: ::prost::alloc::string::String, - #[prost(string, tag="3")] + #[prost(string, required, tag="3")] pub error: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackMuted { - #[prost(string, tag="1")] + #[prost(string, required, tag="1")] pub participant_identity: ::prost::alloc::string::String, - #[prost(string, tag="2")] + #[prost(string, required, tag="2")] pub track_sid: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackUnmuted { - #[prost(string, tag="1")] + #[prost(string, required, tag="1")] pub participant_identity: ::prost::alloc::string::String, - #[prost(string, tag="2")] + #[prost(string, required, tag="2")] pub track_sid: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct E2eeStateChanged { /// Using sid instead of identity for ffi communication - #[prost(string, tag="1")] + #[prost(string, required, tag="1")] pub participant_identity: ::prost::alloc::string::String, - #[prost(enumeration="EncryptionState", tag="2")] + #[prost(enumeration="EncryptionState", required, tag="2")] pub state: i32, } #[allow(clippy::derive_partial_eq_without_eq)] @@ -2646,65 +2708,65 @@ pub struct ActiveSpeakersChanged { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RoomMetadataChanged { - #[prost(string, tag="1")] + #[prost(string, required, tag="1")] pub metadata: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RoomSidChanged { - #[prost(string, tag="1")] + #[prost(string, required, tag="1")] pub sid: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ParticipantMetadataChanged { - #[prost(string, tag="1")] + #[prost(string, required, tag="1")] pub participant_identity: ::prost::alloc::string::String, - #[prost(string, tag="2")] + #[prost(string, required, tag="2")] pub metadata: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ParticipantAttributesChanged { - #[prost(string, tag="1")] + #[prost(string, required, tag="1")] pub participant_identity: ::prost::alloc::string::String, - #[prost(map="string, string", tag="2")] - pub attributes: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, - #[prost(map="string, string", tag="3")] - pub changed_attributes: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + #[prost(message, repeated, tag="2")] + pub attributes: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="3")] + pub changed_attributes: ::prost::alloc::vec::Vec, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ParticipantNameChanged { - #[prost(string, tag="1")] + #[prost(string, required, tag="1")] pub participant_identity: ::prost::alloc::string::String, - #[prost(string, tag="2")] + #[prost(string, required, tag="2")] pub name: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ConnectionQualityChanged { - #[prost(string, tag="1")] + #[prost(string, required, tag="1")] pub participant_identity: ::prost::alloc::string::String, - #[prost(enumeration="ConnectionQuality", tag="2")] + #[prost(enumeration="ConnectionQuality", required, tag="2")] pub quality: i32, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct UserPacket { - #[prost(message, optional, tag="1")] - pub data: ::core::option::Option, + #[prost(message, required, tag="1")] + pub data: OwnedBuffer, #[prost(string, optional, tag="2")] pub topic: ::core::option::Option<::prost::alloc::string::String>, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ChatMessage { - #[prost(string, tag="1")] + #[prost(string, required, tag="1")] pub id: ::prost::alloc::string::String, - #[prost(int64, tag="2")] + #[prost(int64, required, tag="2")] pub timestamp: i64, - #[prost(string, tag="3")] + #[prost(string, required, tag="3")] pub message: ::prost::alloc::string::String, #[prost(int64, optional, tag="4")] pub edit_timestamp: ::core::option::Option, @@ -2716,15 +2778,15 @@ pub struct ChatMessage { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ChatMessageReceived { - #[prost(message, optional, tag="1")] - pub message: ::core::option::Option, - #[prost(string, tag="2")] + #[prost(message, required, tag="1")] + pub message: ChatMessage, + #[prost(string, required, tag="2")] pub participant_identity: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SipDtmf { - #[prost(uint32, tag="1")] + #[prost(uint32, required, tag="1")] pub code: u32, #[prost(string, optional, tag="2")] pub digit: ::core::option::Option<::prost::alloc::string::String>, @@ -2732,10 +2794,10 @@ pub struct SipDtmf { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct DataPacketReceived { - #[prost(enumeration="DataPacketKind", tag="1")] + #[prost(enumeration="DataPacketKind", required, tag="1")] pub kind: i32, /// Can be empty if the data is sent a server SDK - #[prost(string, tag="2")] + #[prost(string, required, tag="2")] pub participant_identity: ::prost::alloc::string::String, #[prost(oneof="data_packet_received::Value", tags="4, 5")] pub value: ::core::option::Option, @@ -2764,7 +2826,7 @@ pub struct TranscriptionReceived { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ConnectionStateChanged { - #[prost(enumeration="ConnectionState", tag="1")] + #[prost(enumeration="ConnectionState", required, tag="1")] pub state: i32, } #[allow(clippy::derive_partial_eq_without_eq)] @@ -2774,7 +2836,7 @@ pub struct Connected { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct Disconnected { - #[prost(enumeration="DisconnectReason", tag="1")] + #[prost(enumeration="DisconnectReason", required, tag="1")] pub reason: i32, } #[allow(clippy::derive_partial_eq_without_eq)] @@ -3003,82 +3065,82 @@ impl DisconnectReason { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct NewAudioStreamRequest { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub track_handle: u64, - #[prost(enumeration="AudioStreamType", tag="2")] + #[prost(enumeration="AudioStreamType", required, tag="2")] pub r#type: i32, - #[prost(uint32, tag="3")] + #[prost(uint32, required, tag="3")] pub sample_rate: u32, - #[prost(uint32, tag="4")] + #[prost(uint32, required, tag="4")] pub num_channels: u32, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct NewAudioStreamResponse { - #[prost(message, optional, tag="1")] - pub stream: ::core::option::Option, + #[prost(message, required, tag="1")] + pub stream: OwnedAudioStream, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct AudioStreamFromParticipantRequest { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub participant_handle: u64, - #[prost(enumeration="AudioStreamType", tag="2")] + #[prost(enumeration="AudioStreamType", required, tag="2")] pub r#type: i32, #[prost(enumeration="TrackSource", optional, tag="3")] pub track_source: ::core::option::Option, - #[prost(uint32, tag="5")] + #[prost(uint32, required, tag="5")] pub sample_rate: u32, - #[prost(uint32, tag="6")] + #[prost(uint32, required, tag="6")] pub num_channels: u32, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct AudioStreamFromParticipantResponse { - #[prost(message, optional, tag="1")] - pub stream: ::core::option::Option, + #[prost(message, required, tag="1")] + pub stream: OwnedAudioStream, } /// Create a new AudioSource #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct NewAudioSourceRequest { - #[prost(enumeration="AudioSourceType", tag="1")] + #[prost(enumeration="AudioSourceType", required, tag="1")] pub r#type: i32, #[prost(message, optional, tag="2")] pub options: ::core::option::Option, - #[prost(uint32, tag="3")] + #[prost(uint32, required, tag="3")] pub sample_rate: u32, - #[prost(uint32, tag="4")] + #[prost(uint32, required, tag="4")] pub num_channels: u32, - #[prost(uint32, tag="5")] + #[prost(uint32, required, tag="5")] pub queue_size_ms: u32, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct NewAudioSourceResponse { - #[prost(message, optional, tag="1")] - pub source: ::core::option::Option, + #[prost(message, required, tag="1")] + pub source: OwnedAudioSource, } /// Push a frame to an AudioSource /// The data provided must be available as long as the client receive the callback. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CaptureAudioFrameRequest { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub source_handle: u64, - #[prost(message, optional, tag="2")] - pub buffer: ::core::option::Option, + #[prost(message, required, tag="2")] + pub buffer: AudioFrameBufferInfo, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CaptureAudioFrameResponse { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub async_id: u64, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CaptureAudioFrameCallback { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub async_id: u64, #[prost(string, optional, tag="2")] pub error: ::core::option::Option<::prost::alloc::string::String>, @@ -3086,7 +3148,7 @@ pub struct CaptureAudioFrameCallback { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ClearAudioBufferRequest { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub source_handle: u64, } #[allow(clippy::derive_partial_eq_without_eq)] @@ -3101,76 +3163,85 @@ pub struct NewAudioResamplerRequest { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct NewAudioResamplerResponse { - #[prost(message, optional, tag="1")] - pub resampler: ::core::option::Option, + #[prost(message, required, tag="1")] + pub resampler: OwnedAudioResampler, } /// Remix and resample an audio frame #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RemixAndResampleRequest { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub resampler_handle: u64, - #[prost(message, optional, tag="2")] - pub buffer: ::core::option::Option, - #[prost(uint32, tag="3")] + #[prost(message, required, tag="2")] + pub buffer: AudioFrameBufferInfo, + #[prost(uint32, required, tag="3")] pub num_channels: u32, - #[prost(uint32, tag="4")] + #[prost(uint32, required, tag="4")] pub sample_rate: u32, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RemixAndResampleResponse { - #[prost(message, optional, tag="1")] - pub buffer: ::core::option::Option, + #[prost(message, required, tag="1")] + pub buffer: OwnedAudioFrameBuffer, } // New resampler using SoX (much better quality) #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct NewSoxResamplerRequest { - #[prost(double, tag="1")] + #[prost(double, required, tag="1")] pub input_rate: f64, - #[prost(double, tag="2")] + #[prost(double, required, tag="2")] pub output_rate: f64, - #[prost(uint32, tag="3")] + #[prost(uint32, required, tag="3")] pub num_channels: u32, - #[prost(enumeration="SoxResamplerDataType", tag="4")] + #[prost(enumeration="SoxResamplerDataType", required, tag="4")] pub input_data_type: i32, - #[prost(enumeration="SoxResamplerDataType", tag="5")] + #[prost(enumeration="SoxResamplerDataType", required, tag="5")] pub output_data_type: i32, - #[prost(enumeration="SoxQualityRecipe", tag="6")] + #[prost(enumeration="SoxQualityRecipe", required, tag="6")] pub quality_recipe: i32, - #[prost(uint32, tag="7")] + #[prost(uint32, required, tag="7")] pub flags: u32, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct NewSoxResamplerResponse { - #[prost(message, optional, tag="1")] - pub resampler: ::core::option::Option, - #[prost(string, optional, tag="2")] - pub error: ::core::option::Option<::prost::alloc::string::String>, + #[prost(oneof="new_sox_resampler_response::Message", tags="1, 2")] + pub message: ::core::option::Option, +} +/// Nested message and enum types in `NewSoxResamplerResponse`. +pub mod new_sox_resampler_response { + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Message { + #[prost(message, tag="1")] + Resampler(super::OwnedSoxResampler), + #[prost(string, tag="2")] + Error(::prost::alloc::string::String), + } } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PushSoxResamplerRequest { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub resampler_handle: u64, /// *const i16 - #[prost(uint64, tag="2")] + #[prost(uint64, required, tag="2")] pub data_ptr: u64, /// in bytes - #[prost(uint32, tag="3")] + #[prost(uint32, required, tag="3")] pub size: u32, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PushSoxResamplerResponse { /// *const i16 (could be null) - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub output_ptr: u64, /// in bytes - #[prost(uint32, tag="2")] + #[prost(uint32, required, tag="2")] pub size: u32, #[prost(string, optional, tag="3")] pub error: ::core::option::Option<::prost::alloc::string::String>, @@ -3178,17 +3249,17 @@ pub struct PushSoxResamplerResponse { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FlushSoxResamplerRequest { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub resampler_handle: u64, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FlushSoxResamplerResponse { /// *const i16 (could be null) - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub output_ptr: u64, /// in bytes - #[prost(uint32, tag="2")] + #[prost(uint32, required, tag="2")] pub size: u32, #[prost(string, optional, tag="3")] pub error: ::core::option::Option<::prost::alloc::string::String>, @@ -3201,41 +3272,41 @@ pub struct FlushSoxResamplerResponse { #[derive(Clone, PartialEq, ::prost::Message)] pub struct AudioFrameBufferInfo { /// *const i16 - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub data_ptr: u64, - #[prost(uint32, tag="2")] + #[prost(uint32, required, tag="2")] pub num_channels: u32, - #[prost(uint32, tag="3")] + #[prost(uint32, required, tag="3")] pub sample_rate: u32, - #[prost(uint32, tag="4")] + #[prost(uint32, required, tag="4")] pub samples_per_channel: u32, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct OwnedAudioFrameBuffer { - #[prost(message, optional, tag="1")] - pub handle: ::core::option::Option, - #[prost(message, optional, tag="2")] - pub info: ::core::option::Option, + #[prost(message, required, tag="1")] + pub handle: FfiOwnedHandle, + #[prost(message, required, tag="2")] + pub info: AudioFrameBufferInfo, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct AudioStreamInfo { - #[prost(enumeration="AudioStreamType", tag="1")] + #[prost(enumeration="AudioStreamType", required, tag="1")] pub r#type: i32, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct OwnedAudioStream { - #[prost(message, optional, tag="1")] - pub handle: ::core::option::Option, - #[prost(message, optional, tag="2")] - pub info: ::core::option::Option, + #[prost(message, required, tag="1")] + pub handle: FfiOwnedHandle, + #[prost(message, required, tag="2")] + pub info: AudioStreamInfo, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct AudioStreamEvent { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub stream_handle: u64, #[prost(oneof="audio_stream_event::Message", tags="2, 3")] pub message: ::core::option::Option, @@ -3254,8 +3325,8 @@ pub mod audio_stream_event { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct AudioFrameReceived { - #[prost(message, optional, tag="1")] - pub frame: ::core::option::Option, + #[prost(message, required, tag="1")] + pub frame: OwnedAudioFrameBuffer, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -3268,26 +3339,26 @@ pub struct AudioStreamEos { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct AudioSourceOptions { - #[prost(bool, tag="1")] + #[prost(bool, required, tag="1")] pub echo_cancellation: bool, - #[prost(bool, tag="2")] + #[prost(bool, required, tag="2")] pub noise_suppression: bool, - #[prost(bool, tag="3")] + #[prost(bool, required, tag="3")] pub auto_gain_control: bool, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct AudioSourceInfo { - #[prost(enumeration="AudioSourceType", tag="2")] + #[prost(enumeration="AudioSourceType", required, tag="2")] pub r#type: i32, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct OwnedAudioSource { - #[prost(message, optional, tag="1")] - pub handle: ::core::option::Option, - #[prost(message, optional, tag="2")] - pub info: ::core::option::Option, + #[prost(message, required, tag="1")] + pub handle: FfiOwnedHandle, + #[prost(message, required, tag="2")] + pub info: AudioSourceInfo, } // // AudioResampler @@ -3300,10 +3371,10 @@ pub struct AudioResamplerInfo { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct OwnedAudioResampler { - #[prost(message, optional, tag="1")] - pub handle: ::core::option::Option, - #[prost(message, optional, tag="2")] - pub info: ::core::option::Option, + #[prost(message, required, tag="1")] + pub handle: FfiOwnedHandle, + #[prost(message, required, tag="2")] + pub info: AudioResamplerInfo, } // // Sox AudioResampler @@ -3316,10 +3387,10 @@ pub struct SoxResamplerInfo { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct OwnedSoxResampler { - #[prost(message, optional, tag="1")] - pub handle: ::core::option::Option, - #[prost(message, optional, tag="2")] - pub info: ::core::option::Option, + #[prost(message, required, tag="1")] + pub handle: FfiOwnedHandle, + #[prost(message, required, tag="2")] + pub info: SoxResamplerInfo, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] @@ -3749,7 +3820,7 @@ pub mod ffi_event { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct DisposeRequest { - #[prost(bool, tag="1")] + #[prost(bool, required, tag="1")] pub r#async: bool, } #[allow(clippy::derive_partial_eq_without_eq)] @@ -3762,16 +3833,16 @@ pub struct DisposeResponse { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct DisposeCallback { - #[prost(uint64, tag="1")] + #[prost(uint64, required, tag="1")] pub async_id: u64, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct LogRecord { - #[prost(enumeration="LogLevel", tag="1")] + #[prost(enumeration="LogLevel", required, tag="1")] pub level: i32, /// e.g "livekit", "libwebrtc", "tokio-tungstenite", etc... - #[prost(string, tag="2")] + #[prost(string, required, tag="2")] pub target: ::prost::alloc::string::String, #[prost(string, optional, tag="3")] pub module_path: ::core::option::Option<::prost::alloc::string::String>, @@ -3779,7 +3850,7 @@ pub struct LogRecord { pub file: ::core::option::Option<::prost::alloc::string::String>, #[prost(uint32, optional, tag="5")] pub line: ::core::option::Option, - #[prost(string, tag="6")] + #[prost(string, required, tag="6")] pub message: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] @@ -3791,7 +3862,7 @@ pub struct LogBatch { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct Panic { - #[prost(string, tag="1")] + #[prost(string, required, tag="1")] pub message: ::prost::alloc::string::String, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] diff --git a/livekit-ffi/src/server/audio_source.rs b/livekit-ffi/src/server/audio_source.rs index e07acee8e..703f9e668 100644 --- a/livekit-ffi/src/server/audio_source.rs +++ b/livekit-ffi/src/server/audio_source.rs @@ -56,10 +56,7 @@ impl FfiAudioSource { let info = proto::AudioSourceInfo::from(&source); server.store_handle(source.handle_id, source); - Ok(proto::OwnedAudioSource { - handle: Some(proto::FfiOwnedHandle { id: handle_id }), - info: Some(info), - }) + Ok(proto::OwnedAudioSource { handle: proto::FfiOwnedHandle { id: handle_id }, info: info }) } pub fn clear_buffer(&self) { @@ -75,9 +72,7 @@ impl FfiAudioSource { server: &'static server::FfiServer, capture: proto::CaptureAudioFrameRequest, ) -> FfiResult { - let Some(buffer) = capture.buffer else { - return Err(FfiError::InvalidRequest("buffer is None".into())); - }; + let buffer = capture.buffer; let source = self.source.clone(); let async_id = server.next_id(); diff --git a/livekit-ffi/src/server/audio_stream.rs b/livekit-ffi/src/server/audio_stream.rs index bef90859a..bde45053d 100644 --- a/livekit-ffi/src/server/audio_stream.rs +++ b/livekit-ffi/src/server/audio_stream.rs @@ -85,10 +85,7 @@ impl FfiAudioStream { let info = proto::AudioStreamInfo::from(&audio_stream); server.store_handle(handle_id, audio_stream); - Ok(proto::OwnedAudioStream { - handle: Some(proto::FfiOwnedHandle { id: handle_id }), - info: Some(info), - }) + Ok(proto::OwnedAudioStream { handle: proto::FfiOwnedHandle { id: handle_id }, info: info }) } pub fn from_participant( @@ -119,10 +116,7 @@ impl FfiAudioStream { let info = proto::AudioStreamInfo::from(&audio_stream); server.store_handle(handle_id, audio_stream); - Ok(proto::OwnedAudioStream { - handle: Some(proto::FfiOwnedHandle { id: handle_id }), - info: Some(info), - }) + Ok(proto::OwnedAudioStream { handle: proto::FfiOwnedHandle { id: handle_id }, info: info }) } async fn participant_audio_stream_task( @@ -249,10 +243,10 @@ impl FfiAudioStream { stream_handle: stream_handle_id, message: Some(proto::audio_stream_event::Message::FrameReceived( proto::AudioFrameReceived { - frame: Some(proto::OwnedAudioFrameBuffer { - handle: Some(proto::FfiOwnedHandle { id: handle_id }), - info: Some(buffer_info), - }), + frame: proto::OwnedAudioFrameBuffer { + handle: proto::FfiOwnedHandle { id: handle_id }, + info: buffer_info, + }, }, )), }, diff --git a/livekit-ffi/src/server/requests.rs b/livekit-ffi/src/server/requests.rs index febb0cec3..a61fe44ea 100644 --- a/livekit-ffi/src/server/requests.rs +++ b/livekit-ffi/src/server/requests.rs @@ -26,7 +26,7 @@ use super::{ room::{self, FfiParticipant, FfiPublication, FfiTrack}, video_source, video_stream, FfiError, FfiResult, FfiServer, }; -use crate::{conversion::track, proto}; +use crate::proto; /// Dispose the server, close all rooms and clean up all handles /// It is not mandatory to call this function. @@ -222,10 +222,10 @@ fn on_create_video_track( server.store_handle(handle_id, ffi_track); Ok(proto::CreateVideoTrackResponse { - track: Some(proto::OwnedTrack { - handle: Some(proto::FfiOwnedHandle { id: handle_id }), - info: Some(track_info), - }), + track: proto::OwnedTrack { + handle: proto::FfiOwnedHandle { id: handle_id }, + info: track_info, + }, }) } @@ -246,10 +246,10 @@ fn on_create_audio_track( server.store_handle(handle_id, ffi_track); Ok(proto::CreateAudioTrackResponse { - track: Some(proto::OwnedTrack { - handle: Some(proto::FfiOwnedHandle { id: handle_id }), - info: Some(track_info), - }), + track: proto::OwnedTrack { + handle: proto::FfiOwnedHandle { id: handle_id }, + info: track_info, + }, }) } @@ -352,7 +352,7 @@ fn on_new_video_stream( new_stream: proto::NewVideoStreamRequest, ) -> FfiResult { let stream_info = video_stream::FfiVideoStream::from_track(server, new_stream)?; - Ok(proto::NewVideoStreamResponse { stream: Some(stream_info) }) + Ok(proto::NewVideoStreamResponse { stream: stream_info }) } fn on_video_stream_from_participant( @@ -360,7 +360,7 @@ fn on_video_stream_from_participant( request: proto::VideoStreamFromParticipantRequest, ) -> FfiResult { let stream_info = video_stream::FfiVideoStream::from_participant(server, request)?; - Ok(proto::VideoStreamFromParticipantResponse { stream: Some(stream_info) }) + Ok(proto::VideoStreamFromParticipantResponse { stream: stream_info }) } /// Create a new video source, used to publish data to a track @@ -369,7 +369,7 @@ fn on_new_video_source( new_source: proto::NewVideoSourceRequest, ) -> FfiResult { let source_info = video_source::FfiVideoSource::setup(server, new_source)?; - Ok(proto::NewVideoSourceResponse { source: Some(source_info) }) + Ok(proto::NewVideoSourceResponse { source: source_info }) } /// Push a frame to a source, libwebrtc will then decide if the frame should be dropped or not @@ -391,24 +391,23 @@ unsafe fn on_video_convert( server: &'static FfiServer, video_convert: proto::VideoConvertRequest, ) -> FfiResult { - let Some(ref buffer) = video_convert.buffer else { - return Err(FfiError::InvalidRequest("buffer is empty".into())); - }; - + let ref buffer = video_convert.buffer; let flip_y = video_convert.flip_y; let dst_type = video_convert.dst_type(); match cvtimpl::cvt(buffer.clone(), dst_type, flip_y) { Ok((buffer, info)) => { let id = server.next_id(); server.store_handle(id, buffer); - let owned_info = proto::OwnedVideoBuffer { - handle: Some(proto::FfiOwnedHandle { id }), - info: Some(info), - }; - Ok(proto::VideoConvertResponse { buffer: Some(owned_info), error: None }) + let owned_info = + proto::OwnedVideoBuffer { handle: proto::FfiOwnedHandle { id }, info: info }; + Ok(proto::VideoConvertResponse { + message: Some(proto::video_convert_response::Message::Buffer(owned_info)), + }) } - Err(err) => Ok(proto::VideoConvertResponse { buffer: None, error: Some(err.to_string()) }), + Err(err) => Ok(proto::VideoConvertResponse { + message: Some(proto::video_convert_response::Message::Error(err.to_string())), + }), } } @@ -418,7 +417,7 @@ fn on_new_audio_stream( new_stream: proto::NewAudioStreamRequest, ) -> FfiResult { let stream_info = audio_stream::FfiAudioStream::from_track(server, new_stream)?; - Ok(proto::NewAudioStreamResponse { stream: Some(stream_info) }) + Ok(proto::NewAudioStreamResponse { stream: stream_info }) } // Create a new audio stream from a participant and track source @@ -427,7 +426,7 @@ fn on_audio_stream_from_participant_stream( request: proto::AudioStreamFromParticipantRequest, ) -> FfiResult { let stream_info = audio_stream::FfiAudioStream::from_participant(server, request)?; - Ok(proto::AudioStreamFromParticipantResponse { stream: Some(stream_info) }) + Ok(proto::AudioStreamFromParticipantResponse { stream: stream_info }) } /// Create a new audio source (used to publish audio frames to a track) @@ -436,7 +435,7 @@ fn on_new_audio_source( new_source: proto::NewAudioSourceRequest, ) -> FfiResult { let source_info = audio_source::FfiAudioSource::setup(server, new_source)?; - Ok(proto::NewAudioSourceResponse { source: Some(source_info) }) + Ok(proto::NewAudioSourceResponse { source: source_info }) } /// Push a frame to a source @@ -470,10 +469,10 @@ fn new_audio_resampler( server.store_handle(handle_id, resampler); Ok(proto::NewAudioResamplerResponse { - resampler: Some(proto::OwnedAudioResampler { - handle: Some(proto::FfiOwnedHandle { id: handle_id }), - info: Some(proto::AudioResamplerInfo {}), - }), + resampler: proto::OwnedAudioResampler { + handle: proto::FfiOwnedHandle { id: handle_id }, + info: proto::AudioResamplerInfo {}, + }, }) } @@ -487,7 +486,7 @@ fn remix_and_resample( .retrieve_handle::>>(remix.resampler_handle)? .clone(); - let buffer = remix.buffer.ok_or(FfiError::InvalidRequest("buffer is empty".into()))?; + let buffer = remix.buffer; let data = unsafe { let len = (buffer.num_channels * buffer.samples_per_channel) as usize; @@ -519,10 +518,10 @@ fn remix_and_resample( server.store_handle(handle_id, audio_frame); Ok(proto::RemixAndResampleResponse { - buffer: Some(proto::OwnedAudioFrameBuffer { - handle: Some(proto::FfiOwnedHandle { id: handle_id }), - info: Some(buffer_info), - }), + buffer: proto::OwnedAudioFrameBuffer { + handle: proto::FfiOwnedHandle { id: handle_id }, + info: buffer_info, + }, }) } @@ -650,17 +649,20 @@ fn on_get_session_stats( let _ = server.send_event(proto::ffi_event::Message::GetSessionStats( proto::GetSessionStatsCallback { async_id, - error: None, - publisher_stats: stats - .publisher_stats - .into_iter() - .map(Into::into) - .collect(), - subscriber_stats: stats - .subscriber_stats - .into_iter() - .map(Into::into) - .collect(), + message: Some(proto::get_session_stats_callback::Message::Result( + proto::get_session_stats_callback::Result { + publisher_stats: stats + .publisher_stats + .into_iter() + .map(Into::into) + .collect(), + subscriber_stats: stats + .subscriber_stats + .into_iter() + .map(Into::into) + .collect(), + }, + )), }, )); } @@ -668,8 +670,9 @@ fn on_get_session_stats( let _ = server.send_event(proto::ffi_event::Message::GetSessionStats( proto::GetSessionStatsCallback { async_id, - error: Some(err.to_string()), - ..Default::default() + message: Some(proto::get_session_stats_callback::Message::Error( + err.to_string(), + )), }, )); } @@ -708,16 +711,17 @@ fn on_new_sox_resampler( server.store_handle(handle_id, resampler); Ok(proto::NewSoxResamplerResponse { - resampler: Some(proto::OwnedSoxResampler { - handle: Some(proto::FfiOwnedHandle { id: handle_id }), - info: Some(proto::SoxResamplerInfo {}), - }), - ..Default::default() + message: Some(proto::new_sox_resampler_response::Message::Resampler( + proto::OwnedSoxResampler { + handle: proto::FfiOwnedHandle { id: handle_id }, + info: proto::SoxResamplerInfo {}, + }, + )), }) } - Err(e) => { - Ok(proto::NewSoxResamplerResponse { error: Some(e.to_string()), ..Default::default() }) - } + Err(e) => Ok(proto::NewSoxResamplerResponse { + message: Some(proto::new_sox_resampler_response::Message::Error(e.to_string())), + }), } } diff --git a/livekit-ffi/src/server/room.rs b/livekit-ffi/src/server/room.rs index 92a6fcb12..d782605b0 100644 --- a/livekit-ffi/src/server/room.rs +++ b/livekit-ffi/src/server/room.rs @@ -117,13 +117,7 @@ impl FfiRoom { let async_id = server.next_id(); let connect = async move { - match Room::connect( - &connect.url, - &connect.token, - connect.options.map(Into::into).unwrap_or_default(), - ) - .await - { + match Room::connect(&connect.url, &connect.token, connect.options.into()).await { Ok((room, mut events)) => { // Successfully connected to the room // Forward the initial state for the FfiClient @@ -171,13 +165,16 @@ impl FfiRoom { let _ = server.send_event(proto::ffi_event::Message::Connect( proto::ConnectCallback { async_id, - error: None, - room: Some(proto::OwnedRoom { - handle: Some(proto::FfiOwnedHandle { id: handle_id }), - info: Some(room_info), - }), - local_participant: Some(local_info), - participants: remote_infos, + message: Some(proto::connect_callback::Message::Result( + proto::connect_callback::Result { + room: proto::OwnedRoom { + handle: proto::FfiOwnedHandle { id: handle_id }, + info: room_info, + }, + local_participant: local_info, + participants: remote_infos, + }, + )), }, )); @@ -250,7 +247,7 @@ impl FfiRoom { let _ = server.send_event(proto::ffi_event::Message::Connect( proto::ConnectCallback { async_id, - error: Some(e.to_string()), + message: Some(proto::connect_callback::Message::Error(e.to_string())), ..Default::default() }, )); @@ -407,20 +404,10 @@ impl RoomInner { let track = LocalTrack::try_from(ffi_track.track.clone()) .map_err(|_| FfiError::InvalidRequest("track is not a LocalTrack".into()))?; - // protobuf 3 doesn't let us require fields, instead defaulting to zero. - if publish.options.clone().is_some_and(|opts| { - opts.video_encoding - .is_some_and(|enc| enc.max_framerate == 0.0 || enc.max_bitrate == 0) - }) { - return Err(FfiError::InvalidRequest( - "VideoEncoding must specify both max_framerate and max_bitrate".into(), - )); - } - let publication = inner .room .local_participant() - .publish_track(track, publish.options.map(Into::into).unwrap_or_default()) + .publish_track(track, publish.options.into()) .await?; Ok::(publication) } @@ -441,11 +428,12 @@ impl RoomInner { let _ = server.send_event(proto::ffi_event::Message::PublishTrack( proto::PublishTrackCallback { async_id, - publication: Some(proto::OwnedTrackPublication { - handle: Some(proto::FfiOwnedHandle { id: handle_id }), - info: Some(publication_info), - }), - ..Default::default() + message: Some(proto::publish_track_callback::Message::Publication( + proto::OwnedTrackPublication { + handle: proto::FfiOwnedHandle { id: handle_id }, + info: publication_info, + }, + )), }, )); @@ -456,8 +444,9 @@ impl RoomInner { let _ = server.send_event(proto::ffi_event::Message::PublishTrack( proto::PublishTrackCallback { async_id, - error: Some(err.to_string()), - ..Default::default() + message: Some(proto::publish_track_callback::Message::Error( + err.to_string(), + )), }, )); } @@ -556,7 +545,13 @@ impl RoomInner { let res = inner .room .local_participant() - .set_attributes(set_local_attributes.attributes) + .set_attributes( + set_local_attributes + .attributes + .into_iter() + .map(|entry| (entry.key, entry.value)) + .collect(), + ) .await; let _ = server.send_event(proto::ffi_event::Message::SetLocalAttributes( @@ -588,13 +583,30 @@ impl RoomInner { ) .await; let sent_message = res.as_ref().unwrap().clone(); - let _ = server.send_event(proto::ffi_event::Message::ChatMessage( - proto::SendChatMessageCallback { - async_id, - error: res.err().map(|e| e.to_string()), - chat_message: proto::ChatMessage::from(sent_message).into(), - }, - )); + + match res { + Ok(message) => { + let _ = server.send_event(proto::ffi_event::Message::ChatMessage( + proto::SendChatMessageCallback { + async_id, + message: Some(proto::send_chat_message_callback::Message::ChatMessage( + proto::ChatMessage::from(message).into(), + )), + }, + )); + } + Err(error) => { + let _ = server.send_event(proto::ffi_event::Message::ChatMessage( + proto::SendChatMessageCallback { + async_id, + message: Some(proto::send_chat_message_callback::Message::Error( + error.to_string(), + )), + }, + )); + } + } + sent_message; }); server.watch_panic(handle); proto::SendChatMessageResponse { async_id } @@ -613,19 +625,35 @@ impl RoomInner { .local_participant() .edit_chat_message( edit_chat_message.edit_text, - edit_chat_message.original_message.unwrap().into(), + edit_chat_message.original_message.into(), edit_chat_message.destination_identities.into(), edit_chat_message.sender_identity, ) .await; let sent_message: ChatMessage = res.as_ref().unwrap().clone(); - let _ = server.send_event(proto::ffi_event::Message::ChatMessage( - proto::SendChatMessageCallback { - async_id, - error: res.err().map(|e| e.to_string()), - chat_message: proto::ChatMessage::from(sent_message).into(), - }, - )); + match res { + Ok(message) => { + let _ = server.send_event(proto::ffi_event::Message::ChatMessage( + proto::SendChatMessageCallback { + async_id, + message: Some(proto::send_chat_message_callback::Message::ChatMessage( + proto::ChatMessage::from(message).into(), + )), + }, + )); + } + Err(error) => { + let _ = server.send_event(proto::ffi_event::Message::ChatMessage( + proto::SendChatMessageCallback { + async_id, + message: Some(proto::send_chat_message_callback::Message::Error( + error.to_string(), + )), + }, + )); + } + } + sent_message; }); server.watch_panic(handle); proto::SendChatMessageResponse { async_id } @@ -799,10 +827,10 @@ async fn forward_event( let _ = send_event(proto::room_event::Message::ParticipantConnected( proto::ParticipantConnected { - info: Some(proto::OwnedParticipant { - handle: Some(proto::FfiOwnedHandle { id: handle_id }), - info: Some(proto::ParticipantInfo::from(&ffi_participant)), - }), + info: proto::OwnedParticipant { + handle: proto::FfiOwnedHandle { id: handle_id }, + info: proto::ParticipantInfo::from(&ffi_participant), + }, }, )); } @@ -865,10 +893,10 @@ async fn forward_event( let _ = send_event(proto::room_event::Message::TrackPublished(proto::TrackPublished { participant_identity: participant.identity().to_string(), - publication: Some(proto::OwnedTrackPublication { - handle: Some(proto::FfiOwnedHandle { id: handle_id }), - info: Some(publication_info), - }), + publication: proto::OwnedTrackPublication { + handle: proto::FfiOwnedHandle { id: handle_id }, + info: publication_info, + }, })); } RoomEvent::TrackUnpublished { publication, participant } => { @@ -890,10 +918,10 @@ async fn forward_event( let _ = send_event(proto::room_event::Message::TrackSubscribed(proto::TrackSubscribed { participant_identity: participant.identity().to_string(), - track: Some(proto::OwnedTrack { - handle: Some(proto::FfiOwnedHandle { id: handle_id }), - info: Some(track_info), - }), + track: proto::OwnedTrack { + handle: proto::FfiOwnedHandle { id: handle_id }, + info: track_info, + }, })); } RoomEvent::TrackUnsubscribed { track, publication: _, participant } => { @@ -950,8 +978,16 @@ async fn forward_event( let _ = send_event(proto::room_event::Message::ParticipantAttributesChanged( proto::ParticipantAttributesChanged { participant_identity: participant.identity().to_string(), - changed_attributes, - attributes: participant.attributes().clone(), + changed_attributes: changed_attributes + .into_iter() + .map(|(key, value)| proto::AttributesEntry { key, value }) + .collect(), + attributes: participant + .attributes() + .clone() + .into_iter() + .map(|(key, value)| proto::AttributesEntry { key, value }) + .collect(), }, )); } @@ -986,10 +1022,10 @@ async fn forward_event( let _ = send_event(proto::room_event::Message::DataPacketReceived( proto::DataPacketReceived { value: Some(proto::data_packet_received::Value::User(proto::UserPacket { - data: Some(proto::OwnedBuffer { - handle: Some(proto::FfiOwnedHandle { id: handle_id }), - data: Some(buffer_info), - }), + data: proto::OwnedBuffer { + handle: proto::FfiOwnedHandle { id: handle_id }, + data: buffer_info, + }, topic, })), participant_identity: identity, @@ -1113,10 +1149,10 @@ fn build_initial_states( server.store_handle(ffi_participant.handle, ffi_participant); proto::connect_callback::ParticipantWithTracks { - participant: Some(proto::OwnedParticipant { - handle: Some(proto::FfiOwnedHandle { id: handle_id }), - info: Some(remote_info), - }), + participant: proto::OwnedParticipant { + handle: proto::FfiOwnedHandle { id: handle_id }, + info: remote_info, + }, publications: tracks .into_iter() .map(|track| { @@ -1130,8 +1166,8 @@ fn build_initial_states( server.store_handle(ffi_publication.handle, ffi_publication); proto::OwnedTrackPublication { - handle: Some(proto::FfiOwnedHandle { id: handle_id }), - info: Some(track_info), + handle: proto::FfiOwnedHandle { id: handle_id }, + info: track_info, } }) .collect::>(), @@ -1141,8 +1177,8 @@ fn build_initial_states( ( proto::OwnedParticipant { - handle: Some(proto::FfiOwnedHandle { id: handle_id }), - info: Some(local_info), + handle: proto::FfiOwnedHandle { id: handle_id }, + info: local_info, }, remote_infos, ) diff --git a/livekit-ffi/src/server/video_source.rs b/livekit-ffi/src/server/video_source.rs index b15c0901b..50327b3bd 100644 --- a/livekit-ffi/src/server/video_source.rs +++ b/livekit-ffi/src/server/video_source.rs @@ -36,9 +36,7 @@ impl FfiVideoSource { proto::VideoSourceType::VideoSourceNative => { use livekit::webrtc::video_source::native::NativeVideoSource; - let video_source = NativeVideoSource::new( - new_source.resolution.map(Into::into).unwrap_or_default(), - ); + let video_source = NativeVideoSource::new(new_source.resolution.into()); RtcVideoSource::Native(video_source) } _ => return Err(FfiError::InvalidRequest("unsupported video source type".into())), @@ -50,8 +48,8 @@ impl FfiVideoSource { server.store_handle(handle_id, video_source); Ok(proto::OwnedVideoSource { - handle: Some(proto::FfiOwnedHandle { id: handle_id }), - info: Some(source_info), + handle: proto::FfiOwnedHandle { id: handle_id }, + info: source_info, }) } @@ -63,12 +61,7 @@ impl FfiVideoSource { match self.source { #[cfg(not(target_arch = "wasm32"))] RtcVideoSource::Native(ref source) => { - let buffer = capture - .buffer - .as_ref() - .ok_or(FfiError::InvalidRequest("frame is empty".into()))?; - - let buffer = colorcvt::to_libwebrtc_buffer(buffer.clone()); + let buffer = colorcvt::to_libwebrtc_buffer(capture.buffer.clone()); let frame = VideoFrame { rotation: capture.rotation().into(), timestamp_us: capture.timestamp_us, diff --git a/livekit-ffi/src/server/video_stream.rs b/livekit-ffi/src/server/video_stream.rs index 5b2d930b2..94d32688d 100644 --- a/livekit-ffi/src/server/video_stream.rs +++ b/livekit-ffi/src/server/video_stream.rs @@ -80,10 +80,7 @@ impl FfiVideoStream { let info = proto::VideoStreamInfo::from(&stream); server.store_handle(stream.handle_id, stream); - Ok(proto::OwnedVideoStream { - handle: Some(proto::FfiOwnedHandle { id: handle_id }), - info: Some(info), - }) + Ok(proto::OwnedVideoStream { handle: proto::FfiOwnedHandle { id: handle_id }, info: info }) } pub fn from_participant( @@ -113,10 +110,7 @@ impl FfiVideoStream { let info = proto::VideoStreamInfo::from(&stream); server.store_handle(stream.handle_id, stream); - Ok(proto::OwnedVideoStream { - handle: Some(proto::FfiOwnedHandle { id: handle_id }), - info: Some(info), - }) + Ok(proto::OwnedVideoStream { handle: proto::FfiOwnedHandle { id: handle_id }, info: info }) } async fn native_video_stream_task( @@ -158,12 +152,12 @@ impl FfiVideoStream { proto::VideoFrameReceived { timestamp_us: frame.timestamp_us, rotation: proto::VideoRotation::from(frame.rotation).into(), - buffer: Some(proto::OwnedVideoBuffer { - handle: Some(proto::FfiOwnedHandle { + buffer: proto::OwnedVideoBuffer { + handle: proto::FfiOwnedHandle { id: handle_id, - }), - info: Some(info), - }), + }, + info, + }, } )), } From be7616691704683fe90cb5b624c319fc370d447e Mon Sep 17 00:00:00 2001 From: Ben Cherry Date: Fri, 18 Oct 2024 09:20:09 -0700 Subject: [PATCH 062/274] await transcription and dtmf handles on room close (#462) --- livekit-ffi/src/server/room.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/livekit-ffi/src/server/room.rs b/livekit-ffi/src/server/room.rs index d782605b0..ddd1b936a 100644 --- a/livekit-ffi/src/server/room.rs +++ b/livekit-ffi/src/server/room.rs @@ -268,6 +268,8 @@ impl FfiRoom { let _ = handle.close_tx.send(()); let _ = handle.event_handle.await; let _ = handle.data_handle.await; + let _ = handle.transcription_handle.await; + let _ = handle.sip_dtmf_handle.await; } } } From 3de6fa60f3cdafc2c106a7f370724eddf1e92fdc Mon Sep 17 00:00:00 2001 From: lukasIO Date: Mon, 21 Oct 2024 10:24:04 +0200 Subject: [PATCH 063/274] ffi-v0.12.0 (#472) * ffi-v0.12.0 * no format --- livekit-ffi/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index cf2656640..b07a6836d 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.11.5" +version = "0.12.0" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" From 21149a4df03616036d3849cca09f7c7c00b9c44a Mon Sep 17 00:00:00 2001 From: Ben Cherry Date: Wed, 23 Oct 2024 16:08:49 -0700 Subject: [PATCH 064/274] Add SDK version, allow override of SDK and version from FFI (#471) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * pass sdk down * fix build * fix * fixed length * make both optional * cstr * Update livekit-api/src/signal_client/mod.rs Co-authored-by: Théo Monnom * Update livekit/src/room/mod.rs Co-authored-by: Théo Monnom * type fixes * analytics options * cstr * removeunused * fmt * cleanup * fmt * simplify * revert * fmt * o * fmt * nw * fmt --------- Co-authored-by: Théo Monnom --- Cargo.lock | 2 +- livekit-api/src/signal_client/mod.rs | 29 ++++++++++++++++-- livekit-ffi/src/cabi.rs | 11 ++++++- livekit-ffi/src/conversion/room.rs | 17 ++++++----- livekit-ffi/src/server/mod.rs | 2 ++ livekit-ffi/src/server/room.rs | 12 +++++++- livekit/src/prelude.rs | 2 +- livekit/src/room/mod.rs | 45 +++++++++++++++++++++++----- 8 files changed, 98 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dc6e29d13..6b2c90388 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1641,7 +1641,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.11.3" +version = "0.12.0" dependencies = [ "console-subscriber", "dashmap", diff --git a/livekit-api/src/signal_client/mod.rs b/livekit-api/src/signal_client/mod.rs index 711ba7ed2..2a8cb16ff 100644 --- a/livekit-api/src/signal_client/mod.rs +++ b/livekit-api/src/signal_client/mod.rs @@ -65,14 +65,33 @@ pub enum SignalError { } #[derive(Debug, Clone)] +#[non_exhaustive] +pub struct SignalSdkOptions { + pub sdk: String, + pub sdk_version: Option, +} + +impl Default for SignalSdkOptions { + fn default() -> Self { + Self { sdk: "rust".to_string(), sdk_version: None } + } +} + +#[derive(Debug, Clone)] +#[non_exhaustive] pub struct SignalOptions { pub auto_subscribe: bool, pub adaptive_stream: bool, + pub sdk_options: SignalSdkOptions, } impl Default for SignalOptions { fn default() -> Self { - Self { auto_subscribe: true, adaptive_stream: false } + Self { + auto_subscribe: true, + adaptive_stream: false, + sdk_options: SignalSdkOptions::default(), + } } } @@ -431,12 +450,16 @@ fn get_livekit_url(url: &str, token: &str, options: &SignalOptions) -> SignalRes lk_url .query_pairs_mut() - .append_pair("sdk", "rust") - .append_pair("access_token", token) + .append_pair("sdk", options.sdk_options.sdk.as_str()) .append_pair("protocol", PROTOCOL_VERSION.to_string().as_str()) + .append_pair("access_token", token) .append_pair("auto_subscribe", if options.auto_subscribe { "1" } else { "0" }) .append_pair("adaptive_stream", if options.adaptive_stream { "1" } else { "0" }); + if let Some(sdk_version) = &options.sdk_options.sdk_version { + lk_url.query_pairs_mut().append_pair("version", sdk_version.as_str()); + } + Ok(lk_url) } diff --git a/livekit-ffi/src/cabi.rs b/livekit-ffi/src/cabi.rs index 1df37c538..5cc630a76 100644 --- a/livekit-ffi/src/cabi.rs +++ b/livekit-ffi/src/cabi.rs @@ -1,5 +1,7 @@ use prost::Message; use server::FfiDataBuffer; +use std::ffi::CStr; +use std::os::raw::c_char; use std::{panic, sync::Arc}; use crate::{ @@ -15,13 +17,20 @@ pub type FfiCallbackFn = unsafe extern "C" fn(*const u8, usize); /// /// The foreign language must only provide valid pointers #[no_mangle] -pub unsafe extern "C" fn livekit_ffi_initialize(cb: FfiCallbackFn, capture_logs: bool) { +pub unsafe extern "C" fn livekit_ffi_initialize( + cb: FfiCallbackFn, + capture_logs: bool, + sdk: *const c_char, + sdk_version: *const c_char, +) { FFI_SERVER.setup(FfiConfig { callback_fn: Arc::new(move |event| { let data = event.encode_to_vec(); cb(data.as_ptr(), data.len()); }), capture_logs, + sdk: CStr::from_ptr(sdk).to_string_lossy().into_owned(), + sdk_version: CStr::from_ptr(sdk_version).to_string_lossy().into_owned(), }); log::info!("initializing ffi server v{}", env!("CARGO_PKG_VERSION")); diff --git a/livekit-ffi/src/conversion/room.rs b/livekit-ffi/src/conversion/room.rs index 514396ab7..b5295f1b3 100644 --- a/livekit-ffi/src/conversion/room.rs +++ b/livekit-ffi/src/conversion/room.rs @@ -176,14 +176,15 @@ impl From for RoomOptions { let rtc_config = value.rtc_config.map(Into::into).unwrap_or(RoomOptions::default().rtc_config); - Self { - adaptive_stream: value.adaptive_stream, - auto_subscribe: value.auto_subscribe, - dynacast: value.dynacast, - e2ee, - rtc_config, - join_retries: value.join_retries, - } + let mut options = RoomOptions::default(); + options.adaptive_stream = value.adaptive_stream; + options.auto_subscribe = value.auto_subscribe; + options.dynacast = value.dynacast; + options.e2ee = e2ee; + options.rtc_config = rtc_config; + options.join_retries = value.join_retries; + options.sdk_options = RoomSdkOptions::default(); + options } } diff --git a/livekit-ffi/src/server/mod.rs b/livekit-ffi/src/server/mod.rs index a9ef15152..461439ab9 100644 --- a/livekit-ffi/src/server/mod.rs +++ b/livekit-ffi/src/server/mod.rs @@ -52,6 +52,8 @@ pub mod video_stream; pub struct FfiConfig { pub callback_fn: Arc, pub capture_logs: bool, + pub sdk: String, + pub sdk_version: String, } /// To make sure we use the right types, only types that implement this trait diff --git a/livekit-ffi/src/server/room.rs b/livekit-ffi/src/server/room.rs index ddd1b936a..a6da9b634 100644 --- a/livekit-ffi/src/server/room.rs +++ b/livekit-ffi/src/server/room.rs @@ -116,8 +116,18 @@ impl FfiRoom { ) -> proto::ConnectResponse { let async_id = server.next_id(); + let mut options: RoomOptions = connect.options.into(); + + { + let config = server.config.lock(); + if let Some(c) = config.as_ref() { + options.sdk_options.sdk = c.sdk.clone(); + options.sdk_options.sdk_version = c.sdk_version.clone(); + } + } + let connect = async move { - match Room::connect(&connect.url, &connect.token, connect.options.into()).await { + match Room::connect(&connect.url, &connect.token, options.clone()).await { Ok((room, mut events)) => { // Successfully connected to the room // Forward the initial state for the FfiClient diff --git a/livekit/src/prelude.rs b/livekit/src/prelude.rs index e801014aa..f53fdda17 100644 --- a/livekit/src/prelude.rs +++ b/livekit/src/prelude.rs @@ -21,5 +21,5 @@ pub use crate::{ RemoteVideoTrack, StreamState, Track, TrackDimension, TrackKind, TrackSource, VideoTrack, }, ConnectionState, DataPacket, DataPacketKind, DisconnectReason, Room, RoomError, RoomEvent, - RoomOptions, RoomResult, SipDTMF, Transcription, TranscriptionSegment, + RoomOptions, RoomResult, RoomSdkOptions, SipDTMF, Transcription, TranscriptionSegment, }; diff --git a/livekit/src/room/mod.rs b/livekit/src/room/mod.rs index 346d4145d..ab296b186 100644 --- a/livekit/src/room/mod.rs +++ b/livekit/src/room/mod.rs @@ -23,7 +23,7 @@ use libwebrtc::{ rtp_transceiver::RtpTransceiver, RtcError, }; -use livekit_api::signal_client::SignalOptions; +use livekit_api::signal_client::{SignalOptions, SignalSdkOptions}; use livekit_protocol as proto; use livekit_protocol::observer::Dispatcher; use livekit_runtime::JoinHandle; @@ -31,7 +31,10 @@ use parking_lot::RwLock; pub use proto::DisconnectReason; use proto::{promise::Promise, SignalTarget}; use thiserror::Error; -use tokio::sync::{mpsc, oneshot, Mutex as AsyncMutex}; +use tokio::{ + signal, + sync::{mpsc, oneshot, Mutex as AsyncMutex}, +}; pub use self::{ e2ee::{manager::E2eeManager, E2eeOptions}, @@ -55,6 +58,8 @@ pub mod publication; pub mod track; pub(crate) mod utils; +pub const SDK_VERSION: &str = env!("CARGO_PKG_VERSION"); + pub type RoomResult = Result; #[derive(Error, Debug)] @@ -251,6 +256,29 @@ pub struct ChatMessage { } #[derive(Debug, Clone)] +#[non_exhaustive] +pub struct RoomSdkOptions { + pub sdk: String, + pub sdk_version: String, +} + +impl Default for RoomSdkOptions { + fn default() -> Self { + Self { sdk: "rust".to_string(), sdk_version: SDK_VERSION.to_string() } + } +} + +impl From for SignalSdkOptions { + fn from(options: RoomSdkOptions) -> Self { + let mut sdk_options = SignalSdkOptions::default(); + sdk_options.sdk = options.sdk; + sdk_options.sdk_version = Some(options.sdk_version); + sdk_options + } +} + +#[derive(Debug, Clone)] +#[non_exhaustive] pub struct RoomOptions { pub auto_subscribe: bool, pub adaptive_stream: bool, @@ -258,6 +286,7 @@ pub struct RoomOptions { pub e2ee: Option, pub rtc_config: RtcConfiguration, pub join_retries: u32, + pub sdk_options: RoomSdkOptions, } impl Default for RoomOptions { @@ -276,6 +305,7 @@ impl Default for RoomOptions { ice_transport_type: IceTransportsType::All, }, join_retries: 3, + sdk_options: RoomSdkOptions::default(), } } } @@ -331,15 +361,16 @@ impl Room { ) -> RoomResult<(Self, mpsc::UnboundedReceiver)> { // TODO(theomonnom): move connection logic to the RoomSession let e2ee_manager = E2eeManager::new(options.e2ee.clone()); + let mut signal_options = SignalOptions::default(); + signal_options.sdk_options = options.sdk_options.clone().into(); + signal_options.auto_subscribe = options.auto_subscribe; + signal_options.adaptive_stream = options.adaptive_stream; let (rtc_engine, join_response, engine_events) = RtcEngine::connect( url, token, EngineOptions { rtc_config: options.rtc_config.clone(), - signal_options: SignalOptions { - auto_subscribe: options.auto_subscribe, - adaptive_stream: options.adaptive_stream, - }, + signal_options, join_retries: options.join_retries, }, ) @@ -445,7 +476,7 @@ impl Room { }), remote_participants: Default::default(), active_speakers: Default::default(), - options, + options: options.clone(), rtc_engine: rtc_engine.clone(), local_participant, dispatcher: dispatcher.clone(), From 2aa9500ad36c28b694cab671d742bfb01db5d75b Mon Sep 17 00:00:00 2001 From: Ben Cherry Date: Thu, 24 Oct 2024 14:29:01 -0700 Subject: [PATCH 065/274] RPC implementation + FFI (#461) * Add types * gen proto * callback * wip * wip * proto * error * wip * Builds and passes * close * string * string * Fixes * more fixes * initial example * wip * compiling * almost * close * somewhat working * working sample * cleanup * ffi building * delegate method * ffi * Fixes * remove dead code * Logging * fixes * fix * fix * logging * comment out' * handle * room handle * Revert "room handle" This reverts commit c62dc0e7ada53a0eb6e3254a8aaf3cbee0fe131a. * handle * cb * cleanup example * cleanup debug logs * cleanup other logs * remove some logging * SimplifyW * errors * fixes * fixes * fmt * waiter * 10k * sender->caller * update example * perform_rpc_request->perform_rpc * move methods to participant * fmt * cleanup * ms * opt * panics * fmt * remove conn * use webrtc uuid * Move waiter * refactor for readability * Simplify * uui * flat * fmt * fix? * fmt * unused imports * start time * better * fix * store rpc state in one spot * macro * opts * rusty * simplify * rm * fmt * remove initial wait * Revert "Merge remote-tracking branch 'origin/main' into bcherry/rpc-full" This reverts commit 961f3b69ed5d67a2767402d2b2648e47e4d379a1, reversing changes made to 73106cfb0211508f9564002c0eac1a5683d49d75. * fix * v * fix pb2 * proto * commit * p * 123 * p * stats * wip * wip * wip * import * add min version check * remove empty callbacks * fixes * rm * 1 * fmt * jr * fmt --- Cargo.lock | 1 + examples/Cargo.lock | 62 ++- examples/Cargo.toml | 1 + examples/basic_room/src/main.rs | 4 +- examples/rpc/Cargo.toml | 13 + examples/rpc/src/main.rs | 268 +++++++++++++ livekit-api/src/services/sip.rs | 3 +- livekit-ffi/generate_proto.sh | 3 +- livekit-ffi/protocol/ffi.proto | 17 +- livekit-ffi/protocol/rpc.proto | 81 ++++ livekit-ffi/src/conversion/participant.rs | 2 +- livekit-ffi/src/livekit.proto.rs | 132 ++++++- livekit-ffi/src/server/mod.rs | 7 +- livekit-ffi/src/server/participant.rs | 171 +++++++++ livekit-ffi/src/server/requests.rs | 76 +++- livekit-ffi/src/server/room.rs | 38 +- livekit-ffi/src/server/utils.rs | 2 +- livekit/Cargo.toml | 1 + livekit/src/prelude.rs | 4 +- livekit/src/room/mod.rs | 53 +++ .../src/room/participant/local_participant.rs | 355 ++++++++++++++++-- livekit/src/room/participant/mod.rs | 2 + livekit/src/room/participant/rpc.rs | 112 ++++++ livekit/src/rtc_engine/mod.rs | 44 +++ livekit/src/rtc_engine/rtc_session.rs | 52 +++ soxr-sys/src/lib.rs | 2 - 26 files changed, 1441 insertions(+), 65 deletions(-) create mode 100644 examples/rpc/Cargo.toml create mode 100644 examples/rpc/src/main.rs create mode 100644 livekit-ffi/protocol/rpc.proto create mode 100644 livekit-ffi/src/server/participant.rs create mode 100644 livekit/src/room/participant/rpc.rs diff --git a/Cargo.lock b/Cargo.lock index 6b2c90388..4c109ac0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1607,6 +1607,7 @@ dependencies = [ "log", "parking_lot", "prost 0.12.3", + "semver", "serde", "serde_json", "thiserror", diff --git a/examples/Cargo.lock b/examples/Cargo.lock index 94d6b303f..06ad21b8f 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -106,6 +106,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + [[package]] name = "android_log-sys" version = "0.3.1" @@ -658,11 +664,16 @@ checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" [[package]] name = "chrono" -version = "0.4.31" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", "num-traits", + "wasm-bindgen", + "windows-targets 0.52.0", ] [[package]] @@ -1842,6 +1853,29 @@ dependencies = [ "tokio-native-tls", ] +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "idna" version = "0.5.0" @@ -2153,6 +2187,7 @@ checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" name = "livekit" version = "0.6.0" dependencies = [ + "chrono", "futures-util", "lazy_static", "libwebrtc", @@ -2162,6 +2197,7 @@ dependencies = [ "log", "parking_lot", "prost 0.12.3", + "semver", "serde", "serde_json", "thiserror", @@ -3281,6 +3317,19 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "rpc_example" +version = "0.1.0" +dependencies = [ + "env_logger", + "livekit", + "livekit-api", + "log", + "rand", + "serde_json", + "tokio", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -4670,6 +4719,15 @@ dependencies = [ "windows-targets 0.42.2", ] +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.0", +] + [[package]] name = "windows-sys" version = "0.45.0" diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 0f0cb3d4d..c1336ef69 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -6,4 +6,5 @@ members = [ "wgpu_room", "webhooks", "api", + "rpc", ] diff --git a/examples/basic_room/src/main.rs b/examples/basic_room/src/main.rs index b023feb57..e82b784c2 100644 --- a/examples/basic_room/src/main.rs +++ b/examples/basic_room/src/main.rs @@ -24,9 +24,7 @@ async fn main() { .to_jwt() .unwrap(); - let (room, mut rx) = Room::connect(&url, &token, RoomOptions::default()) - .await - .unwrap(); + 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() diff --git a/examples/rpc/Cargo.toml b/examples/rpc/Cargo.toml new file mode 100644 index 000000000..85b6b2ab8 --- /dev/null +++ b/examples/rpc/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "rpc_example" +version = "0.1.0" +edition = "2021" + +[dependencies] +tokio = { version = "1", features = ["full"] } +env_logger = "0.10" +livekit = { path = "../../livekit", features = ["native-tls"]} +livekit-api = { path = "../../livekit-api"} +log = "0.4" +rand = "0.8" +serde_json = "1.0" diff --git a/examples/rpc/src/main.rs b/examples/rpc/src/main.rs new file mode 100644 index 000000000..152f380e1 --- /dev/null +++ b/examples/rpc/src/main.rs @@ -0,0 +1,268 @@ +use livekit::prelude::*; +use livekit_api::access_token; +use rand::Rng; +use serde_json::{json, Value}; +use std::env; +use std::sync::Once; +use std::sync::{ + atomic::{AtomicU64, Ordering}, + Arc, +}; +use std::time::{Duration, Instant}; +use tokio::time::sleep; + +// Example usage of RPC calls between participants +// (In a real app, you'd have one participant per client/device such as an agent and a browser app) +// +// Try it with `LIVEKIT_URL= LIVEKIT_API_KEY= LIVEKIT_API_SECRET= cargo run` + +static START_TIME: Once = Once::new(); +static mut START_INSTANT: Option = None; + +fn get_start_time() -> Instant { + unsafe { + START_TIME.call_once(|| { + START_INSTANT = Some(Instant::now()); + }); + START_INSTANT.unwrap() + } +} + +fn elapsed_time() -> String { + let start = get_start_time(); + let elapsed = Instant::now().duration_since(start); + format!("+{:.3}s", elapsed.as_secs_f64()) +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + env_logger::init(); + + // Initialize START_TIME + get_start_time(); + + let url = env::var("LIVEKIT_URL").expect("LIVEKIT_URL is not set"); + let api_key = env::var("LIVEKIT_API_KEY").expect("LIVEKIT_API_KEY is not set"); + let api_secret = env::var("LIVEKIT_API_SECRET").expect("LIVEKIT_API_SECRET is not set"); + + let room_name = format!("rpc-test-{:x}", rand::thread_rng().gen::()); + println!("[{}] Connecting participants to room: {}", elapsed_time(), room_name); + + let (callers_room, greeters_room, math_genius_room) = tokio::try_join!( + connect_participant("caller", &room_name, &url, &api_key, &api_secret), + connect_participant("greeter", &room_name, &url, &api_key, &api_secret), + connect_participant("math-genius", &room_name, &url, &api_key, &api_secret) + )?; + + register_receiver_methods(&greeters_room, &math_genius_room).await; + + println!("\n\nRunning greeting example..."); + perform_greeting(&callers_room).await?; + + println!("\n\nRunning error handling example..."); + perform_division(&callers_room).await?; + + println!("\n\nRunning math example..."); + perform_square_root(&callers_room).await?; + sleep(Duration::from_secs(2)).await; + perform_quantum_hypergeometric_series(&callers_room).await?; + + println!("\n\nParticipants done, disconnecting..."); + callers_room.close().await?; + greeters_room.close().await?; + math_genius_room.close().await?; + + println!("Participants disconnected. Example completed."); + + Ok(()) +} + +async fn register_receiver_methods(greeters_room: &Arc, math_genius_room: &Arc) { + greeters_room.local_participant().register_rpc_method( + "arrival".to_string(), + |_, caller_identity, payload, _| { + Box::pin(async move { + println!( + "[{}] [Greeter] Oh {} arrived and said \"{}\"", + elapsed_time(), + caller_identity, + payload + ); + sleep(Duration::from_secs(2)).await; + Ok("Welcome and have a wonderful day!".to_string()) + }) + }, + ); + + math_genius_room.local_participant().register_rpc_method("square-root".to_string(), |_, caller_identity, payload, response_timeout_ms| { + Box::pin(async move { + let json_data: Value = serde_json::from_str(&payload).unwrap(); + let number = json_data["number"].as_f64().unwrap(); + println!( + "[{}] [Math Genius] I guess {} wants the square root of {}. I've only got {} seconds to respond but I think I can pull it off.", + elapsed_time(), + caller_identity, + number, + response_timeout_ms.as_secs() + ); + + println!("[{}] [Math Genius] *doing math*…", elapsed_time()); + sleep(Duration::from_secs(2)).await; + + let result = number.sqrt(); + println!("[{}] [Math Genius] Aha! It's {}", elapsed_time(), result); + Ok(json!({"result": result}).to_string()) + }) + }); + + math_genius_room.local_participant().register_rpc_method( + "divide".to_string(), + |_, caller_identity, payload, _| { + Box::pin(async move { + let json_data: Value = serde_json::from_str(&payload).unwrap(); + let dividend = json_data["dividend"].as_i64().unwrap(); + let divisor = json_data["divisor"].as_i64().unwrap(); + println!( + "[{}] [Math Genius] {} wants me to divide {} by {}.", + elapsed_time(), + caller_identity, + dividend, + divisor + ); + + let result = dividend / divisor; + println!("[{}] [Math Genius] The result is {}", elapsed_time(), result); + Ok(json!({"result": result}).to_string()) + }) + }, + ); +} + +async fn perform_greeting(room: &Arc) -> Result<(), Box> { + println!("[{}] Letting the greeter know that I've arrived", elapsed_time()); + match room + .local_participant() + .perform_rpc("greeter".to_string(), "arrival".to_string(), "Hello".to_string(), None) + .await + { + Ok(response) => { + println!("[{}] That's nice, the greeter said: \"{}\"", elapsed_time(), response) + } + Err(e) => println!("[{}] RPC call failed: {:?}", elapsed_time(), e), + } + Ok(()) +} + +async fn perform_square_root(room: &Arc) -> Result<(), Box> { + println!("[{}] What's the square root of 16?", elapsed_time()); + match room + .local_participant() + .perform_rpc( + "math-genius".to_string(), + "square-root".to_string(), + json!({"number": 16}).to_string(), + None, + ) + .await + { + Ok(response) => { + let parsed_response: Value = serde_json::from_str(&response)?; + println!("[{}] Nice, the answer was {}", elapsed_time(), parsed_response["result"]); + } + Err(e) => log::error!("[{}] RPC call failed: {:?}", elapsed_time(), e), + } + Ok(()) +} + +async fn perform_quantum_hypergeometric_series( + room: &Arc, +) -> Result<(), Box> { + println!("[{}] What's the quantum hypergeometric series of 42?", elapsed_time()); + match room + .local_participant() + .perform_rpc( + "math-genius".to_string(), + "quantum-hypergeometric-series".to_string(), + json!({"number": 42}).to_string(), + None, + ) + .await + { + Ok(response) => { + let parsed_response: Value = serde_json::from_str(&response)?; + println!("[{}] genius says {}!", elapsed_time(), parsed_response["result"]); + } + Err(e) => { + if e.code == RpcErrorCode::UnsupportedMethod as u32 { + println!("[{}] Aww looks like the genius doesn't know that one.", elapsed_time()); + return Ok(()); + } + log::error!("[{}] RPC error: {} (code: {})", elapsed_time(), e.message, e.code); + } + } + Ok(()) +} + +async fn perform_division(room: &Arc) -> Result<(), Box> { + println!("[{}] Let's try dividing 5 by 0", elapsed_time()); + match room + .local_participant() + .perform_rpc( + "math-genius".to_string(), + "divide".to_string(), + json!({"dividend": 5, "divisor": 0}).to_string(), + None, + ) + .await + { + Ok(response) => { + let parsed_response: Value = serde_json::from_str(&response)?; + println!("[{}] The result is {}", elapsed_time(), parsed_response["result"]); + } + Err(e) => { + println!("[{}] Oops! Dividing by zero didn't work. That's ok...", elapsed_time()); + log::error!("[{}] RPC error: {} (code: {})", elapsed_time(), e.message, e.code); + } + } + + Ok(()) +} + +async fn connect_participant( + identity: &str, + room_name: &str, + url: &str, + api_key: &str, + api_secret: &str, +) -> Result, Box> { + let token = access_token::AccessToken::with_api_key(api_key, api_secret) + .with_identity(identity) + .with_name(identity) + .with_grants(access_token::VideoGrants { + room_join: true, + room: room_name.to_string(), + ..Default::default() + }) + .to_jwt()?; + + println!("[{}] [{}] Connecting...", elapsed_time(), identity); + let (room, mut rx) = Room::connect(url, &token, RoomOptions::default()).await?; + + let room = Arc::new(room); + + tokio::spawn({ + let identity = identity.to_string(); + let room_clone = Arc::clone(&room); + async move { + while let Some(event) = rx.recv().await { + if let RoomEvent::Disconnected { .. } = event { + println!("[{}] Disconnected from room", identity); + break; + } + } + room_clone.close().await.ok(); + } + }); + + Ok(room) +} diff --git a/livekit-api/src/services/sip.rs b/livekit-api/src/services/sip.rs index aaf796f5d..e2517be9d 100644 --- a/livekit-api/src/services/sip.rs +++ b/livekit-api/src/services/sip.rs @@ -14,9 +14,8 @@ use livekit_protocol as proto; use std::collections::HashMap; -use std::ptr::null; -use crate::access_token::{SIPGrants, VideoGrants}; +use crate::access_token::SIPGrants; use crate::get_env_keys; use crate::services::twirp_client::TwirpClient; use crate::services::{ServiceBase, ServiceResult, LIVEKIT_PACKAGE}; diff --git a/livekit-ffi/generate_proto.sh b/livekit-ffi/generate_proto.sh index 19a1ccc15..b1d5e9d6a 100755 --- a/livekit-ffi/generate_proto.sh +++ b/livekit-ffi/generate_proto.sh @@ -28,4 +28,5 @@ protoc \ $PROTOCOL/video_frame.proto \ $PROTOCOL/audio_frame.proto \ $PROTOCOL/e2ee.proto \ - $PROTOCOL/stats.proto + $PROTOCOL/stats.proto \ + $PROTOCOL/rpc.proto diff --git a/livekit-ffi/protocol/ffi.proto b/livekit-ffi/protocol/ffi.proto index 32bfe4416..e8c1fda4a 100644 --- a/livekit-ffi/protocol/ffi.proto +++ b/livekit-ffi/protocol/ffi.proto @@ -23,6 +23,7 @@ import "track.proto"; import "room.proto"; import "video_frame.proto"; import "audio_frame.proto"; +import "rpc.proto"; // **How is the livekit-ffi working: // We refer as the ffi server the Rust server that is running the LiveKit client implementation, and we @@ -69,6 +70,7 @@ message FfiRequest { GetSessionStatsRequest get_session_stats = 12; PublishTranscriptionRequest publish_transcription = 13; PublishSipDtmfRequest publish_sip_dtmf = 14; + // Track CreateVideoTrackRequest create_video_track = 15; @@ -96,9 +98,14 @@ message FfiRequest { NewSoxResamplerRequest new_sox_resampler = 33; PushSoxResamplerRequest push_sox_resampler = 34; FlushSoxResamplerRequest flush_sox_resampler = 35; - SendChatMessageRequest send_chat_message = 36; EditChatMessageRequest edit_chat_message = 37; + + // RPC + PerformRpcRequest perform_rpc = 38; + RegisterRpcMethodRequest register_rpc_method = 39; + UnregisterRpcMethodRequest unregister_rpc_method = 40; + RpcMethodInvocationResponseRequest rpc_method_invocation_response = 41; } } @@ -147,8 +154,12 @@ message FfiResponse { NewSoxResamplerResponse new_sox_resampler = 33; PushSoxResamplerResponse push_sox_resampler = 34; FlushSoxResamplerResponse flush_sox_resampler = 35; - SendChatMessageResponse send_chat_message = 36; + // RPC + PerformRpcResponse perform_rpc = 37; + RegisterRpcMethodResponse register_rpc_method = 38; + UnregisterRpcMethodResponse unregister_rpc_method = 39; + RpcMethodInvocationResponseResponse rpc_method_invocation_response = 40; } } @@ -178,6 +189,8 @@ message FfiEvent { Panic panic = 20; PublishSipDtmfCallback publish_sip_dtmf = 21; SendChatMessageCallback chat_message = 22; + PerformRpcCallback perform_rpc = 23; + RpcMethodInvocationEvent rpc_method_invocation = 24; } } diff --git a/livekit-ffi/protocol/rpc.proto b/livekit-ffi/protocol/rpc.proto new file mode 100644 index 000000000..19fd75f11 --- /dev/null +++ b/livekit-ffi/protocol/rpc.proto @@ -0,0 +1,81 @@ +// Copyright 2023 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto2"; + +package livekit.proto; +option csharp_namespace = "LiveKit.Proto"; + +message RpcError { + required uint32 code = 1; + required string message = 2; + optional string data = 3; +} + +// FFI Requests +message PerformRpcRequest { + required uint64 local_participant_handle = 1; + required string destination_identity = 2; + required string method = 3; + required string payload = 4; + optional uint32 response_timeout_ms = 5; +} + +message RegisterRpcMethodRequest { + required uint64 local_participant_handle = 1; + required string method = 2; +} + +message UnregisterRpcMethodRequest { + required uint64 local_participant_handle = 1; + required string method = 2; +} + +message RpcMethodInvocationResponseRequest { + required uint64 local_participant_handle = 1; + required uint64 invocation_id = 2; + optional string payload = 3; + optional RpcError error = 4; +} + +// FFI Responses +message PerformRpcResponse { + required uint64 async_id = 1; +} + +message RegisterRpcMethodResponse {} + +message UnregisterRpcMethodResponse {} + +message RpcMethodInvocationResponseResponse { + optional string error = 1; +} + +// FFI Callbacks +message PerformRpcCallback { + required uint64 async_id = 1; + optional string payload = 2; + optional RpcError error = 3; +} + +// FFI Events +message RpcMethodInvocationEvent { + required uint64 local_participant_handle = 1; + required uint64 invocation_id = 2; + required string method = 3; + required string request_id = 4; + required string caller_identity = 5; + required string payload = 6; + required uint32 response_timeout_ms = 7; +} diff --git a/livekit-ffi/src/conversion/participant.rs b/livekit-ffi/src/conversion/participant.rs index f8041f8d7..755f0bd06 100644 --- a/livekit-ffi/src/conversion/participant.rs +++ b/livekit-ffi/src/conversion/participant.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{proto, server::room::FfiParticipant}; +use crate::{proto, server::participant::FfiParticipant}; use livekit::ParticipantKind; impl From<&FfiParticipant> for proto::ParticipantInfo { diff --git a/livekit-ffi/src/livekit.proto.rs b/livekit-ffi/src/livekit.proto.rs index 367e02741..a068e502d 100644 --- a/livekit-ffi/src/livekit.proto.rs +++ b/livekit-ffi/src/livekit.proto.rs @@ -3551,6 +3551,110 @@ impl AudioSourceType { } } } +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RpcError { + #[prost(uint32, required, tag="1")] + pub code: u32, + #[prost(string, required, tag="2")] + pub message: ::prost::alloc::string::String, + #[prost(string, optional, tag="3")] + pub data: ::core::option::Option<::prost::alloc::string::String>, +} +/// FFI Requests +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PerformRpcRequest { + #[prost(uint64, required, tag="1")] + pub local_participant_handle: u64, + #[prost(string, required, tag="2")] + pub destination_identity: ::prost::alloc::string::String, + #[prost(string, required, tag="3")] + pub method: ::prost::alloc::string::String, + #[prost(string, required, tag="4")] + pub payload: ::prost::alloc::string::String, + #[prost(uint32, optional, tag="5")] + pub response_timeout_ms: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RegisterRpcMethodRequest { + #[prost(uint64, required, tag="1")] + pub local_participant_handle: u64, + #[prost(string, required, tag="2")] + pub method: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct UnregisterRpcMethodRequest { + #[prost(uint64, required, tag="1")] + pub local_participant_handle: u64, + #[prost(string, required, tag="2")] + pub method: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RpcMethodInvocationResponseRequest { + #[prost(uint64, required, tag="1")] + pub local_participant_handle: u64, + #[prost(uint64, required, tag="2")] + pub invocation_id: u64, + #[prost(string, optional, tag="3")] + pub payload: ::core::option::Option<::prost::alloc::string::String>, + #[prost(message, optional, tag="4")] + pub error: ::core::option::Option, +} +/// FFI Responses +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PerformRpcResponse { + #[prost(uint64, required, tag="1")] + pub async_id: u64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RegisterRpcMethodResponse { +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct UnregisterRpcMethodResponse { +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RpcMethodInvocationResponseResponse { + #[prost(string, optional, tag="1")] + pub error: ::core::option::Option<::prost::alloc::string::String>, +} +/// FFI Callbacks +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PerformRpcCallback { + #[prost(uint64, required, tag="1")] + pub async_id: u64, + #[prost(string, optional, tag="2")] + pub payload: ::core::option::Option<::prost::alloc::string::String>, + #[prost(message, optional, tag="3")] + pub error: ::core::option::Option, +} +/// FFI Events +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RpcMethodInvocationEvent { + #[prost(uint64, required, tag="1")] + pub local_participant_handle: u64, + #[prost(uint64, required, tag="2")] + pub invocation_id: u64, + #[prost(string, required, tag="3")] + pub method: ::prost::alloc::string::String, + #[prost(string, required, tag="4")] + pub request_id: ::prost::alloc::string::String, + #[prost(string, required, tag="5")] + pub caller_identity: ::prost::alloc::string::String, + #[prost(string, required, tag="6")] + pub payload: ::prost::alloc::string::String, + #[prost(uint32, required, tag="7")] + pub response_timeout_ms: u32, +} // **How is the livekit-ffi working: // We refer as the ffi server the Rust server that is running the LiveKit client implementation, and we // refer as the ffi client the foreign language that commumicates with the ffi server. (e.g Python SDK, Unity SDK, etc...) @@ -3582,7 +3686,7 @@ impl AudioSourceType { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiRequest { - #[prost(oneof="ffi_request::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37")] + #[prost(oneof="ffi_request::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41")] pub message: ::core::option::Option, } /// Nested message and enum types in `FfiRequest`. @@ -3666,13 +3770,22 @@ pub mod ffi_request { SendChatMessage(super::SendChatMessageRequest), #[prost(message, tag="37")] EditChatMessage(super::EditChatMessageRequest), + /// RPC + #[prost(message, tag="38")] + PerformRpc(super::PerformRpcRequest), + #[prost(message, tag="39")] + RegisterRpcMethod(super::RegisterRpcMethodRequest), + #[prost(message, tag="40")] + UnregisterRpcMethod(super::UnregisterRpcMethodRequest), + #[prost(message, tag="41")] + RpcMethodInvocationResponse(super::RpcMethodInvocationResponseRequest), } } /// This is the output of livekit_ffi_request function. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiResponse { - #[prost(oneof="ffi_response::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36")] + #[prost(oneof="ffi_response::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40")] pub message: ::core::option::Option, } /// Nested message and enum types in `FfiResponse`. @@ -3754,6 +3867,15 @@ pub mod ffi_response { FlushSoxResampler(super::FlushSoxResamplerResponse), #[prost(message, tag="36")] SendChatMessage(super::SendChatMessageResponse), + /// RPC + #[prost(message, tag="37")] + PerformRpc(super::PerformRpcResponse), + #[prost(message, tag="38")] + RegisterRpcMethod(super::RegisterRpcMethodResponse), + #[prost(message, tag="39")] + UnregisterRpcMethod(super::UnregisterRpcMethodResponse), + #[prost(message, tag="40")] + RpcMethodInvocationResponse(super::RpcMethodInvocationResponseResponse), } } /// To minimize complexity, participant events are not included in the protocol. @@ -3762,7 +3884,7 @@ pub mod ffi_response { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiEvent { - #[prost(oneof="ffi_event::Message", tags="1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22")] + #[prost(oneof="ffi_event::Message", tags="1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24")] pub message: ::core::option::Option, } /// Nested message and enum types in `FfiEvent`. @@ -3812,6 +3934,10 @@ pub mod ffi_event { PublishSipDtmf(super::PublishSipDtmfCallback), #[prost(message, tag="22")] ChatMessage(super::SendChatMessageCallback), + #[prost(message, tag="23")] + PerformRpc(super::PerformRpcCallback), + #[prost(message, tag="24")] + RpcMethodInvocation(super::RpcMethodInvocationEvent), } } /// Stop all rooms synchronously (Do we need async here?). diff --git a/livekit-ffi/src/server/mod.rs b/livekit-ffi/src/server/mod.rs index 461439ab9..cd015b5f8 100644 --- a/livekit-ffi/src/server/mod.rs +++ b/livekit-ffi/src/server/mod.rs @@ -13,7 +13,6 @@ // limitations under the License. use std::{ - collections::HashMap, error::Error, sync::{ atomic::{AtomicU64, Ordering}, @@ -27,10 +26,7 @@ use dashmap::{mapref::one::MappedRef, DashMap}; use downcast_rs::{impl_downcast, Downcast}; use livekit::webrtc::{native::audio_resampler::AudioResampler, prelude::*}; use parking_lot::{deadlock, Mutex}; -use tokio::{ - sync::{broadcast, oneshot}, - task::JoinHandle, -}; +use tokio::{sync::oneshot, task::JoinHandle}; use crate::{proto, proto::FfiEvent, FfiError, FfiHandleId, FfiResult, INVALID_HANDLE}; @@ -38,6 +34,7 @@ pub mod audio_source; pub mod audio_stream; pub mod colorcvt; pub mod logger; +pub mod participant; pub mod requests; pub mod resampler; pub mod room; diff --git a/livekit-ffi/src/server/participant.rs b/livekit-ffi/src/server/participant.rs new file mode 100644 index 000000000..fdf54bd60 --- /dev/null +++ b/livekit-ffi/src/server/participant.rs @@ -0,0 +1,171 @@ +// Copyright 2023 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::sync::Arc; + +use livekit::prelude::*; +use std::time::Duration; +use tokio::sync::oneshot; + +use crate::{ + proto, + server::room::RoomInner, + server::{FfiHandle, FfiServer}, + FfiError, FfiHandleId, FfiResult, +}; + +#[derive(Clone)] +pub struct FfiParticipant { + pub handle: FfiHandleId, + pub participant: Participant, + pub room: Arc, +} + +impl FfiHandle for FfiParticipant {} + +impl FfiParticipant { + pub fn perform_rpc( + &self, + server: &'static FfiServer, + request: proto::PerformRpcRequest, + ) -> FfiResult { + let async_id = server.next_id(); + + let local = match &self.participant { + Participant::Local(local) => local.clone(), + Participant::Remote(_) => { + return Err(FfiError::InvalidRequest("Expected local participant".into())) + } + }; + + let handle = server.async_runtime.spawn(async move { + let result = local + .perform_rpc( + request.destination_identity.to_string(), + request.method, + request.payload, + request.response_timeout_ms, + ) + .await; + + let callback = proto::PerformRpcCallback { + async_id, + payload: result.as_ref().ok().cloned(), + error: result.as_ref().err().map(|error| proto::RpcError { + code: error.code, + message: error.message.clone(), + data: error.data.clone(), + }), + }; + + let _ = server.send_event(proto::ffi_event::Message::PerformRpc(callback)); + }); + server.watch_panic(handle); + Ok(proto::PerformRpcResponse { async_id }) + } + + pub fn register_rpc_method( + &self, + server: &'static FfiServer, + request: proto::RegisterRpcMethodRequest, + ) -> FfiResult { + let method = request.method.clone(); + + let local = match &self.participant { + Participant::Local(local) => local.clone(), + Participant::Remote(_) => { + return Err(FfiError::InvalidRequest("Expected local participant".into())) + } + }; + + let local_participant_handle = self.handle.clone(); + let room: Arc = self.room.clone(); + local.register_rpc_method( + method.clone(), + move |request_id, caller_identity, payload, response_timeout| { + Box::pin({ + let room = room.clone(); + let method = method.clone(); + async move { + forward_rpc_method_invocation( + server, + room, + local_participant_handle, + method, + request_id, + caller_identity, + payload, + response_timeout, + ) + .await + } + }) + }, + ); + Ok(proto::RegisterRpcMethodResponse {}) + } + + pub fn unregister_rpc_method( + &self, + server: &'static FfiServer, + request: proto::UnregisterRpcMethodRequest, + ) -> FfiResult { + let local = match &self.participant { + Participant::Local(local) => local.clone(), + Participant::Remote(_) => { + return Err(FfiError::InvalidRequest("Expected local participant".into())) + } + }; + + local.unregister_rpc_method(request.method); + + Ok(proto::UnregisterRpcMethodResponse {}) + } +} + +async fn forward_rpc_method_invocation( + server: &'static FfiServer, + room: Arc, + local_participant_handle: FfiHandleId, + method: String, + request_id: String, + caller_identity: ParticipantIdentity, + payload: String, + response_timeout: Duration, +) -> Result { + let (tx, rx) = oneshot::channel(); + let invocation_id = server.next_id(); + + let _ = server.send_event(proto::ffi_event::Message::RpcMethodInvocation( + proto::RpcMethodInvocationEvent { + local_participant_handle: local_participant_handle as u64, + invocation_id, + method, + request_id, + caller_identity: caller_identity.into(), + payload, + response_timeout_ms: response_timeout.as_millis() as u32, + }, + )); + + room.store_rpc_method_invocation_waiter(invocation_id, tx); + + rx.await.unwrap_or_else(|_| { + Err(RpcError { + code: RpcErrorCode::ApplicationError as u32, + message: "Error from method handler".to_string(), + data: None, + }) + }) +} diff --git a/livekit-ffi/src/server/requests.rs b/livekit-ffi/src/server/requests.rs index a61fe44ea..cb3b6a715 100644 --- a/livekit-ffi/src/server/requests.rs +++ b/livekit-ffi/src/server/requests.rs @@ -22,8 +22,10 @@ use livekit::{ use parking_lot::Mutex; use super::{ - audio_source, audio_stream, colorcvt, resampler, - room::{self, FfiParticipant, FfiPublication, FfiTrack}, + audio_source, audio_stream, colorcvt, + participant::FfiParticipant, + resampler, + room::{self, FfiPublication, FfiTrack}, video_source, video_stream, FfiError, FfiResult, FfiServer, }; use crate::proto; @@ -788,6 +790,58 @@ fn on_flush_sox_resampler( } } +fn on_perform_rpc( + server: &'static FfiServer, + request: proto::PerformRpcRequest, +) -> FfiResult { + let ffi_participant = + server.retrieve_handle::(request.local_participant_handle)?.clone(); + return ffi_participant.perform_rpc(server, request); +} + +fn on_register_rpc_method( + server: &'static FfiServer, + request: proto::RegisterRpcMethodRequest, +) -> FfiResult { + let ffi_participant = + server.retrieve_handle::(request.local_participant_handle)?.clone(); + return ffi_participant.register_rpc_method(server, request); +} + +fn on_unregister_rpc_method( + server: &'static FfiServer, + request: proto::UnregisterRpcMethodRequest, +) -> FfiResult { + let ffi_participant = + server.retrieve_handle::(request.local_participant_handle)?.clone(); + return ffi_participant.unregister_rpc_method(server, request); +} + +fn on_rpc_method_invocation_response( + server: &'static FfiServer, + request: proto::RpcMethodInvocationResponseRequest, +) -> FfiResult { + let ffi_participant = + server.retrieve_handle::(request.local_participant_handle)?.clone(); + + let room = ffi_participant.room; + + let mut error: Option = None; + + if let Some(waiter) = room.take_rpc_method_invocation_waiter(request.invocation_id) { + let result = if let Some(error) = request.error.clone() { + Err(RpcError { code: error.code, message: error.message, data: error.data }) + } else { + Ok(request.payload.unwrap_or_default()) + }; + let _ = waiter.send(result); + } else { + error = Some("No caller found".to_string()); + } + + Ok(proto::RpcMethodInvocationResponseResponse { error }) +} + #[allow(clippy::field_reassign_with_default)] // Avoid uggly format pub fn handle_request( server: &'static FfiServer, @@ -922,6 +976,24 @@ pub fn handle_request( server, flush_soxr, )?) } + proto::ffi_request::Message::PerformRpc(request) => { + proto::ffi_response::Message::PerformRpc(on_perform_rpc(server, request)?) + } + proto::ffi_request::Message::RegisterRpcMethod(request) => { + proto::ffi_response::Message::RegisterRpcMethod(on_register_rpc_method( + server, request, + )?) + } + proto::ffi_request::Message::UnregisterRpcMethod(request) => { + proto::ffi_response::Message::UnregisterRpcMethod(on_unregister_rpc_method( + server, request, + )?) + } + proto::ffi_request::Message::RpcMethodInvocationResponse(request) => { + proto::ffi_response::Message::RpcMethodInvocationResponse( + on_rpc_method_invocation_response(server, request)?, + ) + } }); Ok(res) diff --git a/livekit-ffi/src/server/room.rs b/livekit-ffi/src/server/room.rs index a6da9b634..1367897e8 100644 --- a/livekit-ffi/src/server/room.rs +++ b/livekit-ffi/src/server/room.rs @@ -13,29 +13,23 @@ // limitations under the License. use std::collections::HashMap; -use std::{collections::HashSet, slice, sync::Arc, time::Duration}; +use std::time::Duration; +use std::{collections::HashSet, slice, sync::Arc}; use livekit::prelude::*; -use livekit::{participant, track, ChatMessage}; +use livekit::ChatMessage; use parking_lot::Mutex; use tokio::sync::{broadcast, mpsc, oneshot, Mutex as AsyncMutex}; use tokio::task::JoinHandle; use super::FfiDataBuffer; -use crate::conversion::room; use crate::{ proto, + server::participant::FfiParticipant, server::{FfiHandle, FfiServer}, FfiError, FfiHandleId, FfiResult, }; -#[derive(Clone)] -pub struct FfiParticipant { - pub handle: FfiHandleId, - pub participant: Participant, - pub room: Arc, -} - #[derive(Clone)] pub struct FfiPublication { pub handle: FfiHandleId, @@ -50,7 +44,6 @@ pub struct FfiTrack { impl FfiHandle for FfiTrack {} impl FfiHandle for FfiPublication {} -impl FfiHandle for FfiParticipant {} impl FfiHandle for FfiRoom {} #[derive(Clone)] @@ -73,6 +66,9 @@ pub struct RoomInner { pending_unpublished_tracks: Mutex>, track_handle_lookup: Arc>>, + + // Used to forward RPC method invocation to the FfiClient and collect their results + rpc_method_invocation_waiters: Mutex>>>, } struct Handle { @@ -140,7 +136,6 @@ impl FfiRoom { let (data_tx, data_rx) = mpsc::unbounded_channel(); let (transcription_tx, transcription_rx) = mpsc::unbounded_channel(); let (dtmf_tx, dtmf_rx) = mpsc::unbounded_channel(); - let (close_tx, close_rx) = broadcast::channel(1); let handle_id = server.next_id(); @@ -153,6 +148,7 @@ impl FfiRoom { pending_published_tracks: Default::default(), pending_unpublished_tracks: Default::default(), track_handle_lookup: Default::default(), + rpc_method_invocation_waiters: Default::default(), }); let (local_info, remote_infos) = @@ -595,7 +591,6 @@ impl RoomInner { ) .await; let sent_message = res.as_ref().unwrap().clone(); - match res { Ok(message) => { let _ = server.send_event(proto::ffi_event::Message::ChatMessage( @@ -670,6 +665,21 @@ impl RoomInner { server.watch_panic(handle); proto::SendChatMessageResponse { async_id } } + + pub fn store_rpc_method_invocation_waiter( + &self, + invocation_id: u64, + waiter: oneshot::Sender>, + ) { + self.rpc_method_invocation_waiters.lock().insert(invocation_id, waiter); + } + + pub fn take_rpc_method_invocation_waiter( + &self, + invocation_id: u64, + ) -> Option>> { + return self.rpc_method_invocation_waiters.lock().remove(&invocation_id); + } } // Task used to publish data without blocking the client thread @@ -1086,6 +1096,7 @@ async fn forward_event( }, )); } + RoomEvent::ChatMessage { message, participant } => { let (sid, identity) = match participant { Some(p) => (Some(p.sid().to_string()), p.identity().to_string()), @@ -1097,6 +1108,7 @@ async fn forward_event( participant_identity: identity, })); } + RoomEvent::ConnectionStateChanged(state) => { let _ = send_event(proto::room_event::Message::ConnectionStateChanged( proto::ConnectionStateChanged { state: proto::ConnectionState::from(state).into() }, diff --git a/livekit-ffi/src/server/utils.rs b/livekit-ffi/src/server/utils.rs index 59f0e037d..7e95cd5fd 100644 --- a/livekit-ffi/src/server/utils.rs +++ b/livekit-ffi/src/server/utils.rs @@ -1,7 +1,7 @@ use livekit::prelude::{RoomEvent, Track, TrackSource}; use tokio::sync::{broadcast, mpsc}; -use super::room::FfiParticipant; +use super::participant::FfiParticipant; use crate::{server, FfiError, FfiHandleId}; pub async fn track_changed_trigger( diff --git a/livekit/Cargo.toml b/livekit/Cargo.toml index 269ffdf1a..6e150330b 100644 --- a/livekit/Cargo.toml +++ b/livekit/Cargo.toml @@ -41,3 +41,4 @@ thiserror = "1.0" lazy_static = "1.4" log = "0.4" chrono = "0.4.38" +semver = "1.0" diff --git a/livekit/src/prelude.rs b/livekit/src/prelude.rs index f53fdda17..a7b86d62a 100644 --- a/livekit/src/prelude.rs +++ b/livekit/src/prelude.rs @@ -14,7 +14,9 @@ pub use crate::{ id::*, - participant::{ConnectionQuality, LocalParticipant, Participant, RemoteParticipant}, + participant::{ + ConnectionQuality, LocalParticipant, Participant, RemoteParticipant, RpcError, RpcErrorCode, + }, publication::{LocalTrackPublication, RemoteTrackPublication, TrackPublication}, track::{ AudioTrack, LocalAudioTrack, LocalTrack, LocalVideoTrack, RemoteAudioTrack, RemoteTrack, diff --git a/livekit/src/room/mod.rs b/livekit/src/room/mod.rs index ab296b186..28d51902f 100644 --- a/livekit/src/room/mod.rs +++ b/livekit/src/room/mod.rs @@ -255,6 +255,30 @@ pub struct ChatMessage { pub generated: Option, } +#[derive(Debug, Clone)] +pub struct RpcRequest { + pub destination_identity: String, + pub id: String, + pub method: String, + pub payload: String, + pub response_timeout_ms: u32, + pub version: u32, +} + +#[derive(Debug, Clone)] +pub struct RpcResponse { + destination_identity: String, + request_id: String, + payload: Option, + error: Option, +} + +#[derive(Debug, Clone)] +pub struct RpcAck { + destination_identity: String, + request_id: String, +} + #[derive(Debug, Clone)] #[non_exhaustive] pub struct RoomSdkOptions { @@ -660,6 +684,34 @@ impl RoomSession { EngineEvent::SipDTMF { code, digit, participant_identity } => { self.handle_dtmf(code, digit, participant_identity); } + EngineEvent::RpcRequest { + caller_identity, + request_id, + method, + payload, + response_timeout_ms, + version, + } => { + if caller_identity.is_none() { + log::warn!("Received RPC request with null caller identity"); + return Ok(()); + } + self.local_participant + .handle_incoming_rpc_request( + caller_identity.unwrap(), + request_id, + method, + payload, + response_timeout_ms, + ) + .await; + } + EngineEvent::RpcResponse { request_id, payload, error } => { + self.local_participant.handle_incoming_rpc_response(request_id, payload, error); + } + EngineEvent::RpcAck { request_id } => { + self.local_participant.handle_incoming_rpc_ack(request_id); + } EngineEvent::SpeakersChanged { speakers } => self.handle_speakers_changed(speakers), EngineEvent::ConnectionQuality { updates } => { self.handle_connection_quality_update(updates) @@ -667,6 +719,7 @@ impl RoomSession { EngineEvent::LocalTrackSubscribed { track_sid } => { self.handle_track_subscribed(track_sid) } + _ => {} } Ok(()) diff --git a/livekit/src/room/participant/local_participant.rs b/livekit/src/room/participant/local_participant.rs index 6b334abb4..8c4596763 100644 --- a/livekit/src/room/participant/local_participant.rs +++ b/livekit/src/room/participant/local_participant.rs @@ -12,30 +12,39 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::{ - collections::HashMap, - fmt::Debug, - sync::{self, Arc}, - time::Duration, -}; - -use chrono::{TimeZone, Utc}; - -use libwebrtc::{native::create_random_uuid, rtp_parameters::RtpEncodingParameters}; -use livekit_api::signal_client::SignalError; -use livekit_protocol as proto; -use livekit_runtime::timeout; -use parking_lot::Mutex; -use proto::request_response::Reason; +use std::{collections::HashMap, fmt::Debug, pin::Pin, sync::Arc, time::Duration}; use super::{ConnectionQuality, ParticipantInner, ParticipantKind}; use crate::{ e2ee::EncryptionType, options::{self, compute_video_encodings, video_layers_from_encodings, TrackPublishOptions}, prelude::*, + room::participant::rpc::{RpcError, RpcErrorCode, MAX_PAYLOAD_BYTES}, rtc_engine::{EngineError, RtcEngine}, - ChatMessage, DataPacket, SipDTMF, Transcription, + ChatMessage, DataPacket, RpcAck, RpcRequest, RpcResponse, SipDTMF, Transcription, }; +use chrono::Utc; +use futures_util::Future; + +use libwebrtc::{native::create_random_uuid, rtp_parameters::RtpEncodingParameters}; +use livekit_api::signal_client::SignalError; +use livekit_protocol as proto; +use livekit_runtime::timeout; +use parking_lot::Mutex; +use proto::request_response::Reason; +use semver::Version; +use tokio::sync::oneshot; + +type RpcHandler = Arc< + dyn Fn( + String, // request_id + ParticipantIdentity, // caller_identity + String, // payload + Duration, // response_timeout_ms + ) -> Pin> + Send>> + + Send + + Sync, +>; const REQUEST_TIMEOUT: Duration = Duration::from_secs(5); @@ -48,9 +57,26 @@ struct LocalEvents { local_track_unpublished: Mutex>, } +struct RpcState { + pending_acks: HashMap>, + pending_responses: HashMap>>, + handlers: HashMap, +} + +impl RpcState { + fn new() -> Self { + Self { + pending_acks: HashMap::new(), + pending_responses: HashMap::new(), + handlers: HashMap::new(), + } + } +} + struct LocalInfo { events: LocalEvents, encryption_type: EncryptionType, + rpc_state: Mutex, } #[derive(Clone)] @@ -82,7 +108,11 @@ impl LocalParticipant { ) -> Self { Self { inner: super::new_inner(rtc_engine, sid, identity, name, metadata, attributes, kind), - local: Arc::new(LocalInfo { events: LocalEvents::default(), encryption_type }), + local: Arc::new(LocalInfo { + events: LocalEvents::default(), + encryption_type, + rpc_state: Mutex::new(RpcState::new()), + }), } } @@ -438,16 +468,14 @@ impl LocalParticipant { let segments: Vec = packet .segments .into_iter() - .map( - (|segment| proto::TranscriptionSegment { - id: segment.id, - start_time: segment.start_time, - end_time: segment.end_time, - text: segment.text, - r#final: segment.r#final, - language: segment.language, - }), - ) + .map(|segment| proto::TranscriptionSegment { + id: segment.id, + start_time: segment.start_time, + end_time: segment.end_time, + text: segment.text, + r#final: segment.r#final, + language: segment.language, + }) .collect(); let transcription_packet = proto::Transcription { transcribed_participant_identity: packet.participant_identity, @@ -483,6 +511,76 @@ impl LocalParticipant { .map_err(Into::into) } + async fn publish_rpc_request(&self, rpc_request: RpcRequest) -> RoomResult<()> { + let destination_identities = vec![rpc_request.destination_identity]; + let rpc_request_message = proto::RpcRequest { + id: rpc_request.id, + method: rpc_request.method, + payload: rpc_request.payload, + response_timeout_ms: rpc_request.response_timeout_ms, + version: rpc_request.version, + ..Default::default() + }; + + let data = proto::DataPacket { + value: Some(proto::data_packet::Value::RpcRequest(rpc_request_message)), + destination_identities, + ..Default::default() + }; + + self.inner + .rtc_engine + .publish_data(&data, DataPacketKind::Reliable) + .await + .map_err(Into::into) + } + + async fn publish_rpc_response(&self, rpc_response: RpcResponse) -> RoomResult<()> { + let destination_identities = vec![rpc_response.destination_identity]; + let rpc_response_message = proto::RpcResponse { + request_id: rpc_response.request_id, + value: Some(match rpc_response.error { + Some(error) => proto::rpc_response::Value::Error(proto::RpcError { + code: error.code, + message: error.message, + data: error.data, + }), + None => proto::rpc_response::Value::Payload(rpc_response.payload.unwrap()), + }), + ..Default::default() + }; + + let data = proto::DataPacket { + value: Some(proto::data_packet::Value::RpcResponse(rpc_response_message)), + destination_identities: destination_identities.clone(), + ..Default::default() + }; + + self.inner + .rtc_engine + .publish_data(&data, DataPacketKind::Reliable) + .await + .map_err(Into::into) + } + + async fn publish_rpc_ack(&self, rpc_ack: RpcAck) -> RoomResult<()> { + let destination_identities = vec![rpc_ack.destination_identity]; + let rpc_ack_message = + proto::RpcAck { request_id: rpc_ack.request_id, ..Default::default() }; + + let data = proto::DataPacket { + value: Some(proto::data_packet::Value::RpcAck(rpc_ack_message)), + destination_identities: destination_identities.clone(), + ..Default::default() + }; + + self.inner + .rtc_engine + .publish_data(&data, DataPacketKind::Reliable) + .await + .map_err(Into::into) + } + pub fn get_track_publication(&self, sid: &TrackSid) -> Option { self.inner.track_publications.read().get(sid).map(|track| { if let TrackPublication::Local(local) = track { @@ -544,4 +642,207 @@ impl LocalParticipant { pub fn kind(&self) -> ParticipantKind { self.inner.info.read().kind } + + pub async fn perform_rpc( + &self, + destination_identity: String, + method: String, + payload: String, + response_timeout_ms: Option, + ) -> Result { + let response_timeout = Duration::from_millis(response_timeout_ms.unwrap_or(10000) as u64); + let max_round_trip_latency = Duration::from_millis(2000); + + if payload.len() > MAX_PAYLOAD_BYTES { + return Err(RpcError::built_in(RpcErrorCode::RequestPayloadTooLarge, None)); + } + + if let Some(server_info) = + self.inner.rtc_engine.session().signal_client().join_response().server_info + { + if !server_info.version.is_empty() { + let server_version = Version::parse(&server_info.version).unwrap(); + let min_required_version = Version::parse("1.8.0").unwrap(); + if server_version < min_required_version { + return Err(RpcError::built_in(RpcErrorCode::UnsupportedServer, None)); + } + } + } + + let id = create_random_uuid(); + let (ack_tx, ack_rx) = oneshot::channel(); + let (response_tx, response_rx) = oneshot::channel(); + + match self + .publish_rpc_request(RpcRequest { + destination_identity: destination_identity.clone(), + id: id.clone(), + method: method.clone(), + payload: payload.clone(), + response_timeout_ms: (response_timeout - max_round_trip_latency).as_millis() as u32, + version: 1, + }) + .await + { + Ok(_) => { + let mut rpc_state = self.local.rpc_state.lock(); + rpc_state.pending_acks.insert(id.clone(), ack_tx); + rpc_state.pending_responses.insert(id.clone(), response_tx); + } + Err(e) => { + log::error!("Failed to publish RPC request: {}", e); + return Err(RpcError::built_in(RpcErrorCode::SendFailed, Some(e.to_string()))); + } + } + + // Wait for ack timeout + match tokio::time::timeout(max_round_trip_latency, ack_rx).await { + Err(_) => { + let mut rpc_state = self.local.rpc_state.lock(); + rpc_state.pending_acks.remove(&id); + rpc_state.pending_responses.remove(&id); + return Err(RpcError::built_in(RpcErrorCode::ConnectionTimeout, None)); + } + Ok(_) => { + // Ack received, continue to wait for response + } + } + + // Wait for response timout + let response = match tokio::time::timeout(response_timeout, response_rx).await { + Err(_) => { + self.local.rpc_state.lock().pending_responses.remove(&id); + return Err(RpcError::built_in(RpcErrorCode::ResponseTimeout, None)); + } + Ok(result) => result, + }; + + match response { + Err(_) => { + // Something went wrong locally + Err(RpcError::built_in(RpcErrorCode::RecipientDisconnected, None)) + } + Ok(Err(e)) => { + // RPC error from remote, forward it + Err(e) + } + Ok(Ok(payload)) => { + // Successful response + Ok(payload) + } + } + } + + pub fn register_rpc_method( + &self, + method: String, + handler: impl Fn( + String, + ParticipantIdentity, + String, + Duration, + ) -> Pin> + Send>> + + Send + + Sync + + 'static, + ) { + self.local.rpc_state.lock().handlers.insert(method, Arc::new(handler)); + } + + pub fn unregister_rpc_method(&self, method: String) { + self.local.rpc_state.lock().handlers.remove(&method); + } + + pub(crate) fn handle_incoming_rpc_ack(&self, request_id: String) { + let mut rpc_state = self.local.rpc_state.lock(); + if let Some(tx) = rpc_state.pending_acks.remove(&request_id) { + let _ = tx.send(()); + } else { + log::error!("Ack received for unexpected RPC request: {}", request_id); + } + } + + pub(crate) fn handle_incoming_rpc_response( + &self, + request_id: String, + payload: Option, + error: Option, + ) { + let mut rpc_state = self.local.rpc_state.lock(); + if let Some(tx) = rpc_state.pending_responses.remove(&request_id) { + let _ = tx.send(match error { + Some(e) => Err(RpcError::from_proto(e)), + None => Ok(payload.unwrap_or_default()), + }); + } else { + log::error!("Response received for unexpected RPC request: {}", request_id); + } + } + + pub(crate) async fn handle_incoming_rpc_request( + &self, + caller_identity: ParticipantIdentity, + request_id: String, + method: String, + payload: String, + response_timeout_ms: u32, + ) { + if let Err(e) = self + .publish_rpc_ack(RpcAck { + destination_identity: caller_identity.to_string(), + request_id: request_id.clone(), + }) + .await + { + log::error!("Failed to publish RPC ACK: {:?}", e); + } + + let handler = self.local.rpc_state.lock().handlers.get(&method).cloned(); + + let caller_identity_2 = caller_identity.clone(); + let request_id_2 = request_id.clone(); + + let response = match handler { + Some(handler) => { + match tokio::task::spawn(async move { + handler( + request_id.clone(), + caller_identity.clone(), + payload.clone(), + Duration::from_millis(response_timeout_ms as u64), + ) + .await + }) + .await + { + Ok(result) => result, + Err(e) => { + log::error!("RPC method handler returned an error: {:?}", e); + Err(RpcError::built_in(RpcErrorCode::ApplicationError, None)) + } + } + } + None => Err(RpcError::built_in(RpcErrorCode::UnsupportedMethod, None)), + }; + + let (payload, error) = match response { + Ok(response_payload) if response_payload.len() <= MAX_PAYLOAD_BYTES => { + (Some(response_payload), None) + } + Ok(_) => (None, Some(RpcError::built_in(RpcErrorCode::ResponsePayloadTooLarge, None))), + Err(e) => (None, Some(e.into())), + }; + + if let Err(e) = self + .publish_rpc_response(RpcResponse { + destination_identity: caller_identity_2.to_string(), + request_id: request_id_2, + payload, + error: error.map(|e| e.to_proto()), + }) + .await + { + log::error!("Failed to publish RPC response: {:?}", e); + } + } } diff --git a/livekit/src/room/participant/mod.rs b/livekit/src/room/participant/mod.rs index c535111cf..f8f7a7460 100644 --- a/livekit/src/room/participant/mod.rs +++ b/livekit/src/room/participant/mod.rs @@ -22,10 +22,12 @@ use crate::{prelude::*, rtc_engine::RtcEngine}; mod local_participant; mod remote_participant; +mod rpc; use crate::room::utils; pub use local_participant::*; pub use remote_participant::*; +pub use rpc::*; #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum ConnectionQuality { diff --git a/livekit/src/room/participant/rpc.rs b/livekit/src/room/participant/rpc.rs new file mode 100644 index 000000000..3ddb0f545 --- /dev/null +++ b/livekit/src/room/participant/rpc.rs @@ -0,0 +1,112 @@ +// SPDX-FileCopyrightText: 2024 LiveKit, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +use livekit_protocol::RpcError as RpcError_Proto; + +/// Specialized error handling for RPC methods. +/// +/// Instances of this type, when thrown in a method handler, will have their `message` +/// serialized and sent across the wire. The caller will receive an equivalent error on the other side. +/// +/// Build-in types are included but developers may use any string, with a max length of 256 bytes. +#[derive(Debug, Clone)] +pub struct RpcError { + pub code: u32, + pub message: String, + pub data: Option, +} + +impl RpcError { + pub const MAX_MESSAGE_BYTES: usize = 256; + pub const MAX_DATA_BYTES: usize = 15360; // 15 KB + + /// Creates an error object with the given code and message, plus an optional data payload. + /// + /// If thrown in an RPC method handler, the error will be sent back to the caller. + /// + /// Error codes 1001-1999 are reserved for built-in errors (see RpcErrorCode for their meanings). + pub fn new(code: u32, message: String, data: Option) -> Self { + Self { + code, + message: truncate_bytes(&message, Self::MAX_MESSAGE_BYTES), + data: data.map(|d| truncate_bytes(&d, Self::MAX_DATA_BYTES)), + } + } + + pub fn from_proto(proto: RpcError_Proto) -> Self { + Self::new(proto.code, proto.message, Some(proto.data)) + } + + pub fn to_proto(&self) -> RpcError_Proto { + RpcError_Proto { + code: self.code, + message: self.message.clone(), + data: self.data.clone().unwrap_or_default(), + } + } +} + +#[derive(Debug, Clone, Copy)] +pub enum RpcErrorCode { + ApplicationError = 1500, + ConnectionTimeout = 1501, + ResponseTimeout = 1502, + RecipientDisconnected = 1503, + ResponsePayloadTooLarge = 1504, + SendFailed = 1505, + + UnsupportedMethod = 1400, + RecipientNotFound = 1401, + RequestPayloadTooLarge = 1402, + UnsupportedServer = 1403, +} + +impl RpcErrorCode { + pub(crate) fn message(&self) -> &'static str { + match self { + Self::ApplicationError => "Application error in method handler", + Self::ConnectionTimeout => "Connection timeout", + Self::ResponseTimeout => "Response timeout", + Self::RecipientDisconnected => "Recipient disconnected", + Self::ResponsePayloadTooLarge => "Response payload too large", + Self::SendFailed => "Failed to send", + + Self::UnsupportedMethod => "Method not supported at destination", + Self::RecipientNotFound => "Recipient not found", + Self::RequestPayloadTooLarge => "Request payload too large", + Self::UnsupportedServer => "RPC not supported by server", + } + } +} + +impl RpcError { + /// Creates an error object from the code, with an auto-populated message. + pub(crate) fn built_in(code: RpcErrorCode, data: Option) -> Self { + Self::new(code as u32, code.message().to_string(), data) + } +} + +/// Maximum payload size in bytes +pub const MAX_PAYLOAD_BYTES: usize = 15360; // 15 KB + +/// Calculate the byte length of a string +pub(crate) fn byte_length(s: &str) -> usize { + s.as_bytes().len() +} + +/// Truncate a string to a maximum number of bytes +pub(crate) fn truncate_bytes(s: &str, max_bytes: usize) -> String { + if byte_length(s) <= max_bytes { + return s.to_string(); + } + + let mut result = String::new(); + for c in s.chars() { + if byte_length(&(result.clone() + &c.to_string())) > max_bytes { + break; + } + result.push(c); + } + result +} diff --git a/livekit/src/rtc_engine/mod.rs b/livekit/src/rtc_engine/mod.rs index efaf28740..d848571b6 100644 --- a/livekit/src/rtc_engine/mod.rs +++ b/livekit/src/rtc_engine/mod.rs @@ -113,6 +113,22 @@ pub enum EngineEvent { code: u32, digit: Option, }, + RpcRequest { + caller_identity: Option, + request_id: String, + method: String, + payload: String, + response_timeout_ms: u32, + version: u32, + }, + RpcResponse { + request_id: String, + payload: Option, + error: Option, + }, + RpcAck { + request_id: String, + }, SpeakersChanged { speakers: Vec, }, @@ -462,6 +478,34 @@ impl EngineInner { segments, }); } + SessionEvent::SipDTMF { participant_identity, code, digit } => { + let _ = + self.engine_tx.send(EngineEvent::SipDTMF { participant_identity, code, digit }); + } + SessionEvent::RpcRequest { + caller_identity, + request_id, + method, + payload, + response_timeout_ms, + version, + } => { + let _ = self.engine_tx.send(EngineEvent::RpcRequest { + caller_identity, + request_id, + method, + payload, + response_timeout_ms, + version, + }); + } + SessionEvent::RpcResponse { request_id, payload, error } => { + let _ = + self.engine_tx.send(EngineEvent::RpcResponse { request_id, payload, error }); + } + SessionEvent::RpcAck { request_id } => { + let _ = self.engine_tx.send(EngineEvent::RpcAck { request_id }); + } SessionEvent::MediaTrack { track, stream, transceiver } => { let _ = self.engine_tx.send(EngineEvent::MediaTrack { track, stream, transceiver }); } diff --git a/livekit/src/rtc_engine/rtc_session.rs b/livekit/src/rtc_engine/rtc_session.rs index 58168359b..5377be510 100644 --- a/livekit/src/rtc_engine/rtc_session.rs +++ b/livekit/src/rtc_engine/rtc_session.rs @@ -96,6 +96,22 @@ pub enum SessionEvent { code: u32, digit: Option, }, + RpcRequest { + caller_identity: Option, + request_id: String, + method: String, + payload: String, + response_timeout_ms: u32, + version: u32, + }, + RpcResponse { + request_id: String, + payload: Option, + error: Option, + }, + RpcAck { + request_id: String, + }, MediaTrack { track: MediaStreamTrack, stream: MediaStream, @@ -661,6 +677,42 @@ impl SessionInner { segments, }); } + proto::data_packet::Value::RpcRequest(rpc_request) => { + let caller_identity = data + .participant_identity + .is_empty() + .not() + .then_some(data.participant_identity.clone()) + .map(|s| s.try_into().unwrap()); + let _ = self.emitter.send(SessionEvent::RpcRequest { + caller_identity, + request_id: rpc_request.id.clone(), + method: rpc_request.method.clone(), + payload: rpc_request.payload.clone(), + response_timeout_ms: rpc_request.response_timeout_ms, + version: rpc_request.version, + }); + } + proto::data_packet::Value::RpcResponse(rpc_response) => { + let _ = self.emitter.send(SessionEvent::RpcResponse { + request_id: rpc_response.request_id.clone(), + payload: rpc_response.value.as_ref().and_then(|v| match v { + proto::rpc_response::Value::Payload(payload) => { + Some(payload.clone()) + } + _ => None, + }), + error: rpc_response.value.as_ref().and_then(|v| match v { + proto::rpc_response::Value::Error(error) => Some(error.clone()), + _ => None, + }), + }); + } + proto::data_packet::Value::RpcAck(rpc_ack) => { + let _ = self.emitter.send(SessionEvent::RpcAck { + request_id: rpc_ack.request_id.clone(), + }); + } proto::data_packet::Value::ChatMessage(message) => { let _ = self.emitter.send(SessionEvent::ChatMessage { participant_identity: ParticipantIdentity( diff --git a/soxr-sys/src/lib.rs b/soxr-sys/src/lib.rs index 545f21c78..1a59d4db3 100644 --- a/soxr-sys/src/lib.rs +++ b/soxr-sys/src/lib.rs @@ -6,8 +6,6 @@ include!("soxr.rs"); #[cfg(test)] mod tests { use super::*; - use std::fs::File; - use std::io::{Read, Seek, SeekFrom}; #[test] fn it_works() { From a5568630d8d2470663e3f5458a3692aae30cffad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Thu, 24 Oct 2024 17:36:43 -0700 Subject: [PATCH 066/274] ffi: be more flexible with optional options (#474) --- livekit-ffi/protocol/audio_frame.proto | 12 ++-- livekit-ffi/protocol/room.proto | 20 +++--- livekit-ffi/protocol/video_frame.proto | 6 +- livekit-ffi/src/conversion/room.rs | 37 +++++++---- livekit-ffi/src/livekit.proto.rs | 76 +++++++++++----------- livekit-ffi/src/server/audio_source.rs | 2 +- livekit-ffi/src/server/audio_stream.rs | 19 ++---- livekit-ffi/src/server/colorcvt/cvtimpl.rs | 19 ++++++ livekit-ffi/src/server/requests.rs | 11 ++-- livekit-ffi/src/server/video_stream.rs | 6 +- 10 files changed, 115 insertions(+), 93 deletions(-) diff --git a/livekit-ffi/protocol/audio_frame.proto b/livekit-ffi/protocol/audio_frame.proto index d10f3a382..4d70c310f 100644 --- a/livekit-ffi/protocol/audio_frame.proto +++ b/livekit-ffi/protocol/audio_frame.proto @@ -25,8 +25,8 @@ import "track.proto"; message NewAudioStreamRequest { required uint64 track_handle = 1; required AudioStreamType type = 2; - required uint32 sample_rate = 3; - required uint32 num_channels = 4; + optional uint32 sample_rate = 3; + optional uint32 num_channels = 4; } message NewAudioStreamResponse { required OwnedAudioStream stream = 1; } @@ -34,8 +34,8 @@ message AudioStreamFromParticipantRequest { required uint64 participant_handle = 1; required AudioStreamType type = 2; optional TrackSource track_source = 3; - required uint32 sample_rate = 5; - required uint32 num_channels = 6; + optional uint32 sample_rate = 5; + optional uint32 num_channels = 6; } message AudioStreamFromParticipantResponse { required OwnedAudioStream stream = 1; } @@ -46,7 +46,7 @@ message NewAudioSourceRequest { optional AudioSourceOptions options = 2; required uint32 sample_rate = 3; required uint32 num_channels = 4; - required uint32 queue_size_ms = 5; + optional uint32 queue_size_ms = 5; } message NewAudioSourceResponse { required OwnedAudioSource source = 1; } @@ -97,7 +97,7 @@ message NewSoxResamplerRequest { required SoxResamplerDataType input_data_type = 4; required SoxResamplerDataType output_data_type = 5; required SoxQualityRecipe quality_recipe = 6; - required uint32 flags = 7; + optional uint32 flags = 7; } message NewSoxResamplerResponse { oneof message { diff --git a/livekit-ffi/protocol/room.proto b/livekit-ffi/protocol/room.proto index b2b295d08..edae5c007 100644 --- a/livekit-ffi/protocol/room.proto +++ b/livekit-ffi/protocol/room.proto @@ -254,12 +254,12 @@ message TrackPublishOptions { // encodings are optional optional VideoEncoding video_encoding = 1; optional AudioEncoding audio_encoding = 2; - required VideoCodec video_codec = 3; - required bool dtx = 4; - required bool red = 5; - required bool simulcast = 6; - required TrackSource source = 7; - required string stream = 8; + optional VideoCodec video_codec = 3; + optional bool dtx = 4; + optional bool red = 5; + optional bool simulcast = 6; + optional TrackSource source = 7; + optional string stream = 8; } enum IceTransportType { @@ -286,12 +286,12 @@ message RtcConfig { } message RoomOptions { - required bool auto_subscribe = 1; - required bool adaptive_stream = 2; - required bool dynacast = 3; + optional bool auto_subscribe = 1; + optional bool adaptive_stream = 2; + optional bool dynacast = 3; optional E2eeOptions e2ee = 4; optional RtcConfig rtc_config = 5; // allow to setup a custom RtcConfiguration - required uint32 join_retries = 6; + optional uint32 join_retries = 6; } // diff --git a/livekit-ffi/protocol/video_frame.proto b/livekit-ffi/protocol/video_frame.proto index 1291ccc48..6ce0a2bc8 100644 --- a/livekit-ffi/protocol/video_frame.proto +++ b/livekit-ffi/protocol/video_frame.proto @@ -27,7 +27,7 @@ message NewVideoStreamRequest { required VideoStreamType type = 2; // Get the frame on a specific format optional VideoBufferType format = 3; - required bool normalize_stride = 4; // if true, stride will be set to width/chroma_width + optional bool normalize_stride = 4; // if true, stride will be set to width/chroma_width } message NewVideoStreamResponse { required OwnedVideoStream stream = 1; } @@ -37,7 +37,7 @@ message VideoStreamFromParticipantRequest { required VideoStreamType type = 2; required TrackSource track_source = 3; optional VideoBufferType format = 4; - required bool normalize_stride = 5; + optional bool normalize_stride = 5; } message VideoStreamFromParticipantResponse { required OwnedVideoStream stream = 1;} @@ -63,7 +63,7 @@ message CaptureVideoFrameRequest { message CaptureVideoFrameResponse {} message VideoConvertRequest { - required bool flip_y = 1; + optional bool flip_y = 1; required VideoBufferInfo buffer = 2; required VideoBufferType dst_type = 3; } diff --git a/livekit-ffi/src/conversion/room.rs b/livekit-ffi/src/conversion/room.rs index b5295f1b3..2dc32170d 100644 --- a/livekit-ffi/src/conversion/room.rs +++ b/livekit-ffi/src/conversion/room.rs @@ -177,13 +177,12 @@ impl From for RoomOptions { value.rtc_config.map(Into::into).unwrap_or(RoomOptions::default().rtc_config); let mut options = RoomOptions::default(); - options.adaptive_stream = value.adaptive_stream; - options.auto_subscribe = value.auto_subscribe; - options.dynacast = value.dynacast; - options.e2ee = e2ee; + options.adaptive_stream = value.adaptive_stream.unwrap_or(options.adaptive_stream); + options.auto_subscribe = value.auto_subscribe.unwrap_or(options.auto_subscribe); + options.dynacast = value.dynacast.unwrap_or(options.dynacast); options.rtc_config = rtc_config; - options.join_retries = value.join_retries; - options.sdk_options = RoomSdkOptions::default(); + options.join_retries = value.join_retries.unwrap_or(options.join_retries); + options.e2ee = e2ee; options } } @@ -208,15 +207,25 @@ impl From for proto::DataPacketKind { impl From for TrackPublishOptions { fn from(opts: proto::TrackPublishOptions) -> Self { + let default_publish_options = TrackPublishOptions::default(); + let video_codec = opts.video_codec.map(|x| proto::VideoCodec::try_from(x).ok()).flatten(); + let source = opts.source.map(|x| proto::TrackSource::try_from(x).ok()).flatten(); + Self { - video_codec: opts.video_codec().into(), - source: opts.source().into(), - video_encoding: opts.video_encoding.map(Into::into), - audio_encoding: opts.audio_encoding.map(Into::into), - dtx: opts.dtx, - red: opts.red, - simulcast: opts.simulcast, - stream: opts.stream, + video_codec: video_codec.map(Into::into).unwrap_or(default_publish_options.video_codec), + source: source.map(Into::into).unwrap_or(default_publish_options.source), + video_encoding: opts + .video_encoding + .map(Into::into) + .or(default_publish_options.video_encoding), + audio_encoding: opts + .audio_encoding + .map(Into::into) + .or(default_publish_options.audio_encoding), + dtx: opts.dtx.unwrap_or(default_publish_options.dtx), + red: opts.red.unwrap_or(default_publish_options.red), + simulcast: opts.simulcast.unwrap_or(default_publish_options.simulcast), + stream: opts.stream.unwrap_or(default_publish_options.stream), } } } diff --git a/livekit-ffi/src/livekit.proto.rs b/livekit-ffi/src/livekit.proto.rs index a068e502d..620588fcd 100644 --- a/livekit-ffi/src/livekit.proto.rs +++ b/livekit-ffi/src/livekit.proto.rs @@ -1601,8 +1601,8 @@ pub struct NewVideoStreamRequest { #[prost(enumeration="VideoBufferType", optional, tag="3")] pub format: ::core::option::Option, /// if true, stride will be set to width/chroma_width - #[prost(bool, required, tag="4")] - pub normalize_stride: bool, + #[prost(bool, optional, tag="4")] + pub normalize_stride: ::core::option::Option, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -1622,8 +1622,8 @@ pub struct VideoStreamFromParticipantRequest { pub track_source: i32, #[prost(enumeration="VideoBufferType", optional, tag="4")] pub format: ::core::option::Option, - #[prost(bool, required, tag="5")] - pub normalize_stride: bool, + #[prost(bool, optional, tag="5")] + pub normalize_stride: ::core::option::Option, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -1670,8 +1670,8 @@ pub struct CaptureVideoFrameResponse { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct VideoConvertRequest { - #[prost(bool, required, tag="1")] - pub flip_y: bool, + #[prost(bool, optional, tag="1")] + pub flip_y: ::core::option::Option, #[prost(message, required, tag="2")] pub buffer: VideoBufferInfo, #[prost(enumeration="VideoBufferType", required, tag="3")] @@ -2423,18 +2423,18 @@ pub struct TrackPublishOptions { pub video_encoding: ::core::option::Option, #[prost(message, optional, tag="2")] pub audio_encoding: ::core::option::Option, - #[prost(enumeration="VideoCodec", required, tag="3")] - pub video_codec: i32, - #[prost(bool, required, tag="4")] - pub dtx: bool, - #[prost(bool, required, tag="5")] - pub red: bool, - #[prost(bool, required, tag="6")] - pub simulcast: bool, - #[prost(enumeration="TrackSource", required, tag="7")] - pub source: i32, - #[prost(string, required, tag="8")] - pub stream: ::prost::alloc::string::String, + #[prost(enumeration="VideoCodec", optional, tag="3")] + pub video_codec: ::core::option::Option, + #[prost(bool, optional, tag="4")] + pub dtx: ::core::option::Option, + #[prost(bool, optional, tag="5")] + pub red: ::core::option::Option, + #[prost(bool, optional, tag="6")] + pub simulcast: ::core::option::Option, + #[prost(enumeration="TrackSource", optional, tag="7")] + pub source: ::core::option::Option, + #[prost(string, optional, tag="8")] + pub stream: ::core::option::Option<::prost::alloc::string::String>, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -2460,19 +2460,19 @@ pub struct RtcConfig { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RoomOptions { - #[prost(bool, required, tag="1")] - pub auto_subscribe: bool, - #[prost(bool, required, tag="2")] - pub adaptive_stream: bool, - #[prost(bool, required, tag="3")] - pub dynacast: bool, + #[prost(bool, optional, tag="1")] + pub auto_subscribe: ::core::option::Option, + #[prost(bool, optional, tag="2")] + pub adaptive_stream: ::core::option::Option, + #[prost(bool, optional, tag="3")] + pub dynacast: ::core::option::Option, #[prost(message, optional, tag="4")] pub e2ee: ::core::option::Option, /// allow to setup a custom RtcConfiguration #[prost(message, optional, tag="5")] pub rtc_config: ::core::option::Option, - #[prost(uint32, required, tag="6")] - pub join_retries: u32, + #[prost(uint32, optional, tag="6")] + pub join_retries: ::core::option::Option, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -3069,10 +3069,10 @@ pub struct NewAudioStreamRequest { pub track_handle: u64, #[prost(enumeration="AudioStreamType", required, tag="2")] pub r#type: i32, - #[prost(uint32, required, tag="3")] - pub sample_rate: u32, - #[prost(uint32, required, tag="4")] - pub num_channels: u32, + #[prost(uint32, optional, tag="3")] + pub sample_rate: ::core::option::Option, + #[prost(uint32, optional, tag="4")] + pub num_channels: ::core::option::Option, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -3089,10 +3089,10 @@ pub struct AudioStreamFromParticipantRequest { pub r#type: i32, #[prost(enumeration="TrackSource", optional, tag="3")] pub track_source: ::core::option::Option, - #[prost(uint32, required, tag="5")] - pub sample_rate: u32, - #[prost(uint32, required, tag="6")] - pub num_channels: u32, + #[prost(uint32, optional, tag="5")] + pub sample_rate: ::core::option::Option, + #[prost(uint32, optional, tag="6")] + pub num_channels: ::core::option::Option, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -3112,8 +3112,8 @@ pub struct NewAudioSourceRequest { pub sample_rate: u32, #[prost(uint32, required, tag="4")] pub num_channels: u32, - #[prost(uint32, required, tag="5")] - pub queue_size_ms: u32, + #[prost(uint32, optional, tag="5")] + pub queue_size_ms: ::core::option::Option, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -3202,8 +3202,8 @@ pub struct NewSoxResamplerRequest { pub output_data_type: i32, #[prost(enumeration="SoxQualityRecipe", required, tag="6")] pub quality_recipe: i32, - #[prost(uint32, required, tag="7")] - pub flags: u32, + #[prost(uint32, optional, tag="7")] + pub flags: ::core::option::Option, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] diff --git a/livekit-ffi/src/server/audio_source.rs b/livekit-ffi/src/server/audio_source.rs index 703f9e668..115792710 100644 --- a/livekit-ffi/src/server/audio_source.rs +++ b/livekit-ffi/src/server/audio_source.rs @@ -43,7 +43,7 @@ impl FfiAudioSource { new_source.options.map(Into::into).unwrap_or_default(), new_source.sample_rate, new_source.num_channels, - new_source.queue_size_ms, + new_source.queue_size_ms.unwrap_or(1000), ); RtcAudioSource::Native(audio_source) } diff --git a/livekit-ffi/src/server/audio_stream.rs b/livekit-ffi/src/server/audio_stream.rs index bde45053d..782969613 100644 --- a/livekit-ffi/src/server/audio_stream.rs +++ b/livekit-ffi/src/server/audio_stream.rs @@ -58,12 +58,8 @@ impl FfiAudioStream { #[cfg(not(target_arch = "wasm32"))] proto::AudioStreamType::AudioStreamNative => { let audio_stream = Self { handle_id, stream_type, self_dropped_tx }; - - let sample_rate = - if new_stream.sample_rate == 0 { 48000 } else { new_stream.sample_rate as i32 }; - - let num_channels = - if new_stream.num_channels == 0 { 1 } else { new_stream.num_channels as i32 }; + let sample_rate = new_stream.sample_rate.unwrap_or(48000); + let num_channels = new_stream.num_channels.unwrap_or(1); let native_stream = NativeAudioStream::new(rtc_track, sample_rate as i32, num_channels as i32); @@ -85,7 +81,7 @@ impl FfiAudioStream { let info = proto::AudioStreamInfo::from(&audio_stream); server.store_handle(handle_id, audio_stream); - Ok(proto::OwnedAudioStream { handle: proto::FfiOwnedHandle { id: handle_id }, info: info }) + Ok(proto::OwnedAudioStream { handle: proto::FfiOwnedHandle { id: handle_id }, info }) } pub fn from_participant( @@ -116,7 +112,7 @@ impl FfiAudioStream { let info = proto::AudioStreamInfo::from(&audio_stream); server.store_handle(handle_id, audio_stream); - Ok(proto::OwnedAudioStream { handle: proto::FfiOwnedHandle { id: handle_id }, info: info }) + Ok(proto::OwnedAudioStream { handle: proto::FfiOwnedHandle { id: handle_id }, info }) } async fn participant_audio_stream_task( @@ -156,11 +152,8 @@ impl FfiAudioStream { let (c_tx, c_rx) = oneshot::channel::<()>(); let (handle_dropped_tx, handle_dropped_rx) = oneshot::channel::<()>(); let (done_tx, mut done_rx) = oneshot::channel::<()>(); - let sample_rate = - if request.sample_rate == 0 { 48000 } else { request.sample_rate as i32 }; - - let num_channels = - if request.num_channels == 0 { 1 } else { request.num_channels as i32 }; + let sample_rate = request.sample_rate.unwrap_or(48000) as i32; + let num_channels = request.num_channels.unwrap_or(1) as i32; let mut track_finished_rx = track_finished_tx.subscribe(); server.async_runtime.spawn(async move { diff --git a/livekit-ffi/src/server/colorcvt/cvtimpl.rs b/livekit-ffi/src/server/colorcvt/cvtimpl.rs index 0ddc6f211..b6ebcb631 100644 --- a/livekit-ffi/src/server/colorcvt/cvtimpl.rs +++ b/livekit-ffi/src/server/colorcvt/cvtimpl.rs @@ -63,6 +63,15 @@ pub unsafe fn cvt_rgba( ); Ok((dst, info)) } + proto::VideoBufferType::Bgra => { + let mut dst = vec![0u8; (width * height * 4) as usize].into_boxed_slice(); + let stride = width * 4; + + colorcvt::abgr_to_argb(data, stride, &mut dst, stride, width, height, flip_y); + + let info = rgba_info(dst.as_ptr(), dst_type, width, height); + Ok((dst, info)) + } _ => { Err(FfiError::InvalidRequest(format!("rgba to {:?} is not supported", dst_type).into())) } @@ -197,6 +206,16 @@ pub unsafe fn cvt_bgra( chroma_w, chroma_w, ); + + Ok((dst, info)) + } + proto::VideoBufferType::Rgba => { + let mut dst = vec![0u8; (width * height * 4) as usize].into_boxed_slice(); + let stride = width * 4; + + colorcvt::argb_to_abgr(data, stride, &mut dst, stride, width, height, flip_y); + + let info = rgba_info(dst.as_ptr(), dst_type, width, height); Ok((dst, info)) } _ => { diff --git a/livekit-ffi/src/server/requests.rs b/livekit-ffi/src/server/requests.rs index cb3b6a715..6af1fd665 100644 --- a/livekit-ffi/src/server/requests.rs +++ b/livekit-ffi/src/server/requests.rs @@ -396,12 +396,11 @@ unsafe fn on_video_convert( let ref buffer = video_convert.buffer; let flip_y = video_convert.flip_y; let dst_type = video_convert.dst_type(); - match cvtimpl::cvt(buffer.clone(), dst_type, flip_y) { + match cvtimpl::cvt(buffer.clone(), dst_type, flip_y.unwrap_or(false)) { Ok((buffer, info)) => { let id = server.next_id(); server.store_handle(id, buffer); - let owned_info = - proto::OwnedVideoBuffer { handle: proto::FfiOwnedHandle { id }, info: info }; + let owned_info = proto::OwnedVideoBuffer { handle: proto::FfiOwnedHandle { id }, info }; Ok(proto::VideoConvertResponse { message: Some(proto::video_convert_response::Message::Buffer(owned_info)), }) @@ -693,8 +692,10 @@ fn on_new_sox_resampler( output_type: new_soxr.output_data_type(), }; - let quality_spec = - resampler::QualitySpec { quality: new_soxr.quality_recipe(), flags: new_soxr.flags }; + let quality_spec = resampler::QualitySpec { + quality: new_soxr.quality_recipe(), + flags: new_soxr.flags.unwrap_or(0), + }; let runtime_spec = resampler::RuntimeSpec { num_threads: 1 }; diff --git a/livekit-ffi/src/server/video_stream.rs b/livekit-ffi/src/server/video_stream.rs index 94d32688d..4e9f2ceb4 100644 --- a/livekit-ffi/src/server/video_stream.rs +++ b/livekit-ffi/src/server/video_stream.rs @@ -64,7 +64,7 @@ impl FfiVideoStream { server, handle_id, new_stream.format.and_then(|_| Some(new_stream.format())), - new_stream.normalize_stride, + new_stream.normalize_stride.unwrap_or(true), NativeVideoStream::new(rtc_track), self_dropped_rx, server.watch_handle_dropped(new_stream.track_handle), @@ -80,7 +80,7 @@ impl FfiVideoStream { let info = proto::VideoStreamInfo::from(&stream); server.store_handle(stream.handle_id, stream); - Ok(proto::OwnedVideoStream { handle: proto::FfiOwnedHandle { id: handle_id }, info: info }) + Ok(proto::OwnedVideoStream { handle: proto::FfiOwnedHandle { id: handle_id }, info }) } pub fn from_participant( @@ -242,7 +242,7 @@ impl FfiVideoStream { server, stream_handle, dst_type, - request.normalize_stride, + request.normalize_stride.unwrap_or(true), NativeVideoStream::new(rtc_track), c_rx, handle_dropped_rx, From 1fbcb2ca3e34e98f8f247f5c5da2110d2f7b5905 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?The=CC=81o=20Monnom?= Date: Thu, 24 Oct 2024 17:40:43 -0700 Subject: [PATCH 067/274] ffi-v0.12.1 --- livekit-ffi/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index b07a6836d..bbe8ed4fa 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.12.0" +version = "0.12.1" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" From bc7368477abd3df67e19166a585f8e3ad822c35f Mon Sep 17 00:00:00 2001 From: kbalt Date: Thu, 31 Oct 2024 19:09:58 +0100 Subject: [PATCH 068/274] expose libwebrtc's ProhibitLibsrtpInitialization (#477) --- webrtc-sys/build.rs | 2 ++ .../livekit/prohibit_libsrtp_initialization.h | 21 +++++++++++++++++ webrtc-sys/src/lib.rs | 1 + .../src/prohibit_libsrtp_initialization.cpp | 23 +++++++++++++++++++ .../src/prohibit_libsrtp_initialization.rs | 22 ++++++++++++++++++ 5 files changed, 69 insertions(+) create mode 100644 webrtc-sys/include/livekit/prohibit_libsrtp_initialization.h create mode 100644 webrtc-sys/src/prohibit_libsrtp_initialization.cpp create mode 100644 webrtc-sys/src/prohibit_libsrtp_initialization.rs diff --git a/webrtc-sys/build.rs b/webrtc-sys/build.rs index 66380274b..69695330c 100644 --- a/webrtc-sys/build.rs +++ b/webrtc-sys/build.rs @@ -47,6 +47,7 @@ fn main() { "src/yuv_helper.rs", "src/audio_resampler.rs", "src/android.rs", + "src/prohibit_libsrtp_initialization.rs", ]); builder.files(&[ @@ -73,6 +74,7 @@ fn main() { "src/audio_resampler.cpp", "src/frame_cryptor.cpp", "src/global_task_queue.cpp", + "src/prohibit_libsrtp_initialization.cpp", ]); let webrtc_dir = webrtc_sys_build::webrtc_dir(); diff --git a/webrtc-sys/include/livekit/prohibit_libsrtp_initialization.h b/webrtc-sys/include/livekit/prohibit_libsrtp_initialization.h new file mode 100644 index 000000000..9670b161a --- /dev/null +++ b/webrtc-sys/include/livekit/prohibit_libsrtp_initialization.h @@ -0,0 +1,21 @@ +/* + * Copyright 2023 LiveKit + * + * Licensed under the Apache License, Version 2.0 (the “License”); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an “AS IS” BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +namespace livekit { +void ProhibitLibsrtpInitialization(); +} \ No newline at end of file diff --git a/webrtc-sys/src/lib.rs b/webrtc-sys/src/lib.rs index 7a3e19e14..657c2fb55 100644 --- a/webrtc-sys/src/lib.rs +++ b/webrtc-sys/src/lib.rs @@ -25,6 +25,7 @@ pub mod media_stream; pub mod media_stream_track; pub mod peer_connection; pub mod peer_connection_factory; +pub mod prohibit_libsrtp_initialization; pub mod rtc_error; pub mod rtp_parameters; pub mod rtp_receiver; diff --git a/webrtc-sys/src/prohibit_libsrtp_initialization.cpp b/webrtc-sys/src/prohibit_libsrtp_initialization.cpp new file mode 100644 index 000000000..9e26bd998 --- /dev/null +++ b/webrtc-sys/src/prohibit_libsrtp_initialization.cpp @@ -0,0 +1,23 @@ +/* + * Copyright 2023 LiveKit + * + * Licensed under the Apache License, Version 2.0 (the “License”); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an “AS IS” BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "pc/srtp_session.h" + +namespace livekit { +void ProhibitLibsrtpInitialization() { + cricket::ProhibitLibsrtpInitialization(); +} +} \ No newline at end of file diff --git a/webrtc-sys/src/prohibit_libsrtp_initialization.rs b/webrtc-sys/src/prohibit_libsrtp_initialization.rs new file mode 100644 index 000000000..8fa9fcca4 --- /dev/null +++ b/webrtc-sys/src/prohibit_libsrtp_initialization.rs @@ -0,0 +1,22 @@ +// Copyright 2023 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[cxx::bridge(namespace = "livekit")] +pub mod ffi { + unsafe extern "C++" { + include!("livekit/prohibit_libsrtp_initialization.h"); + + fn ProhibitLibsrtpInitialization(); + } +} From e9cebf68ec7eba9c7473f08d3d6463d35bffaf73 Mon Sep 17 00:00:00 2001 From: Albrecht Date: Thu, 31 Oct 2024 19:10:47 +0100 Subject: [PATCH 069/274] fix: don't overwrite the url path in twirp-client (#478) --- Cargo.lock | 2 +- livekit-api/src/services/twirp_client.rs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4c109ac0f..31372d3c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1642,7 +1642,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.12.0" +version = "0.12.1" dependencies = [ "console-subscriber", "dashmap", diff --git a/livekit-api/src/services/twirp_client.rs b/livekit-api/src/services/twirp_client.rs index 2939cc266..9dd6c5b51 100644 --- a/livekit-api/src/services/twirp_client.rs +++ b/livekit-api/src/services/twirp_client.rs @@ -102,7 +102,10 @@ impl TwirpClient { mut headers: HeaderMap, ) -> TwirpResult { let mut url = url::Url::parse(&self.host)?; - url.set_path(&format!("{}/{}.{}/{}", self.prefix, self.pkg, service, method)); + + if let Ok(mut segs) = url.path_segments_mut() { + segs.push(&format!("{}/{}.{}/{}", self.prefix, self.pkg, service, method)); + } headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/protobuf")); From 91fcf57de20cc63d13e2d11b360fab56b3265e68 Mon Sep 17 00:00:00 2001 From: Ben Cherry Date: Thu, 31 Oct 2024 14:10:26 -0700 Subject: [PATCH 070/274] RPC updates (#476) * Check RPC version * invocation data, duration * Duration * fmt * params * data --- examples/rpc/src/main.rs | 120 +++++++++--------- livekit-ffi/src/server/participant.rs | 67 +++++----- livekit-ffi/src/server/requests.rs | 2 +- livekit/src/prelude.rs | 3 +- livekit/src/room/mod.rs | 7 +- .../src/room/participant/local_participant.rs | 85 ++++++------- livekit/src/room/participant/rpc.rs | 39 ++++++ livekit/src/rtc_engine/mod.rs | 6 +- livekit/src/rtc_engine/rtc_session.rs | 6 +- 9 files changed, 180 insertions(+), 155 deletions(-) diff --git a/examples/rpc/src/main.rs b/examples/rpc/src/main.rs index 152f380e1..11e0694b7 100644 --- a/examples/rpc/src/main.rs +++ b/examples/rpc/src/main.rs @@ -78,71 +78,73 @@ async fn main() -> Result<(), Box> { } async fn register_receiver_methods(greeters_room: &Arc, math_genius_room: &Arc) { - greeters_room.local_participant().register_rpc_method( - "arrival".to_string(), - |_, caller_identity, payload, _| { - Box::pin(async move { - println!( - "[{}] [Greeter] Oh {} arrived and said \"{}\"", - elapsed_time(), - caller_identity, - payload - ); - sleep(Duration::from_secs(2)).await; - Ok("Welcome and have a wonderful day!".to_string()) - }) - }, - ); - - math_genius_room.local_participant().register_rpc_method("square-root".to_string(), |_, caller_identity, payload, response_timeout_ms| { + greeters_room.local_participant().register_rpc_method("arrival".to_string(), |data| { Box::pin(async move { - let json_data: Value = serde_json::from_str(&payload).unwrap(); - let number = json_data["number"].as_f64().unwrap(); println!( - "[{}] [Math Genius] I guess {} wants the square root of {}. I've only got {} seconds to respond but I think I can pull it off.", + "[{}] [Greeter] Oh {} arrived and said \"{}\"", elapsed_time(), - caller_identity, - number, - response_timeout_ms.as_secs() + data.caller_identity, + data.payload ); - - println!("[{}] [Math Genius] *doing math*…", elapsed_time()); sleep(Duration::from_secs(2)).await; - - let result = number.sqrt(); - println!("[{}] [Math Genius] Aha! It's {}", elapsed_time(), result); - Ok(json!({"result": result}).to_string()) + Ok("Welcome and have a wonderful day!".to_string()) }) }); math_genius_room.local_participant().register_rpc_method( - "divide".to_string(), - |_, caller_identity, payload, _| { + "square-root".to_string(), + |data| { Box::pin(async move { - let json_data: Value = serde_json::from_str(&payload).unwrap(); - let dividend = json_data["dividend"].as_i64().unwrap(); - let divisor = json_data["divisor"].as_i64().unwrap(); + let json_data: Value = serde_json::from_str(&data.payload).unwrap(); + let number = json_data["number"].as_f64().unwrap(); println!( - "[{}] [Math Genius] {} wants me to divide {} by {}.", + "[{}] [Math Genius] I guess {} wants the square root of {}. I've only got {} seconds to respond but I think I can pull it off.", elapsed_time(), - caller_identity, - dividend, - divisor + data.caller_identity, + number, + data.response_timeout.as_secs() ); - let result = dividend / divisor; - println!("[{}] [Math Genius] The result is {}", elapsed_time(), result); + println!("[{}] [Math Genius] *doing math*…", elapsed_time()); + sleep(Duration::from_secs(2)).await; + + let result = number.sqrt(); + println!("[{}] [Math Genius] Aha! It's {}", elapsed_time(), result); Ok(json!({"result": result}).to_string()) }) }, ); + + math_genius_room.local_participant().register_rpc_method("divide".to_string(), |data| { + Box::pin(async move { + let json_data: Value = serde_json::from_str(&data.payload).unwrap(); + let dividend = json_data["dividend"].as_i64().unwrap(); + let divisor = json_data["divisor"].as_i64().unwrap(); + println!( + "[{}] [Math Genius] {} wants me to divide {} by {}.", + elapsed_time(), + data.caller_identity, + dividend, + divisor + ); + + let result = dividend / divisor; + println!("[{}] [Math Genius] The result is {}", elapsed_time(), result); + Ok(json!({"result": result}).to_string()) + }) + }); } async fn perform_greeting(room: &Arc) -> Result<(), Box> { println!("[{}] Letting the greeter know that I've arrived", elapsed_time()); match room .local_participant() - .perform_rpc("greeter".to_string(), "arrival".to_string(), "Hello".to_string(), None) + .perform_rpc(PerformRpcData { + destination_identity: "greeter".to_string(), + method: "arrival".to_string(), + payload: "Hello".to_string(), + ..Default::default() + }) .await { Ok(response) => { @@ -157,12 +159,12 @@ async fn perform_square_root(room: &Arc) -> Result<(), Box { @@ -180,12 +182,12 @@ async fn perform_quantum_hypergeometric_series( println!("[{}] What's the quantum hypergeometric series of 42?", elapsed_time()); match room .local_participant() - .perform_rpc( - "math-genius".to_string(), - "quantum-hypergeometric-series".to_string(), - json!({"number": 42}).to_string(), - None, - ) + .perform_rpc(PerformRpcData { + destination_identity: "math-genius".to_string(), + method: "quantum-hypergeometric-series".to_string(), + payload: json!({"number": 42}).to_string(), + ..Default::default() + }) .await { Ok(response) => { @@ -207,12 +209,12 @@ async fn perform_division(room: &Arc) -> Result<(), Box { diff --git a/livekit-ffi/src/server/participant.rs b/livekit-ffi/src/server/participant.rs index fdf54bd60..afa4bade2 100644 --- a/livekit-ffi/src/server/participant.rs +++ b/livekit-ffi/src/server/participant.rs @@ -51,12 +51,15 @@ impl FfiParticipant { let handle = server.async_runtime.spawn(async move { let result = local - .perform_rpc( - request.destination_identity.to_string(), - request.method, - request.payload, - request.response_timeout_ms, - ) + .perform_rpc(PerformRpcData { + destination_identity: request.destination_identity.to_string(), + method: request.method, + payload: request.payload, + response_timeout: request + .response_timeout_ms + .map(|ms| Duration::from_millis(ms as u64)) + .unwrap_or(PerformRpcData::default().response_timeout), + }) .await; let callback = proto::PerformRpcCallback { @@ -91,34 +94,27 @@ impl FfiParticipant { let local_participant_handle = self.handle.clone(); let room: Arc = self.room.clone(); - local.register_rpc_method( - method.clone(), - move |request_id, caller_identity, payload, response_timeout| { - Box::pin({ - let room = room.clone(); - let method = method.clone(); - async move { - forward_rpc_method_invocation( - server, - room, - local_participant_handle, - method, - request_id, - caller_identity, - payload, - response_timeout, - ) - .await - } - }) - }, - ); + local.register_rpc_method(method.clone(), move |data| { + Box::pin({ + let room = room.clone(); + let method = method.clone(); + async move { + forward_rpc_method_invocation( + server, + room, + local_participant_handle, + method, + data, + ) + .await + } + }) + }); Ok(proto::RegisterRpcMethodResponse {}) } pub fn unregister_rpc_method( &self, - server: &'static FfiServer, request: proto::UnregisterRpcMethodRequest, ) -> FfiResult { let local = match &self.participant { @@ -139,10 +135,7 @@ async fn forward_rpc_method_invocation( room: Arc, local_participant_handle: FfiHandleId, method: String, - request_id: String, - caller_identity: ParticipantIdentity, - payload: String, - response_timeout: Duration, + data: RpcInvocationData, ) -> Result { let (tx, rx) = oneshot::channel(); let invocation_id = server.next_id(); @@ -152,10 +145,10 @@ async fn forward_rpc_method_invocation( local_participant_handle: local_participant_handle as u64, invocation_id, method, - request_id, - caller_identity: caller_identity.into(), - payload, - response_timeout_ms: response_timeout.as_millis() as u32, + request_id: data.request_id, + caller_identity: data.caller_identity.into(), + payload: data.payload, + response_timeout_ms: data.response_timeout.as_millis() as u32, }, )); diff --git a/livekit-ffi/src/server/requests.rs b/livekit-ffi/src/server/requests.rs index 6af1fd665..5c4aa32fc 100644 --- a/livekit-ffi/src/server/requests.rs +++ b/livekit-ffi/src/server/requests.rs @@ -815,7 +815,7 @@ fn on_unregister_rpc_method( ) -> FfiResult { let ffi_participant = server.retrieve_handle::(request.local_participant_handle)?.clone(); - return ffi_participant.unregister_rpc_method(server, request); + return ffi_participant.unregister_rpc_method(request); } fn on_rpc_method_invocation_response( diff --git a/livekit/src/prelude.rs b/livekit/src/prelude.rs index a7b86d62a..d86e328c3 100644 --- a/livekit/src/prelude.rs +++ b/livekit/src/prelude.rs @@ -15,7 +15,8 @@ pub use crate::{ id::*, participant::{ - ConnectionQuality, LocalParticipant, Participant, RemoteParticipant, RpcError, RpcErrorCode, + ConnectionQuality, LocalParticipant, Participant, PerformRpcData, RemoteParticipant, + RpcError, RpcErrorCode, RpcInvocationData, }, publication::{LocalTrackPublication, RemoteTrackPublication, TrackPublication}, track::{ diff --git a/livekit/src/room/mod.rs b/livekit/src/room/mod.rs index 28d51902f..7e38cbfff 100644 --- a/livekit/src/room/mod.rs +++ b/livekit/src/room/mod.rs @@ -261,7 +261,7 @@ pub struct RpcRequest { pub id: String, pub method: String, pub payload: String, - pub response_timeout_ms: u32, + pub response_timeout: Duration, pub version: u32, } @@ -689,7 +689,7 @@ impl RoomSession { request_id, method, payload, - response_timeout_ms, + response_timeout, version, } => { if caller_identity.is_none() { @@ -702,7 +702,8 @@ impl RoomSession { request_id, method, payload, - response_timeout_ms, + response_timeout, + version, ) .await; } diff --git a/livekit/src/room/participant/local_participant.rs b/livekit/src/room/participant/local_participant.rs index 8c4596763..037c31709 100644 --- a/livekit/src/room/participant/local_participant.rs +++ b/livekit/src/room/participant/local_participant.rs @@ -19,7 +19,7 @@ use crate::{ e2ee::EncryptionType, options::{self, compute_video_encodings, video_layers_from_encodings, TrackPublishOptions}, prelude::*, - room::participant::rpc::{RpcError, RpcErrorCode, MAX_PAYLOAD_BYTES}, + room::participant::rpc::{RpcError, RpcErrorCode, RpcInvocationData, MAX_PAYLOAD_BYTES}, rtc_engine::{EngineError, RtcEngine}, ChatMessage, DataPacket, RpcAck, RpcRequest, RpcResponse, SipDTMF, Transcription, }; @@ -36,12 +36,7 @@ use semver::Version; use tokio::sync::oneshot; type RpcHandler = Arc< - dyn Fn( - String, // request_id - ParticipantIdentity, // caller_identity - String, // payload - Duration, // response_timeout_ms - ) -> Pin> + Send>> + dyn Fn(RpcInvocationData) -> Pin> + Send>> + Send + Sync, >; @@ -72,7 +67,6 @@ impl RpcState { } } } - struct LocalInfo { events: LocalEvents, encryption_type: EncryptionType, @@ -517,7 +511,7 @@ impl LocalParticipant { id: rpc_request.id, method: rpc_request.method, payload: rpc_request.payload, - response_timeout_ms: rpc_request.response_timeout_ms, + response_timeout_ms: rpc_request.response_timeout.as_millis() as u32, version: rpc_request.version, ..Default::default() }; @@ -643,17 +637,10 @@ impl LocalParticipant { self.inner.info.read().kind } - pub async fn perform_rpc( - &self, - destination_identity: String, - method: String, - payload: String, - response_timeout_ms: Option, - ) -> Result { - let response_timeout = Duration::from_millis(response_timeout_ms.unwrap_or(10000) as u64); + pub async fn perform_rpc(&self, data: PerformRpcData) -> Result { let max_round_trip_latency = Duration::from_millis(2000); - if payload.len() > MAX_PAYLOAD_BYTES { + if data.payload.len() > MAX_PAYLOAD_BYTES { return Err(RpcError::built_in(RpcErrorCode::RequestPayloadTooLarge, None)); } @@ -675,11 +662,11 @@ impl LocalParticipant { match self .publish_rpc_request(RpcRequest { - destination_identity: destination_identity.clone(), + destination_identity: data.destination_identity.clone(), id: id.clone(), - method: method.clone(), - payload: payload.clone(), - response_timeout_ms: (response_timeout - max_round_trip_latency).as_millis() as u32, + method: data.method.clone(), + payload: data.payload.clone(), + response_timeout: data.response_timeout - max_round_trip_latency, version: 1, }) .await @@ -709,7 +696,7 @@ impl LocalParticipant { } // Wait for response timout - let response = match tokio::time::timeout(response_timeout, response_rx).await { + let response = match tokio::time::timeout(data.response_timeout, response_rx).await { Err(_) => { self.local.rpc_state.lock().pending_responses.remove(&id); return Err(RpcError::built_in(RpcErrorCode::ResponseTimeout, None)); @@ -736,12 +723,7 @@ impl LocalParticipant { pub fn register_rpc_method( &self, method: String, - handler: impl Fn( - String, - ParticipantIdentity, - String, - Duration, - ) -> Pin> + Send>> + handler: impl Fn(RpcInvocationData) -> Pin> + Send>> + Send + Sync + 'static, @@ -785,7 +767,8 @@ impl LocalParticipant { request_id: String, method: String, payload: String, - response_timeout_ms: u32, + response_timeout: Duration, + version: u32, ) { if let Err(e) = self .publish_rpc_ack(RpcAck { @@ -797,32 +780,36 @@ impl LocalParticipant { log::error!("Failed to publish RPC ACK: {:?}", e); } - let handler = self.local.rpc_state.lock().handlers.get(&method).cloned(); - let caller_identity_2 = caller_identity.clone(); let request_id_2 = request_id.clone(); - let response = match handler { - Some(handler) => { - match tokio::task::spawn(async move { - handler( - request_id.clone(), - caller_identity.clone(), - payload.clone(), - Duration::from_millis(response_timeout_ms as u64), - ) + let response = if version != 1 { + Err(RpcError::built_in(RpcErrorCode::UnsupportedVersion, None)) + } else { + let handler = self.local.rpc_state.lock().handlers.get(&method).cloned(); + + match handler { + Some(handler) => { + match tokio::task::spawn(async move { + handler(RpcInvocationData { + request_id: request_id.clone(), + caller_identity: caller_identity.clone(), + payload: payload.clone(), + response_timeout, + }) + .await + }) .await - }) - .await - { - Ok(result) => result, - Err(e) => { - log::error!("RPC method handler returned an error: {:?}", e); - Err(RpcError::built_in(RpcErrorCode::ApplicationError, None)) + { + Ok(result) => result, + Err(e) => { + log::error!("RPC method handler returned an error: {:?}", e); + Err(RpcError::built_in(RpcErrorCode::ApplicationError, None)) + } } } + None => Err(RpcError::built_in(RpcErrorCode::UnsupportedMethod, None)), } - None => Err(RpcError::built_in(RpcErrorCode::UnsupportedMethod, None)), }; let (payload, error) = match response { diff --git a/livekit/src/room/participant/rpc.rs b/livekit/src/room/participant/rpc.rs index 3ddb0f545..34e043efb 100644 --- a/livekit/src/room/participant/rpc.rs +++ b/livekit/src/room/participant/rpc.rs @@ -2,7 +2,44 @@ // // SPDX-License-Identifier: Apache-2.0 +use crate::room::participant::ParticipantIdentity; use livekit_protocol::RpcError as RpcError_Proto; +use std::time::Duration; + +/// Parameters for performing an RPC call +#[derive(Debug, Clone)] +pub struct PerformRpcData { + pub destination_identity: String, + pub method: String, + pub payload: String, + pub response_timeout: Duration, +} + +impl Default for PerformRpcData { + fn default() -> Self { + Self { + destination_identity: Default::default(), + method: Default::default(), + payload: Default::default(), + response_timeout: Duration::from_secs(10), + } + } +} + +/// Data passed to method handler for incoming RPC invocations +/// +/// Attributes: +/// request_id (String): The unique request ID. Will match at both sides of the call, useful for debugging or logging. +/// caller_identity (ParticipantIdentity): The unique participant identity of the caller. +/// payload (String): The payload of the request. User-definable format, typically JSON. +/// response_timeout (Duration): The maximum time the caller will wait for a response. +#[derive(Debug, Clone)] +pub struct RpcInvocationData { + pub request_id: String, + pub caller_identity: ParticipantIdentity, + pub payload: String, + pub response_timeout: Duration, +} /// Specialized error handling for RPC methods. /// @@ -60,6 +97,7 @@ pub enum RpcErrorCode { RecipientNotFound = 1401, RequestPayloadTooLarge = 1402, UnsupportedServer = 1403, + UnsupportedVersion = 1404, } impl RpcErrorCode { @@ -76,6 +114,7 @@ impl RpcErrorCode { Self::RecipientNotFound => "Recipient not found", Self::RequestPayloadTooLarge => "Request payload too large", Self::UnsupportedServer => "RPC not supported by server", + Self::UnsupportedVersion => "Unsupported RPC version", } } } diff --git a/livekit/src/rtc_engine/mod.rs b/livekit/src/rtc_engine/mod.rs index d848571b6..4b3c44933 100644 --- a/livekit/src/rtc_engine/mod.rs +++ b/livekit/src/rtc_engine/mod.rs @@ -118,7 +118,7 @@ pub enum EngineEvent { request_id: String, method: String, payload: String, - response_timeout_ms: u32, + response_timeout: Duration, version: u32, }, RpcResponse { @@ -487,7 +487,7 @@ impl EngineInner { request_id, method, payload, - response_timeout_ms, + response_timeout, version, } => { let _ = self.engine_tx.send(EngineEvent::RpcRequest { @@ -495,7 +495,7 @@ impl EngineInner { request_id, method, payload, - response_timeout_ms, + response_timeout, version, }); } diff --git a/livekit/src/rtc_engine/rtc_session.rs b/livekit/src/rtc_engine/rtc_session.rs index 5377be510..fa01a69fe 100644 --- a/livekit/src/rtc_engine/rtc_session.rs +++ b/livekit/src/rtc_engine/rtc_session.rs @@ -101,7 +101,7 @@ pub enum SessionEvent { request_id: String, method: String, payload: String, - response_timeout_ms: u32, + response_timeout: Duration, version: u32, }, RpcResponse { @@ -689,7 +689,9 @@ impl SessionInner { request_id: rpc_request.id.clone(), method: rpc_request.method.clone(), payload: rpc_request.payload.clone(), - response_timeout_ms: rpc_request.response_timeout_ms, + response_timeout: Duration::from_millis( + rpc_request.response_timeout_ms as u64, + ), version: rpc_request.version, }); } From 8cb20245841442a7a88766a372002f0eccbf0d58 Mon Sep 17 00:00:00 2001 From: Ben Cherry Date: Thu, 31 Oct 2024 14:27:23 -0700 Subject: [PATCH 071/274] ffi 0.12.2 --- Cargo.lock | 2 +- livekit-ffi/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 31372d3c7..b6a1658b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1642,7 +1642,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.12.1" +version = "0.12.2" dependencies = [ "console-subscriber", "dashmap", diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index bbe8ed4fa..27992de71 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.12.1" +version = "0.12.2" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" From a83cb995261141cbafc2bc96e989acf942bc53da Mon Sep 17 00:00:00 2001 From: Ben Cherry Date: Thu, 31 Oct 2024 15:13:18 -0700 Subject: [PATCH 072/274] 0.7 --- livekit/.nanparc | 2 +- livekit/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/livekit/.nanparc b/livekit/.nanparc index 1cc55e1ba..0841e27c2 100644 --- a/livekit/.nanparc +++ b/livekit/.nanparc @@ -1,2 +1,2 @@ -version 0.6.0 +version 0.7.0 language rust diff --git a/livekit/Cargo.toml b/livekit/Cargo.toml index 6e150330b..7ecbed83c 100644 --- a/livekit/Cargo.toml +++ b/livekit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit" -version = "0.6.0" +version = "0.7.0" edition = "2021" license = "Apache-2.0" description = "Rust Client SDK for LiveKit" From 31979b76aa52db4341bed5142c19dc4b984cb7ed Mon Sep 17 00:00:00 2001 From: Ben Cherry Date: Thu, 31 Oct 2024 15:25:58 -0700 Subject: [PATCH 073/274] versions --- livekit-api/.nanparc | 2 +- livekit-api/Cargo.toml | 2 +- livekit-ffi/.nanparc | 2 +- livekit-protocol/.nanparc | 2 +- livekit-protocol/Cargo.toml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/livekit-api/.nanparc b/livekit-api/.nanparc index f3e92f54e..0dacc3fcf 100644 --- a/livekit-api/.nanparc +++ b/livekit-api/.nanparc @@ -1,2 +1,2 @@ -version 0.4.0 +version 0.4.1 language rust diff --git a/livekit-api/Cargo.toml b/livekit-api/Cargo.toml index 401ec9b61..d1e5bcaba 100644 --- a/livekit-api/Cargo.toml +++ b/livekit-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-api" -version = "0.4.0" +version = "0.4.1" license = "Apache-2.0" description = "Rust Server SDK for LiveKit" edition = "2021" diff --git a/livekit-ffi/.nanparc b/livekit-ffi/.nanparc index 18c96a7ba..37b95fd32 100644 --- a/livekit-ffi/.nanparc +++ b/livekit-ffi/.nanparc @@ -1,2 +1,2 @@ -version 0.10.2 +version 0.12.2 language rust diff --git a/livekit-protocol/.nanparc b/livekit-protocol/.nanparc index d431df850..f3e92f54e 100644 --- a/livekit-protocol/.nanparc +++ b/livekit-protocol/.nanparc @@ -1,2 +1,2 @@ -version 0.3.5 +version 0.4.0 language rust diff --git a/livekit-protocol/Cargo.toml b/livekit-protocol/Cargo.toml index 7d44495a1..e9e931113 100644 --- a/livekit-protocol/Cargo.toml +++ b/livekit-protocol/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-protocol" -version = "0.3.5" +version = "0.4.0" edition = "2021" license = "Apache-2.0" description = "Livekit protocol and utilities for the Rust SDK" From 49fb3c3d4b0c13a7f465a760521911d0c885e52a Mon Sep 17 00:00:00 2001 From: Ben Cherry Date: Thu, 31 Oct 2024 15:38:38 -0700 Subject: [PATCH 074/274] Revert "versions" This reverts commit 31979b76aa52db4341bed5142c19dc4b984cb7ed. --- livekit-api/.nanparc | 2 +- livekit-api/Cargo.toml | 2 +- livekit-ffi/.nanparc | 2 +- livekit-protocol/.nanparc | 2 +- livekit-protocol/Cargo.toml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/livekit-api/.nanparc b/livekit-api/.nanparc index 0dacc3fcf..f3e92f54e 100644 --- a/livekit-api/.nanparc +++ b/livekit-api/.nanparc @@ -1,2 +1,2 @@ -version 0.4.1 +version 0.4.0 language rust diff --git a/livekit-api/Cargo.toml b/livekit-api/Cargo.toml index d1e5bcaba..401ec9b61 100644 --- a/livekit-api/Cargo.toml +++ b/livekit-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-api" -version = "0.4.1" +version = "0.4.0" license = "Apache-2.0" description = "Rust Server SDK for LiveKit" edition = "2021" diff --git a/livekit-ffi/.nanparc b/livekit-ffi/.nanparc index 37b95fd32..18c96a7ba 100644 --- a/livekit-ffi/.nanparc +++ b/livekit-ffi/.nanparc @@ -1,2 +1,2 @@ -version 0.12.2 +version 0.10.2 language rust diff --git a/livekit-protocol/.nanparc b/livekit-protocol/.nanparc index f3e92f54e..d431df850 100644 --- a/livekit-protocol/.nanparc +++ b/livekit-protocol/.nanparc @@ -1,2 +1,2 @@ -version 0.4.0 +version 0.3.5 language rust diff --git a/livekit-protocol/Cargo.toml b/livekit-protocol/Cargo.toml index e9e931113..7d44495a1 100644 --- a/livekit-protocol/Cargo.toml +++ b/livekit-protocol/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-protocol" -version = "0.4.0" +version = "0.3.5" edition = "2021" license = "Apache-2.0" description = "Livekit protocol and utilities for the Rust SDK" From 718062838f28ddc07026d9516227a4f539b1c015 Mon Sep 17 00:00:00 2001 From: Ben Cherry Date: Thu, 31 Oct 2024 15:38:47 -0700 Subject: [PATCH 075/274] Revert "0.7" This reverts commit a83cb995261141cbafc2bc96e989acf942bc53da. --- livekit/.nanparc | 2 +- livekit/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/livekit/.nanparc b/livekit/.nanparc index 0841e27c2..1cc55e1ba 100644 --- a/livekit/.nanparc +++ b/livekit/.nanparc @@ -1,2 +1,2 @@ -version 0.7.0 +version 0.6.0 language rust diff --git a/livekit/Cargo.toml b/livekit/Cargo.toml index 7ecbed83c..6e150330b 100644 --- a/livekit/Cargo.toml +++ b/livekit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit" -version = "0.7.0" +version = "0.6.0" edition = "2021" license = "Apache-2.0" description = "Rust Client SDK for LiveKit" From 06997356c083b01da766c4c93e8c4e8354d871ce Mon Sep 17 00:00:00 2001 From: Ben Cherry Date: Thu, 31 Oct 2024 15:55:02 -0700 Subject: [PATCH 076/274] bump rtc 0.7.0, api 0.4.1, proto 0.3.6 (#479) --- Cargo.lock | 8 ++++---- livekit-api/Cargo.toml | 4 ++-- livekit-ffi/Cargo.toml | 6 +++--- livekit-protocol/Cargo.toml | 4 ++-- livekit-runtime/Cargo.toml | 2 +- livekit/Cargo.toml | 8 ++++---- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b6a1658b0..f56302207 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1595,7 +1595,7 @@ checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "livekit" -version = "0.6.0" +version = "0.7.0" dependencies = [ "chrono", "futures-util", @@ -1616,7 +1616,7 @@ dependencies = [ [[package]] name = "livekit-api" -version = "0.4.0" +version = "0.4.1" dependencies = [ "async-tungstenite", "base64", @@ -1667,7 +1667,7 @@ dependencies = [ [[package]] name = "livekit-protocol" -version = "0.3.5" +version = "0.3.6" dependencies = [ "futures-util", "livekit-runtime", @@ -1683,7 +1683,7 @@ dependencies = [ [[package]] name = "livekit-runtime" -version = "0.3.0" +version = "0.3.1" dependencies = [ "async-io 2.3.1", "async-std", diff --git a/livekit-api/Cargo.toml b/livekit-api/Cargo.toml index 401ec9b61..0b50257a2 100644 --- a/livekit-api/Cargo.toml +++ b/livekit-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-api" -version = "0.4.0" +version = "0.4.1" license = "Apache-2.0" description = "Rust Server SDK for LiveKit" edition = "2021" @@ -65,7 +65,7 @@ rustls-tls-webpki-roots = [ __rustls-tls = ["tokio-tungstenite?/__rustls-tls", "reqwest?/__rustls"] [dependencies] -livekit-protocol = { path = "../livekit-protocol", version = "0.3.5" } +livekit-protocol = { path = "../livekit-protocol", version = "0.3.6" } thiserror = "1.0" serde = { version = "1.0", features = ["derive"] } sha2 = "0.10" diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 27992de71..7d6e8d040 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -18,9 +18,9 @@ __rustls-tls = ["livekit/__rustls-tls"] tracing = ["tokio/tracing", "console-subscriber"] [dependencies] -livekit = { path = "../livekit", version = "0.6.0" } +livekit = { path = "../livekit", version = "0.7.0" } soxr-sys = { path = "../soxr-sys" } -livekit-protocol = { path = "../livekit-protocol", version = "0.3.5" } +livekit-protocol = { path = "../livekit-protocol", version = "0.3.6" } tokio = { version = "1", features = ["full", "parking_lot"] } futures-util = { version = "0.3", default-features = false, features = ["sink"] } parking_lot = { version = "0.12", features = ["deadlock_detection"] } @@ -42,7 +42,7 @@ jni = "0.21.1" webrtc-sys-build = { path = "../webrtc-sys/build", version = "0.3.2" } [dev-dependencies] -livekit-api = { path = "../livekit-api", version = "0.4.0" } +livekit-api = { path = "../livekit-api", version = "0.4.1" } [lib] crate-type = ["lib", "cdylib"] diff --git a/livekit-protocol/Cargo.toml b/livekit-protocol/Cargo.toml index 7d44495a1..49d2f17b0 100644 --- a/livekit-protocol/Cargo.toml +++ b/livekit-protocol/Cargo.toml @@ -1,13 +1,13 @@ [package] name = "livekit-protocol" -version = "0.3.5" +version = "0.3.6" edition = "2021" license = "Apache-2.0" description = "Livekit protocol and utilities for the Rust SDK" repository = "https://github.com/livekit/rust-sdks" [dependencies] -livekit-runtime = { path = "../livekit-runtime", version = "0.3.0" } +livekit-runtime = { path = "../livekit-runtime", version = "0.3.1" } tokio = { version = "1", default-features = false, features = [ "sync", "macros", diff --git a/livekit-runtime/Cargo.toml b/livekit-runtime/Cargo.toml index b986d4ae9..bb3a283a7 100644 --- a/livekit-runtime/Cargo.toml +++ b/livekit-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-runtime" -version = "0.3.0" +version = "0.3.1" license = "Apache-2.0" description = "Async runtime compatibility layer for LiveKit" edition = "2021" diff --git a/livekit/Cargo.toml b/livekit/Cargo.toml index 6e150330b..4f5a9cac6 100644 --- a/livekit/Cargo.toml +++ b/livekit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit" -version = "0.6.0" +version = "0.7.0" edition = "2021" license = "Apache-2.0" description = "Rust Client SDK for LiveKit" @@ -27,10 +27,10 @@ __rustls-tls = ["livekit-api/__rustls-tls"] __lk-internal = [] [dependencies] -livekit-runtime = { path = "../livekit-runtime", version = "0.3.0", default-features = false } -livekit-api = { path = "../livekit-api", version = "0.4.0", default-features = false } +livekit-runtime = { path = "../livekit-runtime", version = "0.3.1", default-features = false } +livekit-api = { path = "../livekit-api", version = "0.4.1", default-features = false } libwebrtc = { path = "../libwebrtc", version = "0.3.7" } -livekit-protocol = { path = "../livekit-protocol", version = "0.3.5" } +livekit-protocol = { path = "../livekit-protocol", version = "0.3.6" } prost = "0.12" serde = { version = "1", features = ["derive"] } serde_json = "1.0" From 80a36c2754a77b3e00e128529173020f4da181b7 Mon Sep 17 00:00:00 2001 From: Mervs Date: Mon, 11 Nov 2024 17:21:34 +0100 Subject: [PATCH 077/274] fix: re-export twirp error types (#480) --- livekit-api/src/services/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/livekit-api/src/services/mod.rs b/livekit-api/src/services/mod.rs index d983c39b2..cc47aefad 100644 --- a/livekit-api/src/services/mod.rs +++ b/livekit-api/src/services/mod.rs @@ -19,6 +19,8 @@ use thiserror::Error; use crate::access_token::{AccessToken, AccessTokenError, SIPGrants, VideoGrants}; +pub use twirp_client::{TwirpError, TwirpErrorCode, TwirpResult}; + pub mod egress; pub mod ingress; pub mod room; @@ -35,7 +37,7 @@ pub enum ServiceError { #[error("invalid access token: {0}")] AccessToken(#[from] AccessTokenError), #[error("twirp error: {0}")] - Twirp(#[from] twirp_client::TwirpError), + Twirp(#[from] TwirpError), } pub type ServiceResult = Result; From ee5076baf83bfe9e8100fb7c78757db746ee4c70 Mon Sep 17 00:00:00 2001 From: Michael Sloan Date: Tue, 12 Nov 2024 19:51:53 -0700 Subject: [PATCH 078/274] Use rust::String::lossy for stringification to avoid crashing due to non-utf8 content (#485) --- webrtc-sys/src/jsep.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webrtc-sys/src/jsep.cpp b/webrtc-sys/src/jsep.cpp index 0dcc9f0fb..a1283229e 100644 --- a/webrtc-sys/src/jsep.cpp +++ b/webrtc-sys/src/jsep.cpp @@ -53,7 +53,7 @@ rust::String IceCandidate::candidate() const { rust::String IceCandidate::stringify() const { std::string str; ice_candidate_->ToString(&str); - return rust::String{str}; + return rust::String::lossy(str); } std::unique_ptr IceCandidate::release() { @@ -85,7 +85,7 @@ SdpType SessionDescription::sdp_type() const { rust::String SessionDescription::stringify() const { std::string str; session_description_->ToString(&str); - return rust::String{str}; + return rust::String::lossy(str); } std::unique_ptr SessionDescription::clone() const { From 3c70895941d6de6d28adfd69eea4e5f09336e5db Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 12 Nov 2024 18:53:03 -0800 Subject: [PATCH 079/274] Fix dispatcher example (#484) Co-authored-by: Kirill Bulatov --- examples/basic_room_dispatcher/Cargo.lock | 85 ++++++++++++++++------ examples/basic_room_dispatcher/Cargo.toml | 2 +- examples/basic_room_dispatcher/src/main.rs | 3 +- examples/wgpu_room/src/video_renderer.rs | 63 ++++++---------- livekit-api/Cargo.toml | 2 +- 5 files changed, 89 insertions(+), 66 deletions(-) diff --git a/examples/basic_room_dispatcher/Cargo.lock b/examples/basic_room_dispatcher/Cargo.lock index f43f54d98..c86a54912 100644 --- a/examples/basic_room_dispatcher/Cargo.lock +++ b/examples/basic_room_dispatcher/Cargo.lock @@ -37,6 +37,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anyhow" version = "1.0.80" @@ -429,11 +444,16 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.34" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", "num-traits", + "wasm-bindgen", + "windows-targets 0.52.0", ] [[package]] @@ -1115,6 +1135,29 @@ dependencies = [ "tokio-native-tls", ] +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "idna" version = "0.5.0" @@ -1301,7 +1344,7 @@ checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libwebrtc" -version = "0.3.2" +version = "0.3.7" dependencies = [ "cxx", "jni", @@ -1315,7 +1358,6 @@ dependencies = [ "serde_json", "thiserror", "tokio", - "tokio-stream", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -1357,8 +1399,9 @@ checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "livekit" -version = "0.3.2" +version = "0.7.0" dependencies = [ + "chrono", "futures-util", "lazy_static", "libwebrtc", @@ -1368,6 +1411,7 @@ dependencies = [ "log", "parking_lot", "prost", + "semver", "serde", "serde_json", "thiserror", @@ -1376,7 +1420,7 @@ dependencies = [ [[package]] name = "livekit-api" -version = "0.3.2" +version = "0.4.1" dependencies = [ "async-tungstenite", "base64", @@ -1402,7 +1446,7 @@ dependencies = [ [[package]] name = "livekit-protocol" -version = "0.3.2" +version = "0.3.6" dependencies = [ "futures-util", "livekit-runtime", @@ -1418,7 +1462,7 @@ dependencies = [ [[package]] name = "livekit-runtime" -version = "0.3.0" +version = "0.3.1" dependencies = [ "async-io 2.3.1", "async-std", @@ -2415,6 +2459,7 @@ dependencies = [ "mio", "num_cpus", "pin-project-lite", + "signal-hook-registry", "socket2 0.5.5", "tokio-macros", "windows-sys 0.48.0", @@ -2451,17 +2496,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-stream" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - [[package]] name = "tokio-tungstenite" version = "0.20.1" @@ -2767,7 +2801,7 @@ dependencies = [ [[package]] name = "webrtc-sys" -version = "0.3.2" +version = "0.3.5" dependencies = [ "cc", "cxx", @@ -2779,7 +2813,7 @@ dependencies = [ [[package]] name = "webrtc-sys-build" -version = "0.3.2" +version = "0.3.5" dependencies = [ "fs2", "regex", @@ -2832,6 +2866,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.0", +] + [[package]] name = "windows-sys" version = "0.45.0" diff --git a/examples/basic_room_dispatcher/Cargo.toml b/examples/basic_room_dispatcher/Cargo.toml index 291bea2f7..eef4d7a8a 100644 --- a/examples/basic_room_dispatcher/Cargo.toml +++ b/examples/basic_room_dispatcher/Cargo.toml @@ -11,4 +11,4 @@ livekit = { path = "../../livekit", default-features = false, features = ["nativ livekit-api = { path = "../../livekit-api" } log = "0.4" -[workspace] \ No newline at end of file +[workspace] diff --git a/examples/basic_room_dispatcher/src/main.rs b/examples/basic_room_dispatcher/src/main.rs index e61c182e6..7f05acbaa 100644 --- a/examples/basic_room_dispatcher/src/main.rs +++ b/examples/basic_room_dispatcher/src/main.rs @@ -47,12 +47,11 @@ fn main() { .unwrap(); let (room, mut rx) = Room::connect(&url, &token, RoomOptions::default()).await.unwrap(); - log::info!("Connected to room: {} - {}", room.name(), room.sid()); + log::info!("Connected to room: {} - {}", room.name(), room.sid().await); room.local_participant() .publish_data(DataPacket { payload: "Hello world".to_owned().into_bytes(), - kind: DataPacketKind::Reliable, ..Default::default() }) .await diff --git a/examples/wgpu_room/src/video_renderer.rs b/examples/wgpu_room/src/video_renderer.rs index 1c052f675..b45e615c7 100644 --- a/examples/wgpu_room/src/video_renderer.rs +++ b/examples/wgpu_room/src/video_renderer.rs @@ -86,20 +86,13 @@ impl VideoRenderer { bytes_per_row: Some(width * 4), ..Default::default() }, - wgpu::Extent3d { - width, - height, - ..Default::default() - }, + wgpu::Extent3d { width, height, ..Default::default() }, ); } } }); - Self { - rtc_track, - internal, - } + Self { rtc_track, internal } } // Returns the last frame resolution @@ -124,47 +117,35 @@ impl RendererInternal { self.height = height; self.rgba_data.resize((width * height * 4) as usize, 0); - self.texture = Some( - self.render_state - .device - .create_texture(&wgpu::TextureDescriptor { - label: Some("lk-videotexture"), - usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, - dimension: wgpu::TextureDimension::D2, - size: wgpu::Extent3d { - width, - height, - ..Default::default() - }, - sample_count: 1, - mip_level_count: 1, - format: wgpu::TextureFormat::Rgba8UnormSrgb, - view_formats: &[wgpu::TextureFormat::Rgba8UnormSrgb], - }), - ); - - self.texture_view = Some(self.texture.as_mut().unwrap().create_view( - &wgpu::TextureViewDescriptor { + self.texture = Some(self.render_state.device.create_texture(&wgpu::TextureDescriptor { + label: Some("lk-videotexture"), + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + dimension: wgpu::TextureDimension::D2, + size: wgpu::Extent3d { width, height, ..Default::default() }, + sample_count: 1, + mip_level_count: 1, + format: wgpu::TextureFormat::Rgba8UnormSrgb, + view_formats: &[wgpu::TextureFormat::Rgba8UnormSrgb], + })); + + self.texture_view = + Some(self.texture.as_mut().unwrap().create_view(&wgpu::TextureViewDescriptor { label: Some("lk-videotexture-view"), format: Some(wgpu::TextureFormat::Rgba8UnormSrgb), dimension: Some(wgpu::TextureViewDimension::D2), mip_level_count: Some(1), array_layer_count: Some(1), ..Default::default() - }, - )); + })); if let Some(texture_id) = self.egui_texture { // Update the existing texture - self.render_state - .renderer - .write() - .update_egui_texture_from_wgpu_texture( - &self.render_state.device, - self.texture_view.as_ref().unwrap(), - wgpu::FilterMode::Linear, - texture_id, - ); + self.render_state.renderer.write().update_egui_texture_from_wgpu_texture( + &self.render_state.device, + self.texture_view.as_ref().unwrap(), + wgpu::FilterMode::Linear, + texture_id, + ); } else { self.egui_texture = Some(self.render_state.renderer.write().register_native_texture( &self.render_state.device, diff --git a/livekit-api/Cargo.toml b/livekit-api/Cargo.toml index 0b50257a2..99538ca4a 100644 --- a/livekit-api/Cargo.toml +++ b/livekit-api/Cargo.toml @@ -85,7 +85,7 @@ jsonwebtoken = { version = "9", default-features = false, optional = true } livekit-runtime = { path = "../livekit-runtime", version = "0.3.0", optional = true} tokio-tungstenite = { version = "0.20", optional = true } async-tungstenite = { version = "0.25.0", features = [ "async-std-runtime", "async-native-tls"], optional = true } -tokio = { version = "1", default-features = false, features = ["sync", "macros"], optional = true } +tokio = { version = "1", default-features = false, features = ["sync", "macros", "signal"], optional = true } futures-util = { version = "0.3", default-features = false, features = [ "sink" ], optional = true } # This dependency must be kept in sync with reqwest's version From c48f892fb444433beed2ca4fe5e73c0c32de516e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Thu, 14 Nov 2024 16:51:27 +0100 Subject: [PATCH 080/274] soxr: fix segfault when pushing after a flush (#486) --- livekit-ffi/src/server/resampler.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/livekit-ffi/src/server/resampler.rs b/livekit-ffi/src/server/resampler.rs index ad2eaa753..6faa68b02 100644 --- a/livekit-ffi/src/server/resampler.rs +++ b/livekit-ffi/src/server/resampler.rs @@ -127,6 +127,13 @@ impl SoxResampler { return Err(error_msg.to_string_lossy().to_string()); } + let error = unsafe { soxr_sys::soxr_clear(self.soxr_ptr) }; + + if !error.is_null() { + let error_msg = unsafe { std::ffi::CStr::from_ptr(error) }; + return Err(error_msg.to_string_lossy().to_string()); + } + Ok(&self.out_buf[..odone]) } } From b41861c7b71762d5d85b3de07ae67ffcae7c3fa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?The=CC=81o=20Monnom?= Date: Thu, 14 Nov 2024 21:34:07 +0100 Subject: [PATCH 081/274] ffi v0.12.3 --- livekit-ffi/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 7d6e8d040..7109c2541 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.12.2" +version = "0.12.3" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" From fe8037ab6ff22e2f424148ed1d93ed7e4f9b116e Mon Sep 17 00:00:00 2001 From: aoife cassidy Date: Thu, 21 Nov 2024 00:11:18 +0200 Subject: [PATCH 082/274] simplify version cascading by using workspaces (#483) --- .github/workflows/format.yml | 8 ++++++++ Cargo.toml | 12 ++++++++++++ libwebrtc/Cargo.toml | 2 +- livekit-api/.nanparc | 2 +- livekit-ffi/.nanparc | 2 +- livekit-ffi/Cargo.toml | 4 ++-- livekit-protocol/.nanparc | 2 +- livekit-protocol/Cargo.toml | 2 +- livekit-runtime/.nanparc | 2 +- livekit/.nanparc | 2 +- livekit/Cargo.toml | 8 ++++---- webrtc-sys/Cargo.toml | 2 +- 12 files changed, 34 insertions(+), 14 deletions(-) diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index d990bb829..0e6bbb498 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -31,6 +31,14 @@ jobs: toolchain: nightly-2023-12-30 components: rustfmt + - name: Check workspace versions match + run: | + cargo metadata --format-version=1 --no-deps | + jq -e -r '.packages[] | "\(.name) \(.version)"' | + sort | xargs -l bash -c \ + 'grep "$0 = { version = \"$1\"" -q Cargo.toml && printf "version %-7s %-27s $0\n" $1 "matches package" || printf "version %-7s is not reflected on package $0\n" "$1" | false'; + if [ $? -eq 0 ]; then exit 0; else exit 1; fi + - name: Cargo fmt run: | cargo fmt -- --check diff --git a/Cargo.toml b/Cargo.toml index 47c7ce8f7..42a408a4b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,8 +4,20 @@ members = [ "livekit-api", "livekit-protocol", "livekit-ffi", + "livekit-runtime", "libwebrtc", "soxr-sys", "webrtc-sys", "webrtc-sys/build", ] + +[workspace.dependencies] +libwebrtc = { version = "0.3.7", path = "libwebrtc" } +livekit-api = { version = "0.4.1", path = "livekit-api" } +livekit-ffi = { version = "0.12.2", path = "livekit-ffi" } +livekit-protocol = { version = "0.3.6", path = "livekit-protocol" } +livekit-runtime = { version = "0.3.1", path = "livekit-runtime" } +livekit = { version = "0.7.0", path = "livekit" } +soxr-sys = { version = "0.1.0", path = "soxr-sys" } +webrtc-sys-build = { version = "0.3.5", path = "webrtc-sys/build" } +webrtc-sys = { version = "0.3.5", path = "webrtc-sys" } diff --git a/libwebrtc/Cargo.toml b/libwebrtc/Cargo.toml index 28d7ad5f8..1f8f0c988 100644 --- a/libwebrtc/Cargo.toml +++ b/libwebrtc/Cargo.toml @@ -8,7 +8,7 @@ description = "Livekit safe bindings to libwebrtc" repository = "https://github.com/livekit/rust-sdks" [dependencies] -livekit-protocol = { path = "../livekit-protocol", version = "0.3.5" } +livekit-protocol = { workspace = true } log = "0.4" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/livekit-api/.nanparc b/livekit-api/.nanparc index f3e92f54e..0dacc3fcf 100644 --- a/livekit-api/.nanparc +++ b/livekit-api/.nanparc @@ -1,2 +1,2 @@ -version 0.4.0 +version 0.4.1 language rust diff --git a/livekit-ffi/.nanparc b/livekit-ffi/.nanparc index 18c96a7ba..37b95fd32 100644 --- a/livekit-ffi/.nanparc +++ b/livekit-ffi/.nanparc @@ -1,2 +1,2 @@ -version 0.10.2 +version 0.12.2 language rust diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 7109c2541..8ec2ea3c1 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -18,9 +18,9 @@ __rustls-tls = ["livekit/__rustls-tls"] tracing = ["tokio/tracing", "console-subscriber"] [dependencies] -livekit = { path = "../livekit", version = "0.7.0" } +livekit = { workspace = true } soxr-sys = { path = "../soxr-sys" } -livekit-protocol = { path = "../livekit-protocol", version = "0.3.6" } +livekit-protocol = { workspace = true } tokio = { version = "1", features = ["full", "parking_lot"] } futures-util = { version = "0.3", default-features = false, features = ["sink"] } parking_lot = { version = "0.12", features = ["deadlock_detection"] } diff --git a/livekit-protocol/.nanparc b/livekit-protocol/.nanparc index d431df850..10b95b2c7 100644 --- a/livekit-protocol/.nanparc +++ b/livekit-protocol/.nanparc @@ -1,2 +1,2 @@ -version 0.3.5 +version 0.3.6 language rust diff --git a/livekit-protocol/Cargo.toml b/livekit-protocol/Cargo.toml index 49d2f17b0..bc65d15d4 100644 --- a/livekit-protocol/Cargo.toml +++ b/livekit-protocol/Cargo.toml @@ -7,7 +7,7 @@ description = "Livekit protocol and utilities for the Rust SDK" repository = "https://github.com/livekit/rust-sdks" [dependencies] -livekit-runtime = { path = "../livekit-runtime", version = "0.3.1" } +livekit-runtime = { workspace = true } tokio = { version = "1", default-features = false, features = [ "sync", "macros", diff --git a/livekit-runtime/.nanparc b/livekit-runtime/.nanparc index 80b849a2a..4efcdcec5 100644 --- a/livekit-runtime/.nanparc +++ b/livekit-runtime/.nanparc @@ -1,2 +1,2 @@ -version 0.3.0 +version 0.3.1 language rust diff --git a/livekit/.nanparc b/livekit/.nanparc index 1cc55e1ba..0841e27c2 100644 --- a/livekit/.nanparc +++ b/livekit/.nanparc @@ -1,2 +1,2 @@ -version 0.6.0 +version 0.7.0 language rust diff --git a/livekit/Cargo.toml b/livekit/Cargo.toml index 4f5a9cac6..29113321a 100644 --- a/livekit/Cargo.toml +++ b/livekit/Cargo.toml @@ -27,10 +27,10 @@ __rustls-tls = ["livekit-api/__rustls-tls"] __lk-internal = [] [dependencies] -livekit-runtime = { path = "../livekit-runtime", version = "0.3.1", default-features = false } -livekit-api = { path = "../livekit-api", version = "0.4.1", default-features = false } -libwebrtc = { path = "../libwebrtc", version = "0.3.7" } -livekit-protocol = { path = "../livekit-protocol", version = "0.3.6" } +livekit-runtime = { workspace = true, default-features = false } +livekit-api = { workspace = true, default-features = false } +libwebrtc = { workspace = true } +livekit-protocol = { workspace = true } prost = "0.12" serde = { version = "1", features = ["derive"] } serde_json = "1.0" diff --git a/webrtc-sys/Cargo.toml b/webrtc-sys/Cargo.toml index 5b249ca33..7c5e9b173 100644 --- a/webrtc-sys/Cargo.toml +++ b/webrtc-sys/Cargo.toml @@ -12,7 +12,7 @@ cxx = "1.0" log = "0.4" [build-dependencies] -webrtc-sys-build = { version = "0.3.5", path = "./build" } +webrtc-sys-build = { workspace = true } cxx-build = "1.0" glob = "0.3" cc = "1.0" From 15a5a7f8708d4fb93c0bb5bd56b0cf3696409beb Mon Sep 17 00:00:00 2001 From: zesun96 Date: Fri, 22 Nov 2024 21:19:36 +0800 Subject: [PATCH 083/274] Fix carah error of get session stats request (#490) --- libwebrtc/src/stats.rs | 19 +++++++++++++++++++ livekit-ffi/protocol/stats.proto | 14 +++++++++++++- livekit-ffi/src/conversion/stats.rs | 13 +++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/libwebrtc/src/stats.rs b/libwebrtc/src/stats.rs index dae455e4a..23a64edc1 100644 --- a/libwebrtc/src/stats.rs +++ b/libwebrtc/src/stats.rs @@ -28,6 +28,7 @@ pub enum RtcStats { LocalCandidate(LocalCandidateStats), RemoteCandidate(RemoteCandidateStats), Certificate(CertificateStats), + Stream(StreamStats), Track, // Deprecated } @@ -272,6 +273,15 @@ pub struct CertificateStats { pub certificate: dictionaries::CertificateStats, } +#[derive(Debug, Default, Clone, Deserialize)] +pub struct StreamStats { + #[serde(flatten)] + pub rtc: dictionaries::RtcStats, + + #[serde(flatten)] + pub stream: dictionaries::StreamStats, +} + #[derive(Debug, Default, Clone, Deserialize)] pub struct TrackStats {} @@ -588,4 +598,13 @@ pub mod dictionaries { pub base64_certificate: String, pub issuer_certificate_id: String, } + + #[derive(Debug, Default, Clone, Deserialize)] + #[serde(rename_all = "camelCase")] + #[serde(default)] + pub struct StreamStats { + pub id: String, + pub stream_identifier: String, + // pub timestamp: i64, + } } diff --git a/livekit-ffi/protocol/stats.proto b/livekit-ffi/protocol/stats.proto index f4771f3e1..103981aed 100644 --- a/livekit-ffi/protocol/stats.proto +++ b/livekit-ffi/protocol/stats.proto @@ -170,6 +170,11 @@ message RtcStats { required CertificateStats certificate = 2; } + message Stream { + required RtcStatsData rtc = 1; + required StreamStats stream = 2; + } + message Track { // Deprecated } @@ -189,7 +194,8 @@ message RtcStats { LocalCandidate local_candidate = 14; RemoteCandidate remote_candidate = 15; Certificate certificate = 16; - Track track = 17; + Stream stream = 17; + Track track = 18; } } @@ -447,3 +453,9 @@ message CertificateStats { required string issuer_certificate_id = 4; } +message StreamStats { + required string id = 1; + required string stream_identifier = 2; + // required int64 timestamp = 3; +} + diff --git a/livekit-ffi/src/conversion/stats.rs b/livekit-ffi/src/conversion/stats.rs index 0ee214958..4b2d56e5b 100644 --- a/livekit-ffi/src/conversion/stats.rs +++ b/livekit-ffi/src/conversion/stats.rs @@ -164,6 +164,7 @@ impl From for proto::RtcStats { rtc::RtcStats::Certificate(certificate) => { proto::rtc_stats::Stats::Certificate(certificate.into()) } + rtc::RtcStats::Stream(stream) => proto::rtc_stats::Stats::Stream(stream.into()), rtc::RtcStats::Track {} => { proto::rtc_stats::Stats::Track(proto::rtc_stats::Track {}) } @@ -281,6 +282,12 @@ impl From for proto::rtc_stats::Certificate { } } +impl From for proto::rtc_stats::Stream { + fn from(value: rtc::StreamStats) -> Self { + Self { rtc: value.rtc.into(), stream: value.stream.into() } + } +} + // Dictionaries impl From for proto::RtcStatsData { @@ -289,6 +296,12 @@ impl From for proto::RtcStatsData { } } +impl From for proto::StreamStats { + fn from(value: rtc::dictionaries::StreamStats) -> Self { + Self { id: value.id, stream_identifier: value.stream_identifier } + } +} + impl From for proto::CodecStats { fn from(value: rtc::dictionaries::CodecStats) -> Self { Self { From 979d0c55c442b24c83850f64890056ea2baefcba Mon Sep 17 00:00:00 2001 From: zesun96 Date: Wed, 27 Nov 2024 00:27:36 +0800 Subject: [PATCH 084/274] add adaptive streaming; (#488) --- livekit-ffi/generate_proto.sh | 1 + livekit-ffi/protocol/ffi.proto | 8 ++++ livekit-ffi/protocol/track_publication.proto | 38 +++++++++++++++++ livekit-ffi/src/livekit.proto.rs | 42 ++++++++++++++++++- livekit-ffi/src/server/requests.rs | 40 ++++++++++++++++++ .../room/participant/remote_participant.rs | 27 +++++++++++- livekit/src/room/publication/remote.rs | 27 ++++++++++++ 7 files changed, 180 insertions(+), 3 deletions(-) create mode 100644 livekit-ffi/protocol/track_publication.proto diff --git a/livekit-ffi/generate_proto.sh b/livekit-ffi/generate_proto.sh index b1d5e9d6a..029ccd3d9 100755 --- a/livekit-ffi/generate_proto.sh +++ b/livekit-ffi/generate_proto.sh @@ -24,6 +24,7 @@ protoc \ $PROTOCOL/handle.proto \ $PROTOCOL/room.proto \ $PROTOCOL/track.proto \ + $PROTOCOL/track_publication.proto \ $PROTOCOL/participant.proto \ $PROTOCOL/video_frame.proto \ $PROTOCOL/audio_frame.proto \ diff --git a/livekit-ffi/protocol/ffi.proto b/livekit-ffi/protocol/ffi.proto index e8c1fda4a..4ff6df53c 100644 --- a/livekit-ffi/protocol/ffi.proto +++ b/livekit-ffi/protocol/ffi.proto @@ -20,6 +20,7 @@ option csharp_namespace = "LiveKit.Proto"; // import "handle.proto"; import "e2ee.proto"; import "track.proto"; +import "track_publication.proto"; import "room.proto"; import "video_frame.proto"; import "audio_frame.proto"; @@ -106,6 +107,10 @@ message FfiRequest { RegisterRpcMethodRequest register_rpc_method = 39; UnregisterRpcMethodRequest unregister_rpc_method = 40; RpcMethodInvocationResponseRequest rpc_method_invocation_response = 41; + + // Track Publication + EnableRemoteTrackPublicationRequest enable_remote_track_publication = 42; + UpdateRemoteTrackPublicationDimensionRequest update_remote_track_publication_dimension = 43; } } @@ -160,6 +165,9 @@ message FfiResponse { RegisterRpcMethodResponse register_rpc_method = 38; UnregisterRpcMethodResponse unregister_rpc_method = 39; RpcMethodInvocationResponseResponse rpc_method_invocation_response = 40; + // Track Publication + EnableRemoteTrackPublicationResponse enable_remote_track_publication = 41; + UpdateRemoteTrackPublicationDimensionResponse update_remote_track_publication_dimension = 42; } } diff --git a/livekit-ffi/protocol/track_publication.proto b/livekit-ffi/protocol/track_publication.proto new file mode 100644 index 000000000..44f0b681f --- /dev/null +++ b/livekit-ffi/protocol/track_publication.proto @@ -0,0 +1,38 @@ +// Copyright 2023 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto2"; + +package livekit.proto; +option csharp_namespace = "LiveKit.Proto"; + +// Enable/Disable a remote track publication +message EnableRemoteTrackPublicationRequest { + required uint64 track_publication_handle = 1; + required bool enabled = 2; +} + +message EnableRemoteTrackPublicationResponse {} + +// update a remote track publication dimension +message UpdateRemoteTrackPublicationDimensionRequest { + required uint64 track_publication_handle = 1; + required uint32 width = 2; + required uint32 height = 3; +} + +message UpdateRemoteTrackPublicationDimensionResponse {} + + + diff --git a/livekit-ffi/src/livekit.proto.rs b/livekit-ffi/src/livekit.proto.rs index 620588fcd..d5b75fede 100644 --- a/livekit-ffi/src/livekit.proto.rs +++ b/livekit-ffi/src/livekit.proto.rs @@ -1529,6 +1529,34 @@ impl StreamState { } } } +/// Enable/Disable a remote track publication +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EnableRemoteTrackPublicationRequest { + #[prost(uint64, required, tag="1")] + pub track_publication_handle: u64, + #[prost(bool, required, tag="2")] + pub enabled: bool, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EnableRemoteTrackPublicationResponse { +} +/// update a remote track publication dimension +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct UpdateRemoteTrackPublicationDimensionRequest { + #[prost(uint64, required, tag="1")] + pub track_publication_handle: u64, + #[prost(uint32, required, tag="2")] + pub width: u32, + #[prost(uint32, required, tag="3")] + pub height: u32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct UpdateRemoteTrackPublicationDimensionResponse { +} #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ParticipantInfo { @@ -3686,7 +3714,7 @@ pub struct RpcMethodInvocationEvent { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiRequest { - #[prost(oneof="ffi_request::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41")] + #[prost(oneof="ffi_request::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43")] pub message: ::core::option::Option, } /// Nested message and enum types in `FfiRequest`. @@ -3779,13 +3807,18 @@ pub mod ffi_request { UnregisterRpcMethod(super::UnregisterRpcMethodRequest), #[prost(message, tag="41")] RpcMethodInvocationResponse(super::RpcMethodInvocationResponseRequest), + /// Track Publication + #[prost(message, tag="42")] + EnableRemoteTrackPublication(super::EnableRemoteTrackPublicationRequest), + #[prost(message, tag="43")] + UpdateRemoteTrackPublicationDimension(super::UpdateRemoteTrackPublicationDimensionRequest), } } /// This is the output of livekit_ffi_request function. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiResponse { - #[prost(oneof="ffi_response::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40")] + #[prost(oneof="ffi_response::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42")] pub message: ::core::option::Option, } /// Nested message and enum types in `FfiResponse`. @@ -3876,6 +3909,11 @@ pub mod ffi_response { UnregisterRpcMethod(super::UnregisterRpcMethodResponse), #[prost(message, tag="40")] RpcMethodInvocationResponse(super::RpcMethodInvocationResponseResponse), + /// Track Publication + #[prost(message, tag="41")] + EnableRemoteTrackPublication(super::EnableRemoteTrackPublicationResponse), + #[prost(message, tag="42")] + UpdateRemoteTrackPublicationDimension(super::UpdateRemoteTrackPublicationDimensionResponse), } } /// To minimize complexity, participant events are not included in the protocol. diff --git a/livekit-ffi/src/server/requests.rs b/livekit-ffi/src/server/requests.rs index 5c4aa32fc..f14ab0d75 100644 --- a/livekit-ffi/src/server/requests.rs +++ b/livekit-ffi/src/server/requests.rs @@ -152,6 +152,36 @@ fn on_set_subscribed( Ok(proto::SetSubscribedResponse {}) } +fn on_enable_remote_track_publication( + server: &'static FfiServer, + request: proto::EnableRemoteTrackPublicationRequest, +) -> FfiResult { + let ffi_publication = + server.retrieve_handle::(request.track_publication_handle)?; + + let TrackPublication::Remote(publication) = &ffi_publication.publication else { + return Err(FfiError::InvalidRequest("publication is not a RemotePublication".into())); + }; + + publication.set_enabled(request.enabled); + Ok(proto::EnableRemoteTrackPublicationResponse {}) +} + +fn on_update_remote_track_publication_dimension( + server: &'static FfiServer, + request: proto::UpdateRemoteTrackPublicationDimensionRequest, +) -> FfiResult { + let ffi_publication = + server.retrieve_handle::(request.track_publication_handle)?; + + let TrackPublication::Remote(publication) = &ffi_publication.publication else { + return Err(FfiError::InvalidRequest("publication is not a RemotePublication".into())); + }; + let dimension = TrackDimension(request.width, request.height); + publication.update_video_dimensions(dimension); + Ok(proto::UpdateRemoteTrackPublicationDimensionResponse {}) +} + fn on_set_local_metadata( server: &'static FfiServer, set_local_metadata: proto::SetLocalMetadataRequest, @@ -995,6 +1025,16 @@ pub fn handle_request( on_rpc_method_invocation_response(server, request)?, ) } + proto::ffi_request::Message::EnableRemoteTrackPublication(request) => { + proto::ffi_response::Message::EnableRemoteTrackPublication( + on_enable_remote_track_publication(server, request)?, + ) + } + proto::ffi_request::Message::UpdateRemoteTrackPublicationDimension(request) => { + proto::ffi_response::Message::UpdateRemoteTrackPublicationDimension( + on_update_remote_track_publication_dimension(server, request)?, + ) + } }); Ok(res) diff --git a/livekit/src/room/participant/remote_participant.rs b/livekit/src/room/participant/remote_participant.rs index 7ca6e6bd5..e9e61ea07 100644 --- a/livekit/src/room/participant/remote_participant.rs +++ b/livekit/src/room/participant/remote_participant.rs @@ -384,7 +384,32 @@ impl RemoteParticipant { .await }); } - }) + }); + + publication.on_video_dimensions_changed({ + let rtc_engine = self.inner.rtc_engine.clone(); + move |publication, dimension| { + let rtc_engine = rtc_engine.clone(); + livekit_runtime::spawn(async move { + let tsid: String = publication.sid().into(); + let TrackDimension(width, height) = dimension; + let enabled = publication.is_enabled(); + let update_track_settings = proto::UpdateTrackSettings { + track_sids: vec![tsid.clone()], + disabled: !enabled, + width, + height, + ..Default::default() + }; + + rtc_engine + .send_request(proto::signal_request::Message::TrackSetting( + update_track_settings, + )) + .await + }); + } + }); } pub(crate) fn remove_publication(&self, sid: &TrackSid) -> Option { diff --git a/livekit/src/room/publication/remote.rs b/livekit/src/room/publication/remote.rs index dff2d6705..fe86ebf1f 100644 --- a/livekit/src/room/publication/remote.rs +++ b/livekit/src/room/publication/remote.rs @@ -28,6 +28,7 @@ type PermissionStatusChangedHandler = Box; // old_status, new_status type SubscriptionUpdateNeededHandler = Box; type EnabledStatusChangedHandler = Box; +type VideoDimensionsChangedHandler = Box; #[derive(Default)] struct RemoteEvents { @@ -37,6 +38,7 @@ struct RemoteEvents { permission_status_changed: Mutex>, subscription_update_needed: Mutex>, enabled_status_changed: Mutex>, + video_dimensions_changed: Mutex>, } #[derive(Debug)] @@ -215,6 +217,13 @@ impl RemoteTrackPublication { *self.remote.events.enabled_status_changed.lock() = Some(Box::new(f)); } + pub(crate) fn on_video_dimensions_changed( + &self, + f: impl Fn(RemoteTrackPublication, TrackDimension) + Send + 'static, + ) { + *self.remote.events.video_dimensions_changed.lock() = Some(Box::new(f)); + } + pub fn set_subscribed(&self, subscribed: bool) { let old_subscription_state = self.subscription_status(); let old_permission_state = self.permission_status(); @@ -262,6 +271,24 @@ impl RemoteTrackPublication { } } + pub fn update_video_dimensions(&self, dimension: TrackDimension) { + if self.is_subscribed() { + if dimension != self.dimension() { + let TrackDimension(width, height) = dimension; + let mut new_info = self.proto_info(); + new_info.width = width; + new_info.height = height; + self.update_info(new_info); + } + // Request to send an update to the SFU + if let Some(video_dimensions_changed) = + self.remote.events.video_dimensions_changed.lock().as_ref() + { + video_dimensions_changed(self.clone(), dimension) + } + } + } + pub fn subscription_status(&self) -> SubscriptionStatus { if !self.remote.info.read().subscribed { return SubscriptionStatus::Unsubscribed; From 21bdee20612ce2cbb1f0902881447d25f9a6dcda Mon Sep 17 00:00:00 2001 From: Iason Paraskevopoulos Date: Tue, 26 Nov 2024 16:37:42 +0000 Subject: [PATCH 085/274] Fix enconding presets (#464) --- livekit/src/room/options.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/livekit/src/room/options.rs b/livekit/src/room/options.rs index 76577e14c..e17ac2145 100644 --- a/livekit/src/room/options.rs +++ b/livekit/src/room/options.rs @@ -179,7 +179,7 @@ pub fn compute_appropriate_encoding( for preset in presets { encoding = preset.encoding.clone(); - if preset.width >= size { + if preset.width > size { break; } } @@ -257,6 +257,7 @@ pub fn into_rtp_encodings( }) } + encodings.reverse(); encodings } From 0d92f49a4583d064b4957ac89feebb99d1b2ee54 Mon Sep 17 00:00:00 2001 From: Michael Sloan Date: Sat, 30 Nov 2024 00:00:56 -0700 Subject: [PATCH 086/274] Add license to `soxr-sys/Cargo.toml` (#498) Motivation for this is a bit of a corner case: * Including a local checkout of livekit rust-sdks as a package * Installing Zed from source. This requires licenses specified by all packages. --- soxr-sys/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/soxr-sys/Cargo.toml b/soxr-sys/Cargo.toml index 6661f1e24..fde7a4c42 100644 --- a/soxr-sys/Cargo.toml +++ b/soxr-sys/Cargo.toml @@ -3,6 +3,7 @@ name = "soxr-sys" version = "0.1.0" authors = ["Theo Monnom Date: Sat, 30 Nov 2024 00:01:40 -0700 Subject: [PATCH 087/274] Add libwebrtc/README.md explaining how to build and use it (#499) --- webrtc-sys/libwebrtc/README.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 webrtc-sys/libwebrtc/README.md diff --git a/webrtc-sys/libwebrtc/README.md b/webrtc-sys/libwebrtc/README.md new file mode 100644 index 000000000..222db6317 --- /dev/null +++ b/webrtc-sys/libwebrtc/README.md @@ -0,0 +1,28 @@ +This directory can contain a checkout of WebRTC. The build scripts +here will install dependencies, checkout the version that LiveKit +currently uses, apply some patches to it, and build it. For example to +do a Linux debug build on x64: + +```sh +$ ./build-linux.sh --arch x64 --profile release +``` + +After running this, `linux-x64-debug/lib/libwebrtc.a` should +exist. This can be rerun to rebuild it, but will complain about +patches not applying as they have already been applied. + +If something goes wrong it may be helpful to consult the [WebRTC native +development documentation](https://webrtc.googlesource.com/src/+/main/docs/native-code/development/). + +# Building LiveKit Rust SDK with custom WebRTC checkout + +Add the following environment variable to `/.config/config.toml`, to +specify use of a custom WebRTC build: + +```toml +[env] +LK_CUSTOM_WEBRTC = { value = "webrtc-sys/libwebrtc/linux-x64-release", relative = true } +``` + +Note that `linux-x64-debug` should be replaced with the artifact +directory appropriate for your configuration. From 154555bfb9c6aee6f4e5c57874826bb1d5b23a04 Mon Sep 17 00:00:00 2001 From: zesun96 Date: Sat, 30 Nov 2024 20:08:29 +0800 Subject: [PATCH 088/274] add livekit-ffi generate proto script in windows (#494) --- livekit-ffi/generate_proto_win.bat | 33 ++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 livekit-ffi/generate_proto_win.bat diff --git a/livekit-ffi/generate_proto_win.bat b/livekit-ffi/generate_proto_win.bat new file mode 100644 index 000000000..303cf3056 --- /dev/null +++ b/livekit-ffi/generate_proto_win.bat @@ -0,0 +1,33 @@ +@echo off + +rem Copyright 2023 LiveKit, Inc. +rem +rem Licensed under the Apache License, Version 2.0 (the "License"); +rem you may not use this file except in compliance with the License. +rem You may obtain a copy of the License at +rem +rem http://www.apache.org/licenses/LICENSE-2.0 +rem +rem Unless required by applicable law or agreed to in writing, software +rem distributed under the License is distributed on an "AS IS" BASIS, +rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +rem See the License for the specific language governing permissions and +rem limitations under the License. + +set PROTOCOL=protocol +set OUT_RUST=src + +protoc.exe ^ + -I=%PROTOCOL% ^ + --prost_out=%OUT_RUST% ^ + %PROTOCOL%/ffi.proto ^ + %PROTOCOL%/handle.proto ^ + %PROTOCOL%/room.proto ^ + %PROTOCOL%/track.proto ^ + %PROTOCOL%/track_publication.proto ^ + %PROTOCOL%/participant.proto ^ + %PROTOCOL%/video_frame.proto ^ + %PROTOCOL%/audio_frame.proto ^ + %PROTOCOL%/e2ee.proto ^ + %PROTOCOL%/stats.proto ^ + %PROTOCOL%/rpc.proto From 9d68b19c90363e5a75c3b11a4ee07f7f7a5afaa8 Mon Sep 17 00:00:00 2001 From: tommady Date: Sat, 30 Nov 2024 20:09:16 +0800 Subject: [PATCH 089/274] fix: Doc.rs Build Fails For livekit-api = 0.4.1 (#495) --- .github/workflows/tests.yml | 6 +- Cargo.lock | 2 +- Cargo.toml | 3 +- livekit-ffi/Cargo.toml | 2 +- livekit-ffi/build.rs | 36 +- livekit-ffi/src/livekit.proto.rs | 832 +++++++++++-------------------- livekit-runtime/Cargo.toml | 1 + 7 files changed, 326 insertions(+), 556 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e0f696ef5..2b159c66f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -43,8 +43,8 @@ jobs: steps: - name: Install Rust toolchain run: | - rustup update --no-self-update stable - rustup target add ${{ matrix.target }} + rustup update --no-self-update nightly + rustup target add ${{ matrix.target }} --toolchain nightly - name: Install linux dependencies if: ${{ matrix.os == 'ubuntu-20.04' }} @@ -57,6 +57,6 @@ jobs: submodules: true - name: Test - run: cargo test --release --verbose --target ${{ matrix.target }} -- --nocapture + run: cargo +nightly test --release --verbose --target ${{ matrix.target }} -- --nocapture diff --git a/Cargo.lock b/Cargo.lock index f56302207..d907dc782 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1642,7 +1642,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.12.2" +version = "0.12.3" dependencies = [ "console-subscriber", "dashmap", diff --git a/Cargo.toml b/Cargo.toml index 42a408a4b..52650e01e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,5 @@ [workspace] +resolver = "2" members = [ "livekit", "livekit-api", @@ -14,7 +15,7 @@ members = [ [workspace.dependencies] libwebrtc = { version = "0.3.7", path = "libwebrtc" } livekit-api = { version = "0.4.1", path = "livekit-api" } -livekit-ffi = { version = "0.12.2", path = "livekit-ffi" } +livekit-ffi = { version = "0.12.3", path = "livekit-ffi" } livekit-protocol = { version = "0.3.6", path = "livekit-protocol" } livekit-runtime = { version = "0.3.1", path = "livekit-runtime" } livekit = { version = "0.7.0", path = "livekit" } diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 8ec2ea3c1..01d743237 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -39,7 +39,7 @@ imgproc = "0.3.11" jni = "0.21.1" [build-dependencies] -webrtc-sys-build = { path = "../webrtc-sys/build", version = "0.3.2" } +webrtc-sys-build = { path = "../webrtc-sys/build", version = "0.3.5" } [dev-dependencies] livekit-api = { path = "../livekit-api", version = "0.4.1" } diff --git a/livekit-ffi/build.rs b/livekit-ffi/build.rs index 92af1ff61..28afe0d05 100644 --- a/livekit-ffi/build.rs +++ b/livekit-ffi/build.rs @@ -19,20 +19,34 @@ fn main() { return; } - webrtc_sys_build::download_webrtc().unwrap(); - if env::var("CARGO_CFG_TARGET_OS").unwrap() == "android" { - webrtc_sys_build::configure_jni_symbols().unwrap(); + let webrtc_dir = webrtc_sys_build::webrtc_dir(); + if !webrtc_dir.exists() { + webrtc_sys_build::download_webrtc().unwrap(); } - { - // Copy the webrtc license to CARGO_MANIFEST_DIR - // (used by the ffi release action) - let webrtc_dir = webrtc_sys_build::webrtc_dir(); - let license = webrtc_dir.join("LICENSE.md"); - let target_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap(); + match target_os.as_str() { + "windows" => {} + "linux" => { + // Link webrtc library + println!("cargo:rustc-link-lib=static=webrtc"); + } + "android" => { + webrtc_sys_build::configure_jni_symbols().unwrap(); + // Copy the webrtc license to CARGO_MANIFEST_DIR + // (used by the ffi release action) + let license = webrtc_dir.join("LICENSE.md"); + let target_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); - let out_file = Path::new(&target_dir).join("WEBRTC_LICENSE.md"); + let out_file = Path::new(&target_dir).join("WEBRTC_LICENSE.md"); - std::fs::copy(license, out_file).unwrap(); + std::fs::copy(license, out_file).unwrap(); + } + "macos" => { + println!("cargo:rustc-link-arg=-ObjC"); + } + _ => { + panic!("Unsupported target, {}", target_os); + } } } diff --git a/livekit-ffi/src/livekit.proto.rs b/livekit-ffi/src/livekit.proto.rs index d5b75fede..077dc4aeb 100644 --- a/livekit-ffi/src/livekit.proto.rs +++ b/livekit-ffi/src/livekit.proto.rs @@ -1,6 +1,5 @@ // @generated // This file is @generated by prost-build. -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FrameCryptor { #[prost(string, required, tag="1")] @@ -12,7 +11,6 @@ pub struct FrameCryptor { #[prost(bool, required, tag="4")] pub enabled: bool, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct KeyProviderOptions { /// Only specify if you want to use a shared_key @@ -26,7 +24,6 @@ pub struct KeyProviderOptions { #[prost(int32, required, tag="4")] pub failure_tolerance: i32, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct E2eeOptions { #[prost(enumeration="EncryptionType", required, tag="1")] @@ -34,27 +31,22 @@ pub struct E2eeOptions { #[prost(message, required, tag="2")] pub key_provider_options: KeyProviderOptions, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct E2eeManagerSetEnabledRequest { #[prost(bool, required, tag="1")] pub enabled: bool, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct E2eeManagerSetEnabledResponse { } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct E2eeManagerGetFrameCryptorsRequest { } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct E2eeManagerGetFrameCryptorsResponse { #[prost(message, repeated, tag="1")] pub frame_cryptors: ::prost::alloc::vec::Vec, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FrameCryptorSetEnabledRequest { #[prost(string, required, tag="1")] @@ -64,11 +56,9 @@ pub struct FrameCryptorSetEnabledRequest { #[prost(bool, required, tag="3")] pub enabled: bool, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct FrameCryptorSetEnabledResponse { } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FrameCryptorSetKeyIndexRequest { #[prost(string, required, tag="1")] @@ -78,11 +68,9 @@ pub struct FrameCryptorSetKeyIndexRequest { #[prost(int32, required, tag="3")] pub key_index: i32, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct FrameCryptorSetKeyIndexResponse { } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SetSharedKeyRequest { #[prost(bytes="vec", required, tag="1")] @@ -90,35 +78,29 @@ pub struct SetSharedKeyRequest { #[prost(int32, required, tag="2")] pub key_index: i32, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct SetSharedKeyResponse { } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct RatchetSharedKeyRequest { #[prost(int32, required, tag="1")] pub key_index: i32, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RatchetSharedKeyResponse { #[prost(bytes="vec", optional, tag="1")] pub new_key: ::core::option::Option<::prost::alloc::vec::Vec>, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct GetSharedKeyRequest { #[prost(int32, required, tag="1")] pub key_index: i32, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetSharedKeyResponse { #[prost(bytes="vec", optional, tag="1")] pub key: ::core::option::Option<::prost::alloc::vec::Vec>, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SetKeyRequest { #[prost(string, required, tag="1")] @@ -128,11 +110,9 @@ pub struct SetKeyRequest { #[prost(int32, required, tag="3")] pub key_index: i32, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct SetKeyResponse { } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RatchetKeyRequest { #[prost(string, required, tag="1")] @@ -140,13 +120,11 @@ pub struct RatchetKeyRequest { #[prost(int32, required, tag="2")] pub key_index: i32, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RatchetKeyResponse { #[prost(bytes="vec", optional, tag="1")] pub new_key: ::core::option::Option<::prost::alloc::vec::Vec>, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetKeyRequest { #[prost(string, required, tag="1")] @@ -154,13 +132,11 @@ pub struct GetKeyRequest { #[prost(int32, required, tag="2")] pub key_index: i32, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetKeyResponse { #[prost(bytes="vec", optional, tag="1")] pub key: ::core::option::Option<::prost::alloc::vec::Vec>, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct E2eeRequest { #[prost(uint64, required, tag="1")] @@ -170,8 +146,7 @@ pub struct E2eeRequest { } /// Nested message and enum types in `E2eeRequest`. pub mod e2ee_request { - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Oneof)] + #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Message { #[prost(message, tag="2")] ManagerSetEnabled(super::E2eeManagerSetEnabledRequest), @@ -195,7 +170,6 @@ pub mod e2ee_request { GetKey(super::GetKeyRequest), } } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct E2eeResponse { #[prost(oneof="e2ee_response::Message", tags="1, 2, 3, 4, 5, 6, 7, 8, 9, 10")] @@ -203,8 +177,7 @@ pub struct E2eeResponse { } /// Nested message and enum types in `E2eeResponse`. pub mod e2ee_response { - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Oneof)] + #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Message { #[prost(message, tag="1")] ManagerSetEnabled(super::E2eeManagerSetEnabledResponse), @@ -244,9 +217,9 @@ impl EncryptionType { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - EncryptionType::None => "NONE", - EncryptionType::Gcm => "GCM", - EncryptionType::Custom => "CUSTOM", + Self::None => "NONE", + Self::Gcm => "GCM", + Self::Custom => "CUSTOM", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -277,13 +250,13 @@ impl EncryptionState { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - EncryptionState::New => "NEW", - EncryptionState::Ok => "OK", - EncryptionState::EncryptionFailed => "ENCRYPTION_FAILED", - EncryptionState::DecryptionFailed => "DECRYPTION_FAILED", - EncryptionState::MissingKey => "MISSING_KEY", - EncryptionState::KeyRatcheted => "KEY_RATCHETED", - EncryptionState::InternalError => "INTERNAL_ERROR", + Self::New => "NEW", + Self::Ok => "OK", + Self::EncryptionFailed => "ENCRYPTION_FAILED", + Self::DecryptionFailed => "DECRYPTION_FAILED", + Self::MissingKey => "MISSING_KEY", + Self::KeyRatcheted => "KEY_RATCHETED", + Self::InternalError => "INTERNAL_ERROR", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -309,30 +282,26 @@ impl EncryptionState { /// /// When refering to a handle without owning it, we just use a uint32 without this message. /// (the variable name is suffixed with "_handle") -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct FfiOwnedHandle { #[prost(uint64, required, tag="1")] pub id: u64, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RtcStats { - #[prost(oneof="rtc_stats::Stats", tags="3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17")] + #[prost(oneof="rtc_stats::Stats", tags="3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18")] pub stats: ::core::option::Option, } /// Nested message and enum types in `RtcStats`. pub mod rtc_stats { - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] + #[derive(Clone, PartialEq, ::prost::Message)] pub struct Codec { #[prost(message, required, tag="1")] pub rtc: super::RtcStatsData, #[prost(message, required, tag="2")] pub codec: super::CodecStats, } - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] + #[derive(Clone, PartialEq, ::prost::Message)] pub struct InboundRtp { #[prost(message, required, tag="1")] pub rtc: super::RtcStatsData, @@ -343,8 +312,7 @@ pub mod rtc_stats { #[prost(message, required, tag="4")] pub inbound: super::InboundRtpStreamStats, } - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] + #[derive(Clone, PartialEq, ::prost::Message)] pub struct OutboundRtp { #[prost(message, required, tag="1")] pub rtc: super::RtcStatsData, @@ -355,8 +323,7 @@ pub mod rtc_stats { #[prost(message, required, tag="4")] pub outbound: super::OutboundRtpStreamStats, } - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] + #[derive(Clone, PartialEq, ::prost::Message)] pub struct RemoteInboundRtp { #[prost(message, required, tag="1")] pub rtc: super::RtcStatsData, @@ -367,8 +334,7 @@ pub mod rtc_stats { #[prost(message, required, tag="4")] pub remote_inbound: super::RemoteInboundRtpStreamStats, } - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] + #[derive(Clone, PartialEq, ::prost::Message)] pub struct RemoteOutboundRtp { #[prost(message, required, tag="1")] pub rtc: super::RtcStatsData, @@ -379,8 +345,7 @@ pub mod rtc_stats { #[prost(message, required, tag="4")] pub remote_outbound: super::RemoteOutboundRtpStreamStats, } - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] + #[derive(Clone, PartialEq, ::prost::Message)] pub struct MediaSource { #[prost(message, required, tag="1")] pub rtc: super::RtcStatsData, @@ -391,77 +356,74 @@ pub mod rtc_stats { #[prost(message, required, tag="4")] pub video: super::VideoSourceStats, } - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] + #[derive(Clone, PartialEq, ::prost::Message)] pub struct MediaPlayout { #[prost(message, required, tag="1")] pub rtc: super::RtcStatsData, #[prost(message, required, tag="2")] pub audio_playout: super::AudioPlayoutStats, } - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] + #[derive(Clone, PartialEq, ::prost::Message)] pub struct PeerConnection { #[prost(message, required, tag="1")] pub rtc: super::RtcStatsData, #[prost(message, required, tag="2")] pub pc: super::PeerConnectionStats, } - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] + #[derive(Clone, PartialEq, ::prost::Message)] pub struct DataChannel { #[prost(message, required, tag="1")] pub rtc: super::RtcStatsData, #[prost(message, required, tag="2")] pub dc: super::DataChannelStats, } - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] + #[derive(Clone, PartialEq, ::prost::Message)] pub struct Transport { #[prost(message, required, tag="1")] pub rtc: super::RtcStatsData, #[prost(message, required, tag="2")] pub transport: super::TransportStats, } - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] + #[derive(Clone, PartialEq, ::prost::Message)] pub struct CandidatePair { #[prost(message, required, tag="1")] pub rtc: super::RtcStatsData, #[prost(message, required, tag="2")] pub candidate_pair: super::CandidatePairStats, } - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] + #[derive(Clone, PartialEq, ::prost::Message)] pub struct LocalCandidate { #[prost(message, required, tag="1")] pub rtc: super::RtcStatsData, #[prost(message, required, tag="2")] pub candidate: super::IceCandidateStats, } - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] + #[derive(Clone, PartialEq, ::prost::Message)] pub struct RemoteCandidate { #[prost(message, required, tag="1")] pub rtc: super::RtcStatsData, #[prost(message, required, tag="2")] pub candidate: super::IceCandidateStats, } - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] + #[derive(Clone, PartialEq, ::prost::Message)] pub struct Certificate { #[prost(message, required, tag="1")] pub rtc: super::RtcStatsData, #[prost(message, required, tag="2")] pub certificate: super::CertificateStats, } + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Stream { + #[prost(message, required, tag="1")] + pub rtc: super::RtcStatsData, + #[prost(message, required, tag="2")] + pub stream: super::StreamStats, + } /// Deprecated - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] + #[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct Track { } - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Oneof)] + #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Stats { #[prost(message, tag="3")] Codec(Codec), @@ -492,10 +454,11 @@ pub mod rtc_stats { #[prost(message, tag="16")] Certificate(Certificate), #[prost(message, tag="17")] + Stream(Stream), + #[prost(message, tag="18")] Track(Track), } } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RtcStatsData { #[prost(string, required, tag="1")] @@ -503,7 +466,6 @@ pub struct RtcStatsData { #[prost(int64, required, tag="2")] pub timestamp: i64, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CodecStats { #[prost(uint32, required, tag="1")] @@ -519,7 +481,6 @@ pub struct CodecStats { #[prost(string, required, tag="6")] pub sdp_fmtp_line: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RtpStreamStats { #[prost(uint32, required, tag="1")] @@ -531,8 +492,7 @@ pub struct RtpStreamStats { #[prost(string, required, tag="4")] pub codec_id: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct ReceivedRtpStreamStats { #[prost(uint64, required, tag="1")] pub packets_received: u64, @@ -541,7 +501,6 @@ pub struct ReceivedRtpStreamStats { #[prost(double, required, tag="3")] pub jitter: f64, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct InboundRtpStreamStats { #[prost(string, required, tag="1")] @@ -651,15 +610,13 @@ pub struct InboundRtpStreamStats { #[prost(uint32, required, tag="53")] pub fec_ssrc: u32, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct SentRtpStreamStats { #[prost(uint64, required, tag="1")] pub packets_sent: u64, #[prost(uint64, required, tag="2")] pub bytes_sent: u64, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct OutboundRtpStreamStats { #[prost(string, required, tag="1")] @@ -723,7 +680,6 @@ pub struct OutboundRtpStreamStats { #[prost(string, required, tag="30")] pub scalability_mode: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RemoteInboundRtpStreamStats { #[prost(string, required, tag="1")] @@ -737,7 +693,6 @@ pub struct RemoteInboundRtpStreamStats { #[prost(uint64, required, tag="5")] pub round_trip_time_measurements: u64, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RemoteOutboundRtpStreamStats { #[prost(string, required, tag="1")] @@ -753,7 +708,6 @@ pub struct RemoteOutboundRtpStreamStats { #[prost(uint64, required, tag="6")] pub round_trip_time_measurements: u64, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct MediaSourceStats { #[prost(string, required, tag="1")] @@ -761,8 +715,7 @@ pub struct MediaSourceStats { #[prost(string, required, tag="2")] pub kind: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct AudioSourceStats { #[prost(double, required, tag="1")] pub audio_level: f64, @@ -783,8 +736,7 @@ pub struct AudioSourceStats { #[prost(uint64, required, tag="9")] pub total_samples_captured: u64, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct VideoSourceStats { #[prost(uint32, required, tag="1")] pub width: u32, @@ -795,7 +747,6 @@ pub struct VideoSourceStats { #[prost(double, required, tag="4")] pub frames_per_second: f64, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct AudioPlayoutStats { #[prost(string, required, tag="1")] @@ -811,15 +762,13 @@ pub struct AudioPlayoutStats { #[prost(uint64, required, tag="6")] pub total_samples_count: u64, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct PeerConnectionStats { #[prost(uint32, required, tag="1")] pub data_channels_opened: u32, #[prost(uint32, required, tag="2")] pub data_channels_closed: u32, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct DataChannelStats { #[prost(string, required, tag="1")] @@ -839,7 +788,6 @@ pub struct DataChannelStats { #[prost(uint64, required, tag="8")] pub bytes_received: u64, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TransportStats { #[prost(uint64, required, tag="1")] @@ -875,7 +823,6 @@ pub struct TransportStats { #[prost(uint32, required, tag="16")] pub selected_candidate_pair_changes: u32, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CandidatePairStats { #[prost(string, required, tag="1")] @@ -923,7 +870,6 @@ pub struct CandidatePairStats { #[prost(uint64, required, tag="22")] pub bytes_discarded_on_send: u64, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct IceCandidateStats { #[prost(string, required, tag="1")] @@ -953,7 +899,6 @@ pub struct IceCandidateStats { #[prost(enumeration="IceTcpCandidateType", optional, tag="13")] pub tcp_type: ::core::option::Option, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CertificateStats { #[prost(string, required, tag="1")] @@ -965,6 +910,14 @@ pub struct CertificateStats { #[prost(string, required, tag="4")] pub issuer_certificate_id: ::prost::alloc::string::String, } +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct StreamStats { + #[prost(string, required, tag="1")] + pub id: ::prost::alloc::string::String, + /// required int64 timestamp = 3; + #[prost(string, required, tag="2")] + pub stream_identifier: ::prost::alloc::string::String, +} #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] pub enum DataChannelState { @@ -980,10 +933,10 @@ impl DataChannelState { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - DataChannelState::DcConnecting => "DC_CONNECTING", - DataChannelState::DcOpen => "DC_OPEN", - DataChannelState::DcClosing => "DC_CLOSING", - DataChannelState::DcClosed => "DC_CLOSED", + Self::DcConnecting => "DC_CONNECTING", + Self::DcOpen => "DC_OPEN", + Self::DcClosing => "DC_CLOSING", + Self::DcClosed => "DC_CLOSED", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1012,10 +965,10 @@ impl QualityLimitationReason { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - QualityLimitationReason::LimitationNone => "LIMITATION_NONE", - QualityLimitationReason::LimitationCpu => "LIMITATION_CPU", - QualityLimitationReason::LimitationBandwidth => "LIMITATION_BANDWIDTH", - QualityLimitationReason::LimitationOther => "LIMITATION_OTHER", + Self::LimitationNone => "LIMITATION_NONE", + Self::LimitationCpu => "LIMITATION_CPU", + Self::LimitationBandwidth => "LIMITATION_BANDWIDTH", + Self::LimitationOther => "LIMITATION_OTHER", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1043,9 +996,9 @@ impl IceRole { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - IceRole::IceUnknown => "ICE_UNKNOWN", - IceRole::IceControlling => "ICE_CONTROLLING", - IceRole::IceControlled => "ICE_CONTROLLED", + Self::IceUnknown => "ICE_UNKNOWN", + Self::IceControlling => "ICE_CONTROLLING", + Self::IceControlled => "ICE_CONTROLLED", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1074,11 +1027,11 @@ impl DtlsTransportState { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - DtlsTransportState::DtlsTransportNew => "DTLS_TRANSPORT_NEW", - DtlsTransportState::DtlsTransportConnecting => "DTLS_TRANSPORT_CONNECTING", - DtlsTransportState::DtlsTransportConnected => "DTLS_TRANSPORT_CONNECTED", - DtlsTransportState::DtlsTransportClosed => "DTLS_TRANSPORT_CLOSED", - DtlsTransportState::DtlsTransportFailed => "DTLS_TRANSPORT_FAILED", + Self::DtlsTransportNew => "DTLS_TRANSPORT_NEW", + Self::DtlsTransportConnecting => "DTLS_TRANSPORT_CONNECTING", + Self::DtlsTransportConnected => "DTLS_TRANSPORT_CONNECTED", + Self::DtlsTransportClosed => "DTLS_TRANSPORT_CLOSED", + Self::DtlsTransportFailed => "DTLS_TRANSPORT_FAILED", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1111,13 +1064,13 @@ impl IceTransportState { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - IceTransportState::IceTransportNew => "ICE_TRANSPORT_NEW", - IceTransportState::IceTransportChecking => "ICE_TRANSPORT_CHECKING", - IceTransportState::IceTransportConnected => "ICE_TRANSPORT_CONNECTED", - IceTransportState::IceTransportCompleted => "ICE_TRANSPORT_COMPLETED", - IceTransportState::IceTransportDisconnected => "ICE_TRANSPORT_DISCONNECTED", - IceTransportState::IceTransportFailed => "ICE_TRANSPORT_FAILED", - IceTransportState::IceTransportClosed => "ICE_TRANSPORT_CLOSED", + Self::IceTransportNew => "ICE_TRANSPORT_NEW", + Self::IceTransportChecking => "ICE_TRANSPORT_CHECKING", + Self::IceTransportConnected => "ICE_TRANSPORT_CONNECTED", + Self::IceTransportCompleted => "ICE_TRANSPORT_COMPLETED", + Self::IceTransportDisconnected => "ICE_TRANSPORT_DISCONNECTED", + Self::IceTransportFailed => "ICE_TRANSPORT_FAILED", + Self::IceTransportClosed => "ICE_TRANSPORT_CLOSED", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1148,9 +1101,9 @@ impl DtlsRole { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - DtlsRole::DtlsClient => "DTLS_CLIENT", - DtlsRole::DtlsServer => "DTLS_SERVER", - DtlsRole::DtlsUnknown => "DTLS_UNKNOWN", + Self::DtlsClient => "DTLS_CLIENT", + Self::DtlsServer => "DTLS_SERVER", + Self::DtlsUnknown => "DTLS_UNKNOWN", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1179,11 +1132,11 @@ impl IceCandidatePairState { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - IceCandidatePairState::PairFrozen => "PAIR_FROZEN", - IceCandidatePairState::PairWaiting => "PAIR_WAITING", - IceCandidatePairState::PairInProgress => "PAIR_IN_PROGRESS", - IceCandidatePairState::PairFailed => "PAIR_FAILED", - IceCandidatePairState::PairSucceeded => "PAIR_SUCCEEDED", + Self::PairFrozen => "PAIR_FROZEN", + Self::PairWaiting => "PAIR_WAITING", + Self::PairInProgress => "PAIR_IN_PROGRESS", + Self::PairFailed => "PAIR_FAILED", + Self::PairSucceeded => "PAIR_SUCCEEDED", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1213,10 +1166,10 @@ impl IceCandidateType { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - IceCandidateType::Host => "HOST", - IceCandidateType::Srflx => "SRFLX", - IceCandidateType::Prflx => "PRFLX", - IceCandidateType::Relay => "RELAY", + Self::Host => "HOST", + Self::Srflx => "SRFLX", + Self::Prflx => "PRFLX", + Self::Relay => "RELAY", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1244,9 +1197,9 @@ impl IceServerTransportProtocol { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - IceServerTransportProtocol::TransportUdp => "TRANSPORT_UDP", - IceServerTransportProtocol::TransportTcp => "TRANSPORT_TCP", - IceServerTransportProtocol::TransportTls => "TRANSPORT_TLS", + Self::TransportUdp => "TRANSPORT_UDP", + Self::TransportTcp => "TRANSPORT_TCP", + Self::TransportTls => "TRANSPORT_TLS", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1273,9 +1226,9 @@ impl IceTcpCandidateType { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - IceTcpCandidateType::CandidateActive => "CANDIDATE_ACTIVE", - IceTcpCandidateType::CandidatePassive => "CANDIDATE_PASSIVE", - IceTcpCandidateType::CandidateSo => "CANDIDATE_SO", + Self::CandidateActive => "CANDIDATE_ACTIVE", + Self::CandidatePassive => "CANDIDATE_PASSIVE", + Self::CandidateSo => "CANDIDATE_SO", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1289,7 +1242,6 @@ impl IceTcpCandidateType { } } /// Create a new VideoTrack from a VideoSource -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CreateVideoTrackRequest { #[prost(string, required, tag="1")] @@ -1297,14 +1249,12 @@ pub struct CreateVideoTrackRequest { #[prost(uint64, required, tag="2")] pub source_handle: u64, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CreateVideoTrackResponse { #[prost(message, required, tag="1")] pub track: OwnedTrack, } /// Create a new AudioTrack from a AudioSource -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CreateAudioTrackRequest { #[prost(string, required, tag="1")] @@ -1312,25 +1262,21 @@ pub struct CreateAudioTrackRequest { #[prost(uint64, required, tag="2")] pub source_handle: u64, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CreateAudioTrackResponse { #[prost(message, required, tag="1")] pub track: OwnedTrack, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct GetStatsRequest { #[prost(uint64, required, tag="1")] pub track_handle: u64, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct GetStatsResponse { #[prost(uint64, required, tag="1")] pub async_id: u64, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetStatsCallback { #[prost(uint64, required, tag="1")] @@ -1344,11 +1290,9 @@ pub struct GetStatsCallback { // Track // -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct TrackEvent { } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackPublicationInfo { #[prost(string, required, tag="1")] @@ -1374,7 +1318,6 @@ pub struct TrackPublicationInfo { #[prost(enumeration="EncryptionType", required, tag="11")] pub encryption_type: i32, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct OwnedTrackPublication { #[prost(message, required, tag="1")] @@ -1382,7 +1325,6 @@ pub struct OwnedTrackPublication { #[prost(message, required, tag="2")] pub info: TrackPublicationInfo, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackInfo { #[prost(string, required, tag="1")] @@ -1398,7 +1340,6 @@ pub struct TrackInfo { #[prost(bool, required, tag="6")] pub remote: bool, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct OwnedTrack { #[prost(message, required, tag="1")] @@ -1407,31 +1348,27 @@ pub struct OwnedTrack { pub info: TrackInfo, } /// Mute/UnMute a track -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct LocalTrackMuteRequest { #[prost(uint64, required, tag="1")] pub track_handle: u64, #[prost(bool, required, tag="2")] pub mute: bool, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct LocalTrackMuteResponse { #[prost(bool, required, tag="1")] pub muted: bool, } /// Enable/Disable a remote track -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct EnableRemoteTrackRequest { #[prost(uint64, required, tag="1")] pub track_handle: u64, #[prost(bool, required, tag="2")] pub enabled: bool, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct EnableRemoteTrackResponse { #[prost(bool, required, tag="1")] pub enabled: bool, @@ -1450,9 +1387,9 @@ impl TrackKind { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - TrackKind::KindUnknown => "KIND_UNKNOWN", - TrackKind::KindAudio => "KIND_AUDIO", - TrackKind::KindVideo => "KIND_VIDEO", + Self::KindUnknown => "KIND_UNKNOWN", + Self::KindAudio => "KIND_AUDIO", + Self::KindVideo => "KIND_VIDEO", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1481,11 +1418,11 @@ impl TrackSource { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - TrackSource::SourceUnknown => "SOURCE_UNKNOWN", - TrackSource::SourceCamera => "SOURCE_CAMERA", - TrackSource::SourceMicrophone => "SOURCE_MICROPHONE", - TrackSource::SourceScreenshare => "SOURCE_SCREENSHARE", - TrackSource::SourceScreenshareAudio => "SOURCE_SCREENSHARE_AUDIO", + Self::SourceUnknown => "SOURCE_UNKNOWN", + Self::SourceCamera => "SOURCE_CAMERA", + Self::SourceMicrophone => "SOURCE_MICROPHONE", + Self::SourceScreenshare => "SOURCE_SCREENSHARE", + Self::SourceScreenshareAudio => "SOURCE_SCREENSHARE_AUDIO", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1514,9 +1451,9 @@ impl StreamState { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - StreamState::StateUnknown => "STATE_UNKNOWN", - StreamState::StateActive => "STATE_ACTIVE", - StreamState::StatePaused => "STATE_PAUSED", + Self::StateUnknown => "STATE_UNKNOWN", + Self::StateActive => "STATE_ACTIVE", + Self::StatePaused => "STATE_PAUSED", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1530,21 +1467,18 @@ impl StreamState { } } /// Enable/Disable a remote track publication -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct EnableRemoteTrackPublicationRequest { #[prost(uint64, required, tag="1")] pub track_publication_handle: u64, #[prost(bool, required, tag="2")] pub enabled: bool, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct EnableRemoteTrackPublicationResponse { } /// update a remote track publication dimension -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct UpdateRemoteTrackPublicationDimensionRequest { #[prost(uint64, required, tag="1")] pub track_publication_handle: u64, @@ -1553,11 +1487,9 @@ pub struct UpdateRemoteTrackPublicationDimensionRequest { #[prost(uint32, required, tag="3")] pub height: u32, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct UpdateRemoteTrackPublicationDimensionResponse { } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ParticipantInfo { #[prost(string, required, tag="1")] @@ -1573,7 +1505,6 @@ pub struct ParticipantInfo { #[prost(enumeration="ParticipantKind", required, tag="6")] pub kind: i32, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct OwnedParticipant { #[prost(message, required, tag="1")] @@ -1597,11 +1528,11 @@ impl ParticipantKind { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - ParticipantKind::Standard => "PARTICIPANT_KIND_STANDARD", - ParticipantKind::Ingress => "PARTICIPANT_KIND_INGRESS", - ParticipantKind::Egress => "PARTICIPANT_KIND_EGRESS", - ParticipantKind::Sip => "PARTICIPANT_KIND_SIP", - ParticipantKind::Agent => "PARTICIPANT_KIND_AGENT", + Self::Standard => "PARTICIPANT_KIND_STANDARD", + Self::Ingress => "PARTICIPANT_KIND_INGRESS", + Self::Egress => "PARTICIPANT_KIND_EGRESS", + Self::Sip => "PARTICIPANT_KIND_SIP", + Self::Agent => "PARTICIPANT_KIND_AGENT", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1618,8 +1549,7 @@ impl ParticipantKind { } /// Create a new VideoStream /// VideoStream is used to receive video frames from a track -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct NewVideoStreamRequest { #[prost(uint64, required, tag="1")] pub track_handle: u64, @@ -1632,15 +1562,13 @@ pub struct NewVideoStreamRequest { #[prost(bool, optional, tag="4")] pub normalize_stride: ::core::option::Option, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct NewVideoStreamResponse { #[prost(message, required, tag="1")] pub stream: OwnedVideoStream, } /// Request a video stream from a participant -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct VideoStreamFromParticipantRequest { #[prost(uint64, required, tag="1")] pub participant_handle: u64, @@ -1653,16 +1581,14 @@ pub struct VideoStreamFromParticipantRequest { #[prost(bool, optional, tag="5")] pub normalize_stride: ::core::option::Option, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct VideoStreamFromParticipantResponse { #[prost(message, required, tag="1")] pub stream: OwnedVideoStream, } /// Create a new VideoSource /// VideoSource is used to send video frame to a track -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct NewVideoSourceRequest { #[prost(enumeration="VideoSourceType", required, tag="1")] pub r#type: i32, @@ -1671,14 +1597,12 @@ pub struct NewVideoSourceRequest { #[prost(message, required, tag="2")] pub resolution: VideoSourceResolution, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct NewVideoSourceResponse { #[prost(message, required, tag="1")] pub source: OwnedVideoSource, } /// Push a frame to a VideoSource -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CaptureVideoFrameRequest { #[prost(uint64, required, tag="1")] @@ -1691,11 +1615,9 @@ pub struct CaptureVideoFrameRequest { #[prost(enumeration="VideoRotation", required, tag="4")] pub rotation: i32, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct CaptureVideoFrameResponse { } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct VideoConvertRequest { #[prost(bool, optional, tag="1")] @@ -1705,7 +1627,6 @@ pub struct VideoConvertRequest { #[prost(enumeration="VideoBufferType", required, tag="3")] pub dst_type: i32, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct VideoConvertResponse { #[prost(oneof="video_convert_response::Message", tags="1, 2")] @@ -1713,8 +1634,7 @@ pub struct VideoConvertResponse { } /// Nested message and enum types in `VideoConvertResponse`. pub mod video_convert_response { - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Oneof)] + #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Message { #[prost(string, tag="1")] Error(::prost::alloc::string::String), @@ -1726,8 +1646,7 @@ pub mod video_convert_response { // VideoFrame buffers // -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct VideoResolution { #[prost(uint32, required, tag="1")] pub width: u32, @@ -1736,7 +1655,6 @@ pub struct VideoResolution { #[prost(double, required, tag="3")] pub frame_rate: f64, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct VideoBufferInfo { #[prost(enumeration="VideoBufferType", required, tag="1")] @@ -1755,8 +1673,7 @@ pub struct VideoBufferInfo { } /// Nested message and enum types in `VideoBufferInfo`. pub mod video_buffer_info { - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] + #[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct ComponentInfo { #[prost(uint64, required, tag="1")] pub data_ptr: u64, @@ -1766,7 +1683,6 @@ pub mod video_buffer_info { pub size: u32, } } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct OwnedVideoBuffer { #[prost(message, required, tag="1")] @@ -1774,21 +1690,18 @@ pub struct OwnedVideoBuffer { #[prost(message, required, tag="2")] pub info: VideoBufferInfo, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct VideoStreamInfo { #[prost(enumeration="VideoStreamType", required, tag="1")] pub r#type: i32, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct OwnedVideoStream { #[prost(message, required, tag="1")] pub handle: FfiOwnedHandle, #[prost(message, required, tag="2")] pub info: VideoStreamInfo, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct VideoStreamEvent { #[prost(uint64, required, tag="1")] @@ -1798,8 +1711,7 @@ pub struct VideoStreamEvent { } /// Nested message and enum types in `VideoStreamEvent`. pub mod video_stream_event { - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Oneof)] + #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Message { #[prost(message, tag="2")] FrameReceived(super::VideoFrameReceived), @@ -1807,7 +1719,6 @@ pub mod video_stream_event { Eos(super::VideoStreamEos), } } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct VideoFrameReceived { #[prost(message, required, tag="1")] @@ -1818,30 +1729,26 @@ pub struct VideoFrameReceived { #[prost(enumeration="VideoRotation", required, tag="3")] pub rotation: i32, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct VideoStreamEos { } // // VideoSource // -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct VideoSourceResolution { #[prost(uint32, required, tag="1")] pub width: u32, #[prost(uint32, required, tag="2")] pub height: u32, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct VideoSourceInfo { #[prost(enumeration="VideoSourceType", required, tag="1")] pub r#type: i32, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct OwnedVideoSource { #[prost(message, required, tag="1")] pub handle: FfiOwnedHandle, @@ -1863,10 +1770,10 @@ impl VideoCodec { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - VideoCodec::Vp8 => "VP8", - VideoCodec::H264 => "H264", - VideoCodec::Av1 => "AV1", - VideoCodec::Vp9 => "VP9", + Self::Vp8 => "VP8", + Self::H264 => "H264", + Self::Av1 => "AV1", + Self::Vp9 => "VP9", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1895,10 +1802,10 @@ impl VideoRotation { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - VideoRotation::VideoRotation0 => "VIDEO_ROTATION_0", - VideoRotation::VideoRotation90 => "VIDEO_ROTATION_90", - VideoRotation::VideoRotation180 => "VIDEO_ROTATION_180", - VideoRotation::VideoRotation270 => "VIDEO_ROTATION_270", + Self::VideoRotation0 => "VIDEO_ROTATION_0", + Self::VideoRotation90 => "VIDEO_ROTATION_90", + Self::VideoRotation180 => "VIDEO_ROTATION_180", + Self::VideoRotation270 => "VIDEO_ROTATION_270", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1934,17 +1841,17 @@ impl VideoBufferType { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - VideoBufferType::Rgba => "RGBA", - VideoBufferType::Abgr => "ABGR", - VideoBufferType::Argb => "ARGB", - VideoBufferType::Bgra => "BGRA", - VideoBufferType::Rgb24 => "RGB24", - VideoBufferType::I420 => "I420", - VideoBufferType::I420a => "I420A", - VideoBufferType::I422 => "I422", - VideoBufferType::I444 => "I444", - VideoBufferType::I010 => "I010", - VideoBufferType::Nv12 => "NV12", + Self::Rgba => "RGBA", + Self::Abgr => "ABGR", + Self::Argb => "ARGB", + Self::Bgra => "BGRA", + Self::Rgb24 => "RGB24", + Self::I420 => "I420", + Self::I420a => "I420A", + Self::I422 => "I422", + Self::I444 => "I444", + Self::I010 => "I010", + Self::Nv12 => "NV12", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1983,9 +1890,9 @@ impl VideoStreamType { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - VideoStreamType::VideoStreamNative => "VIDEO_STREAM_NATIVE", - VideoStreamType::VideoStreamWebgl => "VIDEO_STREAM_WEBGL", - VideoStreamType::VideoStreamHtml => "VIDEO_STREAM_HTML", + Self::VideoStreamNative => "VIDEO_STREAM_NATIVE", + Self::VideoStreamWebgl => "VIDEO_STREAM_WEBGL", + Self::VideoStreamHtml => "VIDEO_STREAM_HTML", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -2010,7 +1917,7 @@ impl VideoSourceType { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - VideoSourceType::VideoSourceNative => "VIDEO_SOURCE_NATIVE", + Self::VideoSourceNative => "VIDEO_SOURCE_NATIVE", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -2022,7 +1929,6 @@ impl VideoSourceType { } } /// Connect to a new LiveKit room -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ConnectRequest { #[prost(string, required, tag="1")] @@ -2032,13 +1938,11 @@ pub struct ConnectRequest { #[prost(message, required, tag="3")] pub options: RoomOptions, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct ConnectResponse { #[prost(uint64, required, tag="1")] pub async_id: u64, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ConnectCallback { #[prost(uint64, required, tag="1")] @@ -2048,8 +1952,7 @@ pub struct ConnectCallback { } /// Nested message and enum types in `ConnectCallback`. pub mod connect_callback { - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] + #[derive(Clone, PartialEq, ::prost::Message)] pub struct ParticipantWithTracks { #[prost(message, required, tag="1")] pub participant: super::OwnedParticipant, @@ -2058,8 +1961,7 @@ pub mod connect_callback { #[prost(message, repeated, tag="2")] pub publications: ::prost::alloc::vec::Vec, } - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] + #[derive(Clone, PartialEq, ::prost::Message)] pub struct Result { #[prost(message, required, tag="1")] pub room: super::OwnedRoom, @@ -2068,8 +1970,7 @@ pub mod connect_callback { #[prost(message, repeated, tag="3")] pub participants: ::prost::alloc::vec::Vec, } - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Oneof)] + #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Message { #[prost(string, tag="2")] Error(::prost::alloc::string::String), @@ -2078,26 +1979,22 @@ pub mod connect_callback { } } /// Disconnect from the a room -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct DisconnectRequest { #[prost(uint64, required, tag="1")] pub room_handle: u64, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct DisconnectResponse { #[prost(uint64, required, tag="1")] pub async_id: u64, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct DisconnectCallback { #[prost(uint64, required, tag="1")] pub async_id: u64, } /// Publish a track to the room -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PublishTrackRequest { #[prost(uint64, required, tag="1")] @@ -2107,13 +2004,11 @@ pub struct PublishTrackRequest { #[prost(message, required, tag="3")] pub options: TrackPublishOptions, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct PublishTrackResponse { #[prost(uint64, required, tag="1")] pub async_id: u64, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PublishTrackCallback { #[prost(uint64, required, tag="1")] @@ -2123,8 +2018,7 @@ pub struct PublishTrackCallback { } /// Nested message and enum types in `PublishTrackCallback`. pub mod publish_track_callback { - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Oneof)] + #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Message { #[prost(string, tag="2")] Error(::prost::alloc::string::String), @@ -2133,7 +2027,6 @@ pub mod publish_track_callback { } } /// Unpublish a track from the room -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct UnpublishTrackRequest { #[prost(uint64, required, tag="1")] @@ -2143,13 +2036,11 @@ pub struct UnpublishTrackRequest { #[prost(bool, required, tag="3")] pub stop_on_unpublish: bool, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct UnpublishTrackResponse { #[prost(uint64, required, tag="1")] pub async_id: u64, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct UnpublishTrackCallback { #[prost(uint64, required, tag="1")] @@ -2158,7 +2049,6 @@ pub struct UnpublishTrackCallback { pub error: ::core::option::Option<::prost::alloc::string::String>, } /// Publish data to other participants -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PublishDataRequest { #[prost(uint64, required, tag="1")] @@ -2177,13 +2067,11 @@ pub struct PublishDataRequest { #[prost(string, repeated, tag="7")] pub destination_identities: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct PublishDataResponse { #[prost(uint64, required, tag="1")] pub async_id: u64, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PublishDataCallback { #[prost(uint64, required, tag="1")] @@ -2192,7 +2080,6 @@ pub struct PublishDataCallback { pub error: ::core::option::Option<::prost::alloc::string::String>, } /// Publish transcription messages to room -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PublishTranscriptionRequest { #[prost(uint64, required, tag="1")] @@ -2204,13 +2091,11 @@ pub struct PublishTranscriptionRequest { #[prost(message, repeated, tag="4")] pub segments: ::prost::alloc::vec::Vec, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct PublishTranscriptionResponse { #[prost(uint64, required, tag="1")] pub async_id: u64, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PublishTranscriptionCallback { #[prost(uint64, required, tag="1")] @@ -2219,7 +2104,6 @@ pub struct PublishTranscriptionCallback { pub error: ::core::option::Option<::prost::alloc::string::String>, } /// Publish Sip DTMF messages to other participants -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PublishSipDtmfRequest { #[prost(uint64, required, tag="1")] @@ -2231,13 +2115,11 @@ pub struct PublishSipDtmfRequest { #[prost(string, repeated, tag="4")] pub destination_identities: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct PublishSipDtmfResponse { #[prost(uint64, required, tag="1")] pub async_id: u64, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PublishSipDtmfCallback { #[prost(uint64, required, tag="1")] @@ -2246,7 +2128,6 @@ pub struct PublishSipDtmfCallback { pub error: ::core::option::Option<::prost::alloc::string::String>, } /// Change the local participant's metadata -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SetLocalMetadataRequest { #[prost(uint64, required, tag="1")] @@ -2254,13 +2135,11 @@ pub struct SetLocalMetadataRequest { #[prost(string, required, tag="2")] pub metadata: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct SetLocalMetadataResponse { #[prost(uint64, required, tag="1")] pub async_id: u64, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SetLocalMetadataCallback { #[prost(uint64, required, tag="1")] @@ -2268,7 +2147,6 @@ pub struct SetLocalMetadataCallback { #[prost(string, optional, tag="2")] pub error: ::core::option::Option<::prost::alloc::string::String>, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SendChatMessageRequest { #[prost(uint64, required, tag="1")] @@ -2280,7 +2158,6 @@ pub struct SendChatMessageRequest { #[prost(string, optional, tag="4")] pub sender_identity: ::core::option::Option<::prost::alloc::string::String>, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct EditChatMessageRequest { #[prost(uint64, required, tag="1")] @@ -2294,13 +2171,11 @@ pub struct EditChatMessageRequest { #[prost(string, optional, tag="5")] pub sender_identity: ::core::option::Option<::prost::alloc::string::String>, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct SendChatMessageResponse { #[prost(uint64, required, tag="1")] pub async_id: u64, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SendChatMessageCallback { #[prost(uint64, required, tag="1")] @@ -2310,8 +2185,7 @@ pub struct SendChatMessageCallback { } /// Nested message and enum types in `SendChatMessageCallback`. pub mod send_chat_message_callback { - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Oneof)] + #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Message { #[prost(string, tag="2")] Error(::prost::alloc::string::String), @@ -2320,7 +2194,6 @@ pub mod send_chat_message_callback { } } /// Change the local participant's attributes -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SetLocalAttributesRequest { #[prost(uint64, required, tag="1")] @@ -2328,7 +2201,6 @@ pub struct SetLocalAttributesRequest { #[prost(message, repeated, tag="2")] pub attributes: ::prost::alloc::vec::Vec, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct AttributesEntry { #[prost(string, required, tag="1")] @@ -2336,13 +2208,11 @@ pub struct AttributesEntry { #[prost(string, required, tag="2")] pub value: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct SetLocalAttributesResponse { #[prost(uint64, required, tag="1")] pub async_id: u64, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SetLocalAttributesCallback { #[prost(uint64, required, tag="1")] @@ -2351,7 +2221,6 @@ pub struct SetLocalAttributesCallback { pub error: ::core::option::Option<::prost::alloc::string::String>, } /// Change the local participant's name -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SetLocalNameRequest { #[prost(uint64, required, tag="1")] @@ -2359,13 +2228,11 @@ pub struct SetLocalNameRequest { #[prost(string, required, tag="2")] pub name: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct SetLocalNameResponse { #[prost(uint64, required, tag="1")] pub async_id: u64, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SetLocalNameCallback { #[prost(uint64, required, tag="1")] @@ -2374,31 +2241,26 @@ pub struct SetLocalNameCallback { pub error: ::core::option::Option<::prost::alloc::string::String>, } /// Change the "desire" to subs2ribe to a track -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct SetSubscribedRequest { #[prost(bool, required, tag="1")] pub subscribe: bool, #[prost(uint64, required, tag="2")] pub publication_handle: u64, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct SetSubscribedResponse { } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct GetSessionStatsRequest { #[prost(uint64, required, tag="1")] pub room_handle: u64, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct GetSessionStatsResponse { #[prost(uint64, required, tag="1")] pub async_id: u64, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetSessionStatsCallback { #[prost(uint64, required, tag="1")] @@ -2408,16 +2270,14 @@ pub struct GetSessionStatsCallback { } /// Nested message and enum types in `GetSessionStatsCallback`. pub mod get_session_stats_callback { - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] + #[derive(Clone, PartialEq, ::prost::Message)] pub struct Result { #[prost(message, repeated, tag="1")] pub publisher_stats: ::prost::alloc::vec::Vec, #[prost(message, repeated, tag="2")] pub subscriber_stats: ::prost::alloc::vec::Vec, } - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Oneof)] + #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Message { #[prost(string, tag="2")] Error(::prost::alloc::string::String), @@ -2429,21 +2289,18 @@ pub mod get_session_stats_callback { // Options // -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct VideoEncoding { #[prost(uint64, required, tag="1")] pub max_bitrate: u64, #[prost(double, required, tag="2")] pub max_framerate: f64, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct AudioEncoding { #[prost(uint64, required, tag="1")] pub max_bitrate: u64, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackPublishOptions { /// encodings are optional @@ -2464,7 +2321,6 @@ pub struct TrackPublishOptions { #[prost(string, optional, tag="8")] pub stream: ::core::option::Option<::prost::alloc::string::String>, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct IceServer { #[prost(string, repeated, tag="1")] @@ -2474,7 +2330,6 @@ pub struct IceServer { #[prost(string, optional, tag="3")] pub password: ::core::option::Option<::prost::alloc::string::String>, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RtcConfig { #[prost(enumeration="IceTransportType", optional, tag="1")] @@ -2485,7 +2340,6 @@ pub struct RtcConfig { #[prost(message, repeated, tag="3")] pub ice_servers: ::prost::alloc::vec::Vec, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RoomOptions { #[prost(bool, optional, tag="1")] @@ -2502,7 +2356,6 @@ pub struct RoomOptions { #[prost(uint32, optional, tag="6")] pub join_retries: ::core::option::Option, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TranscriptionSegment { #[prost(string, required, tag="1")] @@ -2518,23 +2371,20 @@ pub struct TranscriptionSegment { #[prost(string, required, tag="6")] pub language: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct BufferInfo { #[prost(uint64, required, tag="1")] pub data_ptr: u64, #[prost(uint64, required, tag="2")] pub data_len: u64, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct OwnedBuffer { #[prost(message, required, tag="1")] pub handle: FfiOwnedHandle, #[prost(message, required, tag="2")] pub data: BufferInfo, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RoomEvent { #[prost(uint64, required, tag="1")] @@ -2544,8 +2394,7 @@ pub struct RoomEvent { } /// Nested message and enum types in `RoomEvent`. pub mod room_event { - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Oneof)] + #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Message { #[prost(message, tag="2")] ParticipantConnected(super::ParticipantConnected), @@ -2607,7 +2456,6 @@ pub mod room_event { ChatMessage(super::ChatMessageReceived), } } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RoomInfo { #[prost(string, optional, tag="1")] @@ -2617,7 +2465,6 @@ pub struct RoomInfo { #[prost(string, required, tag="3")] pub metadata: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct OwnedRoom { #[prost(message, required, tag="1")] @@ -2625,19 +2472,16 @@ pub struct OwnedRoom { #[prost(message, required, tag="2")] pub info: RoomInfo, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ParticipantConnected { #[prost(message, required, tag="1")] pub info: OwnedParticipant, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ParticipantDisconnected { #[prost(string, required, tag="1")] pub participant_identity: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct LocalTrackPublished { /// The TrackPublicationInfo comes from the PublishTrack response @@ -2645,19 +2489,16 @@ pub struct LocalTrackPublished { #[prost(string, required, tag="1")] pub track_sid: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct LocalTrackUnpublished { #[prost(string, required, tag="1")] pub publication_sid: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct LocalTrackSubscribed { #[prost(string, required, tag="2")] pub track_sid: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackPublished { #[prost(string, required, tag="1")] @@ -2665,7 +2506,6 @@ pub struct TrackPublished { #[prost(message, required, tag="2")] pub publication: OwnedTrackPublication, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackUnpublished { #[prost(string, required, tag="1")] @@ -2675,7 +2515,6 @@ pub struct TrackUnpublished { } /// Publication isn't needed for subscription events on the FFI /// The FFI will retrieve the publication using the Track sid -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackSubscribed { #[prost(string, required, tag="1")] @@ -2683,7 +2522,6 @@ pub struct TrackSubscribed { #[prost(message, required, tag="2")] pub track: OwnedTrack, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackUnsubscribed { /// The FFI language can dispose/remove the VideoSink here @@ -2692,7 +2530,6 @@ pub struct TrackUnsubscribed { #[prost(string, required, tag="2")] pub track_sid: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackSubscriptionFailed { #[prost(string, required, tag="1")] @@ -2702,7 +2539,6 @@ pub struct TrackSubscriptionFailed { #[prost(string, required, tag="3")] pub error: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackMuted { #[prost(string, required, tag="1")] @@ -2710,7 +2546,6 @@ pub struct TrackMuted { #[prost(string, required, tag="2")] pub track_sid: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackUnmuted { #[prost(string, required, tag="1")] @@ -2718,7 +2553,6 @@ pub struct TrackUnmuted { #[prost(string, required, tag="2")] pub track_sid: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct E2eeStateChanged { /// Using sid instead of identity for ffi communication @@ -2727,25 +2561,21 @@ pub struct E2eeStateChanged { #[prost(enumeration="EncryptionState", required, tag="2")] pub state: i32, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ActiveSpeakersChanged { #[prost(string, repeated, tag="1")] pub participant_identities: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RoomMetadataChanged { #[prost(string, required, tag="1")] pub metadata: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RoomSidChanged { #[prost(string, required, tag="1")] pub sid: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ParticipantMetadataChanged { #[prost(string, required, tag="1")] @@ -2753,7 +2583,6 @@ pub struct ParticipantMetadataChanged { #[prost(string, required, tag="2")] pub metadata: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ParticipantAttributesChanged { #[prost(string, required, tag="1")] @@ -2763,7 +2592,6 @@ pub struct ParticipantAttributesChanged { #[prost(message, repeated, tag="3")] pub changed_attributes: ::prost::alloc::vec::Vec, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ParticipantNameChanged { #[prost(string, required, tag="1")] @@ -2771,7 +2599,6 @@ pub struct ParticipantNameChanged { #[prost(string, required, tag="2")] pub name: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ConnectionQualityChanged { #[prost(string, required, tag="1")] @@ -2779,7 +2606,6 @@ pub struct ConnectionQualityChanged { #[prost(enumeration="ConnectionQuality", required, tag="2")] pub quality: i32, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct UserPacket { #[prost(message, required, tag="1")] @@ -2787,7 +2613,6 @@ pub struct UserPacket { #[prost(string, optional, tag="2")] pub topic: ::core::option::Option<::prost::alloc::string::String>, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ChatMessage { #[prost(string, required, tag="1")] @@ -2803,7 +2628,6 @@ pub struct ChatMessage { #[prost(bool, optional, tag="6")] pub generated: ::core::option::Option, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ChatMessageReceived { #[prost(message, required, tag="1")] @@ -2811,7 +2635,6 @@ pub struct ChatMessageReceived { #[prost(string, required, tag="2")] pub participant_identity: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SipDtmf { #[prost(uint32, required, tag="1")] @@ -2819,7 +2642,6 @@ pub struct SipDtmf { #[prost(string, optional, tag="2")] pub digit: ::core::option::Option<::prost::alloc::string::String>, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct DataPacketReceived { #[prost(enumeration="DataPacketKind", required, tag="1")] @@ -2832,8 +2654,7 @@ pub struct DataPacketReceived { } /// Nested message and enum types in `DataPacketReceived`. pub mod data_packet_received { - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Oneof)] + #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Value { #[prost(message, tag="4")] User(super::UserPacket), @@ -2841,7 +2662,6 @@ pub mod data_packet_received { SipDtmf(super::SipDtmf), } } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TranscriptionReceived { #[prost(string, optional, tag="1")] @@ -2851,32 +2671,26 @@ pub struct TranscriptionReceived { #[prost(message, repeated, tag="3")] pub segments: ::prost::alloc::vec::Vec, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct ConnectionStateChanged { #[prost(enumeration="ConnectionState", required, tag="1")] pub state: i32, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct Connected { } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct Disconnected { #[prost(enumeration="DisconnectReason", required, tag="1")] pub reason: i32, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct Reconnecting { } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct Reconnected { } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct RoomEos { } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] @@ -2893,9 +2707,9 @@ impl IceTransportType { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - IceTransportType::TransportRelay => "TRANSPORT_RELAY", - IceTransportType::TransportNohost => "TRANSPORT_NOHOST", - IceTransportType::TransportAll => "TRANSPORT_ALL", + Self::TransportRelay => "TRANSPORT_RELAY", + Self::TransportNohost => "TRANSPORT_NOHOST", + Self::TransportAll => "TRANSPORT_ALL", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -2921,8 +2735,8 @@ impl ContinualGatheringPolicy { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - ContinualGatheringPolicy::GatherOnce => "GATHER_ONCE", - ContinualGatheringPolicy::GatherContinually => "GATHER_CONTINUALLY", + Self::GatherOnce => "GATHER_ONCE", + Self::GatherContinually => "GATHER_CONTINUALLY", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -2953,10 +2767,10 @@ impl ConnectionQuality { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - ConnectionQuality::QualityPoor => "QUALITY_POOR", - ConnectionQuality::QualityGood => "QUALITY_GOOD", - ConnectionQuality::QualityExcellent => "QUALITY_EXCELLENT", - ConnectionQuality::QualityLost => "QUALITY_LOST", + Self::QualityPoor => "QUALITY_POOR", + Self::QualityGood => "QUALITY_GOOD", + Self::QualityExcellent => "QUALITY_EXCELLENT", + Self::QualityLost => "QUALITY_LOST", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -2984,9 +2798,9 @@ impl ConnectionState { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - ConnectionState::ConnDisconnected => "CONN_DISCONNECTED", - ConnectionState::ConnConnected => "CONN_CONNECTED", - ConnectionState::ConnReconnecting => "CONN_RECONNECTING", + Self::ConnDisconnected => "CONN_DISCONNECTED", + Self::ConnConnected => "CONN_CONNECTED", + Self::ConnReconnecting => "CONN_RECONNECTING", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -3012,8 +2826,8 @@ impl DataPacketKind { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - DataPacketKind::KindLossy => "KIND_LOSSY", - DataPacketKind::KindReliable => "KIND_RELIABLE", + Self::KindLossy => "KIND_LOSSY", + Self::KindReliable => "KIND_RELIABLE", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -3057,17 +2871,17 @@ impl DisconnectReason { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - DisconnectReason::UnknownReason => "UNKNOWN_REASON", - DisconnectReason::ClientInitiated => "CLIENT_INITIATED", - DisconnectReason::DuplicateIdentity => "DUPLICATE_IDENTITY", - DisconnectReason::ServerShutdown => "SERVER_SHUTDOWN", - DisconnectReason::ParticipantRemoved => "PARTICIPANT_REMOVED", - DisconnectReason::RoomDeleted => "ROOM_DELETED", - DisconnectReason::StateMismatch => "STATE_MISMATCH", - DisconnectReason::JoinFailure => "JOIN_FAILURE", - DisconnectReason::Migration => "MIGRATION", - DisconnectReason::SignalClose => "SIGNAL_CLOSE", - DisconnectReason::RoomClosed => "ROOM_CLOSED", + Self::UnknownReason => "UNKNOWN_REASON", + Self::ClientInitiated => "CLIENT_INITIATED", + Self::DuplicateIdentity => "DUPLICATE_IDENTITY", + Self::ServerShutdown => "SERVER_SHUTDOWN", + Self::ParticipantRemoved => "PARTICIPANT_REMOVED", + Self::RoomDeleted => "ROOM_DELETED", + Self::StateMismatch => "STATE_MISMATCH", + Self::JoinFailure => "JOIN_FAILURE", + Self::Migration => "MIGRATION", + Self::SignalClose => "SIGNAL_CLOSE", + Self::RoomClosed => "ROOM_CLOSED", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -3090,8 +2904,7 @@ impl DisconnectReason { } /// Create a new AudioStream /// AudioStream is used to receive audio frames from a track -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct NewAudioStreamRequest { #[prost(uint64, required, tag="1")] pub track_handle: u64, @@ -3102,14 +2915,12 @@ pub struct NewAudioStreamRequest { #[prost(uint32, optional, tag="4")] pub num_channels: ::core::option::Option, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct NewAudioStreamResponse { #[prost(message, required, tag="1")] pub stream: OwnedAudioStream, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct AudioStreamFromParticipantRequest { #[prost(uint64, required, tag="1")] pub participant_handle: u64, @@ -3122,15 +2933,13 @@ pub struct AudioStreamFromParticipantRequest { #[prost(uint32, optional, tag="6")] pub num_channels: ::core::option::Option, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct AudioStreamFromParticipantResponse { #[prost(message, required, tag="1")] pub stream: OwnedAudioStream, } /// Create a new AudioSource -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct NewAudioSourceRequest { #[prost(enumeration="AudioSourceType", required, tag="1")] pub r#type: i32, @@ -3143,29 +2952,25 @@ pub struct NewAudioSourceRequest { #[prost(uint32, optional, tag="5")] pub queue_size_ms: ::core::option::Option, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct NewAudioSourceResponse { #[prost(message, required, tag="1")] pub source: OwnedAudioSource, } /// Push a frame to an AudioSource /// The data provided must be available as long as the client receive the callback. -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct CaptureAudioFrameRequest { #[prost(uint64, required, tag="1")] pub source_handle: u64, #[prost(message, required, tag="2")] pub buffer: AudioFrameBufferInfo, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct CaptureAudioFrameResponse { #[prost(uint64, required, tag="1")] pub async_id: u64, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CaptureAudioFrameCallback { #[prost(uint64, required, tag="1")] @@ -3173,30 +2978,25 @@ pub struct CaptureAudioFrameCallback { #[prost(string, optional, tag="2")] pub error: ::core::option::Option<::prost::alloc::string::String>, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct ClearAudioBufferRequest { #[prost(uint64, required, tag="1")] pub source_handle: u64, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct ClearAudioBufferResponse { } /// Create a new AudioResampler -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct NewAudioResamplerRequest { } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct NewAudioResamplerResponse { #[prost(message, required, tag="1")] pub resampler: OwnedAudioResampler, } /// Remix and resample an audio frame -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct RemixAndResampleRequest { #[prost(uint64, required, tag="1")] pub resampler_handle: u64, @@ -3207,16 +3007,14 @@ pub struct RemixAndResampleRequest { #[prost(uint32, required, tag="4")] pub sample_rate: u32, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct RemixAndResampleResponse { #[prost(message, required, tag="1")] pub buffer: OwnedAudioFrameBuffer, } // New resampler using SoX (much better quality) -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct NewSoxResamplerRequest { #[prost(double, required, tag="1")] pub input_rate: f64, @@ -3233,7 +3031,6 @@ pub struct NewSoxResamplerRequest { #[prost(uint32, optional, tag="7")] pub flags: ::core::option::Option, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct NewSoxResamplerResponse { #[prost(oneof="new_sox_resampler_response::Message", tags="1, 2")] @@ -3241,8 +3038,7 @@ pub struct NewSoxResamplerResponse { } /// Nested message and enum types in `NewSoxResamplerResponse`. pub mod new_sox_resampler_response { - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Oneof)] + #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Message { #[prost(message, tag="1")] Resampler(super::OwnedSoxResampler), @@ -3250,8 +3046,7 @@ pub mod new_sox_resampler_response { Error(::prost::alloc::string::String), } } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct PushSoxResamplerRequest { #[prost(uint64, required, tag="1")] pub resampler_handle: u64, @@ -3262,7 +3057,6 @@ pub struct PushSoxResamplerRequest { #[prost(uint32, required, tag="3")] pub size: u32, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PushSoxResamplerResponse { /// *const i16 (could be null) @@ -3274,13 +3068,11 @@ pub struct PushSoxResamplerResponse { #[prost(string, optional, tag="3")] pub error: ::core::option::Option<::prost::alloc::string::String>, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct FlushSoxResamplerRequest { #[prost(uint64, required, tag="1")] pub resampler_handle: u64, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FlushSoxResamplerResponse { /// *const i16 (could be null) @@ -3296,8 +3088,7 @@ pub struct FlushSoxResamplerResponse { // AudioFrame buffer // -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct AudioFrameBufferInfo { /// *const i16 #[prost(uint64, required, tag="1")] @@ -3309,30 +3100,26 @@ pub struct AudioFrameBufferInfo { #[prost(uint32, required, tag="4")] pub samples_per_channel: u32, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct OwnedAudioFrameBuffer { #[prost(message, required, tag="1")] pub handle: FfiOwnedHandle, #[prost(message, required, tag="2")] pub info: AudioFrameBufferInfo, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct AudioStreamInfo { #[prost(enumeration="AudioStreamType", required, tag="1")] pub r#type: i32, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct OwnedAudioStream { #[prost(message, required, tag="1")] pub handle: FfiOwnedHandle, #[prost(message, required, tag="2")] pub info: AudioStreamInfo, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct AudioStreamEvent { #[prost(uint64, required, tag="1")] pub stream_handle: u64, @@ -3341,8 +3128,7 @@ pub struct AudioStreamEvent { } /// Nested message and enum types in `AudioStreamEvent`. pub mod audio_stream_event { - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Oneof)] + #[derive(Clone, Copy, PartialEq, ::prost::Oneof)] pub enum Message { #[prost(message, tag="2")] FrameReceived(super::AudioFrameReceived), @@ -3350,22 +3136,19 @@ pub mod audio_stream_event { Eos(super::AudioStreamEos), } } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct AudioFrameReceived { #[prost(message, required, tag="1")] pub frame: OwnedAudioFrameBuffer, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct AudioStreamEos { } // // AudioSource // -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct AudioSourceOptions { #[prost(bool, required, tag="1")] pub echo_cancellation: bool, @@ -3374,14 +3157,12 @@ pub struct AudioSourceOptions { #[prost(bool, required, tag="3")] pub auto_gain_control: bool, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct AudioSourceInfo { #[prost(enumeration="AudioSourceType", required, tag="2")] pub r#type: i32, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct OwnedAudioSource { #[prost(message, required, tag="1")] pub handle: FfiOwnedHandle, @@ -3392,12 +3173,10 @@ pub struct OwnedAudioSource { // AudioResampler // -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct AudioResamplerInfo { } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct OwnedAudioResampler { #[prost(message, required, tag="1")] pub handle: FfiOwnedHandle, @@ -3408,12 +3187,10 @@ pub struct OwnedAudioResampler { // Sox AudioResampler // -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct SoxResamplerInfo { } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct OwnedSoxResampler { #[prost(message, required, tag="1")] pub handle: FfiOwnedHandle, @@ -3434,8 +3211,8 @@ impl SoxResamplerDataType { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - SoxResamplerDataType::SoxrDatatypeInt16i => "SOXR_DATATYPE_INT16I", - SoxResamplerDataType::SoxrDatatypeInt16s => "SOXR_DATATYPE_INT16S", + Self::SoxrDatatypeInt16i => "SOXR_DATATYPE_INT16I", + Self::SoxrDatatypeInt16s => "SOXR_DATATYPE_INT16S", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -3463,11 +3240,11 @@ impl SoxQualityRecipe { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - SoxQualityRecipe::SoxrQualityQuick => "SOXR_QUALITY_QUICK", - SoxQualityRecipe::SoxrQualityLow => "SOXR_QUALITY_LOW", - SoxQualityRecipe::SoxrQualityMedium => "SOXR_QUALITY_MEDIUM", - SoxQualityRecipe::SoxrQualityHigh => "SOXR_QUALITY_HIGH", - SoxQualityRecipe::SoxrQualityVeryhigh => "SOXR_QUALITY_VERYHIGH", + Self::SoxrQualityQuick => "SOXR_QUALITY_QUICK", + Self::SoxrQualityLow => "SOXR_QUALITY_LOW", + Self::SoxrQualityMedium => "SOXR_QUALITY_MEDIUM", + Self::SoxrQualityHigh => "SOXR_QUALITY_HIGH", + Self::SoxrQualityVeryhigh => "SOXR_QUALITY_VERYHIGH", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -3505,12 +3282,12 @@ impl SoxFlagBits { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - SoxFlagBits::SoxrRolloffSmall => "SOXR_ROLLOFF_SMALL", - SoxFlagBits::SoxrRolloffMedium => "SOXR_ROLLOFF_MEDIUM", - SoxFlagBits::SoxrRolloffNone => "SOXR_ROLLOFF_NONE", - SoxFlagBits::SoxrHighPrecClock => "SOXR_HIGH_PREC_CLOCK", - SoxFlagBits::SoxrDoublePrecision => "SOXR_DOUBLE_PRECISION", - SoxFlagBits::SoxrVr => "SOXR_VR", + Self::SoxrRolloffSmall => "SOXR_ROLLOFF_SMALL", + Self::SoxrRolloffMedium => "SOXR_ROLLOFF_MEDIUM", + Self::SoxrRolloffNone => "SOXR_ROLLOFF_NONE", + Self::SoxrHighPrecClock => "SOXR_HIGH_PREC_CLOCK", + Self::SoxrDoublePrecision => "SOXR_DOUBLE_PRECISION", + Self::SoxrVr => "SOXR_VR", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -3543,8 +3320,8 @@ impl AudioStreamType { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - AudioStreamType::AudioStreamNative => "AUDIO_STREAM_NATIVE", - AudioStreamType::AudioStreamHtml => "AUDIO_STREAM_HTML", + Self::AudioStreamNative => "AUDIO_STREAM_NATIVE", + Self::AudioStreamHtml => "AUDIO_STREAM_HTML", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -3568,7 +3345,7 @@ impl AudioSourceType { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - AudioSourceType::AudioSourceNative => "AUDIO_SOURCE_NATIVE", + Self::AudioSourceNative => "AUDIO_SOURCE_NATIVE", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -3579,7 +3356,6 @@ impl AudioSourceType { } } } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RpcError { #[prost(uint32, required, tag="1")] @@ -3590,7 +3366,6 @@ pub struct RpcError { pub data: ::core::option::Option<::prost::alloc::string::String>, } /// FFI Requests -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PerformRpcRequest { #[prost(uint64, required, tag="1")] @@ -3604,7 +3379,6 @@ pub struct PerformRpcRequest { #[prost(uint32, optional, tag="5")] pub response_timeout_ms: ::core::option::Option, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RegisterRpcMethodRequest { #[prost(uint64, required, tag="1")] @@ -3612,7 +3386,6 @@ pub struct RegisterRpcMethodRequest { #[prost(string, required, tag="2")] pub method: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct UnregisterRpcMethodRequest { #[prost(uint64, required, tag="1")] @@ -3620,7 +3393,6 @@ pub struct UnregisterRpcMethodRequest { #[prost(string, required, tag="2")] pub method: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RpcMethodInvocationResponseRequest { #[prost(uint64, required, tag="1")] @@ -3633,28 +3405,23 @@ pub struct RpcMethodInvocationResponseRequest { pub error: ::core::option::Option, } /// FFI Responses -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct PerformRpcResponse { #[prost(uint64, required, tag="1")] pub async_id: u64, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct RegisterRpcMethodResponse { } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct UnregisterRpcMethodResponse { } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RpcMethodInvocationResponseResponse { #[prost(string, optional, tag="1")] pub error: ::core::option::Option<::prost::alloc::string::String>, } /// FFI Callbacks -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PerformRpcCallback { #[prost(uint64, required, tag="1")] @@ -3665,7 +3432,6 @@ pub struct PerformRpcCallback { pub error: ::core::option::Option, } /// FFI Events -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RpcMethodInvocationEvent { #[prost(uint64, required, tag="1")] @@ -3711,7 +3477,6 @@ pub struct RpcMethodInvocationEvent { /// This is the input of livekit_ffi_request function /// We always expect a response (FFIResponse, even if it's empty) -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiRequest { #[prost(oneof="ffi_request::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43")] @@ -3719,8 +3484,7 @@ pub struct FfiRequest { } /// Nested message and enum types in `FfiRequest`. pub mod ffi_request { - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Oneof)] + #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Message { #[prost(message, tag="2")] Dispose(super::DisposeRequest), @@ -3815,7 +3579,6 @@ pub mod ffi_request { } } /// This is the output of livekit_ffi_request function. -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiResponse { #[prost(oneof="ffi_response::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42")] @@ -3823,8 +3586,7 @@ pub struct FfiResponse { } /// Nested message and enum types in `FfiResponse`. pub mod ffi_response { - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Oneof)] + #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Message { #[prost(message, tag="2")] Dispose(super::DisposeResponse), @@ -3919,7 +3681,6 @@ pub mod ffi_response { /// To minimize complexity, participant events are not included in the protocol. /// It is easily deducible from the room events and it turned out that is is easier to implement /// on the ffi client side. -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiEvent { #[prost(oneof="ffi_event::Message", tags="1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24")] @@ -3927,8 +3688,7 @@ pub struct FfiEvent { } /// Nested message and enum types in `FfiEvent`. pub mod ffi_event { - #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Oneof)] + #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Message { #[prost(message, tag="1")] RoomEvent(super::RoomEvent), @@ -3981,26 +3741,22 @@ pub mod ffi_event { /// Stop all rooms synchronously (Do we need async here?). /// e.g: This is used for the Unity Editor after each assemblies reload. /// TODO(theomonnom): Implement a debug mode where we can find all leaked handles? -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct DisposeRequest { #[prost(bool, required, tag="1")] pub r#async: bool, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct DisposeResponse { /// None if sync #[prost(uint64, optional, tag="1")] pub async_id: ::core::option::Option, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct DisposeCallback { #[prost(uint64, required, tag="1")] pub async_id: u64, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct LogRecord { #[prost(enumeration="LogLevel", required, tag="1")] @@ -4017,13 +3773,11 @@ pub struct LogRecord { #[prost(string, required, tag="6")] pub message: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct LogBatch { #[prost(message, repeated, tag="1")] pub records: ::prost::alloc::vec::Vec, } -#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct Panic { #[prost(string, required, tag="1")] @@ -4045,11 +3799,11 @@ impl LogLevel { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - LogLevel::LogError => "LOG_ERROR", - LogLevel::LogWarn => "LOG_WARN", - LogLevel::LogInfo => "LOG_INFO", - LogLevel::LogDebug => "LOG_DEBUG", - LogLevel::LogTrace => "LOG_TRACE", + Self::LogError => "LOG_ERROR", + Self::LogWarn => "LOG_WARN", + Self::LogInfo => "LOG_INFO", + Self::LogDebug => "LOG_DEBUG", + Self::LogTrace => "LOG_TRACE", } } /// Creates an enum from field names used in the ProtoBuf definition. diff --git a/livekit-runtime/Cargo.toml b/livekit-runtime/Cargo.toml index bb3a283a7..2ec822ae4 100644 --- a/livekit-runtime/Cargo.toml +++ b/livekit-runtime/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" repository = "https://github.com/livekit/rust-sdks" [features] +default = ["tokio"] tokio = ["dep:tokio", "dep:tokio-stream"] async = [ "dep:async-std", From 5a3a8eae0db1d0ab04149a1d98f754354bed7b41 Mon Sep 17 00:00:00 2001 From: CloudWebRTC Date: Sat, 30 Nov 2024 21:49:21 +0800 Subject: [PATCH 090/274] fixed ffi build for android. (#409) fixed ffi build for android. --- .github/workflows/builds.yml | 4 +++ .github/workflows/ffi-builds.yml | 47 +++++++++++++++++--------------- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index 48403b16b..4940634da 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -54,10 +54,13 @@ jobs: target: aarch64-unknown-linux-gnu - os: ubuntu-20.04 target: aarch64-linux-android + ndk_arch: aarch64-unknown-linux-musl - os: ubuntu-20.04 target: armv7-linux-androideabi + ndk_arch: arm-unknown-linux-musleabihf - os: ubuntu-20.04 target: x86_64-linux-android + ndk_arch: x86_64-unknown-linux-musl name: Build (${{ matrix.target }}) runs-on: ${{ matrix.os }} @@ -85,5 +88,6 @@ jobs: - name: Build (Android) if: ${{ contains(matrix.target, 'android') }} run: | + ln -sf $ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/lib/${{ matrix.ndk_arch }}/{libunwind.so,libc++abi.a} $ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/lib/ cargo install cargo-ndk cargo ndk --target ${{ matrix.target }} build --release -p livekit --workspace -vv diff --git a/.github/workflows/ffi-builds.yml b/.github/workflows/ffi-builds.yml index 34a2ea8e4..ccba91f91 100644 --- a/.github/workflows/ffi-builds.yml +++ b/.github/workflows/ffi-builds.yml @@ -78,28 +78,30 @@ jobs: dylib: liblivekit_ffi.so target: aarch64-unknown-linux-gnu name: ffi-linux-arm64 - ## android builds broke - # - os: ubuntu-20.04 - # platform: android - # dylib: liblivekit_ffi.so - # jar: libwebrtc.jar - # target: aarch64-linux-android - # name: ffi-android-arm64 - # buildargs: --no-default-features --features "rustls-tls-webpki-roots" - # - os: ubuntu-20.04 - # platform: android - # dylib: liblivekit_ffi.so - # jar: libwebrtc.jar - # target: armv7-linux-androideabi - # name: ffi-android-armv7 - # buildargs: --no-default-features --features "rustls-tls-webpki-roots" - # - os: ubuntu-20.04 - # platform: android - # dylib: liblivekit_ffi.so - # jar: libwebrtc.jar - # target: x86_64-linux-android - # name: ffi-android-x86_64 - # buildargs: --no-default-features --features "rustls-tls-webpki-roots" + - os: ubuntu-20.04 + platform: android + dylib: liblivekit_ffi.so + jar: libwebrtc.jar + target: aarch64-linux-android + ndk_arch: aarch64-unknown-linux-musl + name: ffi-android-arm64 + buildargs: --no-default-features --features "rustls-tls-webpki-roots" + - os: ubuntu-20.04 + platform: android + dylib: liblivekit_ffi.so + jar: libwebrtc.jar + target: armv7-linux-androideabi + ndk_arch: arm-unknown-linux-musleabihf + name: ffi-android-armv7 + buildargs: --no-default-features --features "rustls-tls-webpki-roots" + - os: ubuntu-20.04 + platform: android + dylib: liblivekit_ffi.so + jar: libwebrtc.jar + target: x86_64-linux-android + ndk_arch: x86_64-unknown-linux-musl + name: ffi-android-x86_64 + buildargs: --no-default-features --features "rustls-tls-webpki-roots" name: Build (${{ matrix.target }}) runs-on: ${{ matrix.os }} @@ -157,6 +159,7 @@ jobs: if: ${{ matrix.platform == 'android' }} run: | cd livekit-ffi/ + ln -sf $ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/lib/${{ matrix.ndk_arch }}/{libunwind.so,libc++abi.a} $ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/lib/ cargo install cargo-ndk cargo ndk --bindgen --target ${{ matrix.target }} build --release ${{ matrix.buildargs }} From fb1efb03ff5109120ac639946c2ea3364c969aa9 Mon Sep 17 00:00:00 2001 From: tommady Date: Mon, 2 Dec 2024 15:22:27 +0800 Subject: [PATCH 091/274] fix-livekit-ffi-build-on-ios-failure (#502) Signed-off-by: tommady --- livekit-ffi/build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/livekit-ffi/build.rs b/livekit-ffi/build.rs index 28afe0d05..75432fdb5 100644 --- a/livekit-ffi/build.rs +++ b/livekit-ffi/build.rs @@ -42,7 +42,7 @@ fn main() { std::fs::copy(license, out_file).unwrap(); } - "macos" => { + "macos" | "ios" => { println!("cargo:rustc-link-arg=-ObjC"); } _ => { From 65a1b02a2806547fa1f77a55bab1e24eef194880 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Mon, 2 Dec 2024 14:58:17 +0100 Subject: [PATCH 092/274] Update lib.rs --- webrtc-sys/build/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webrtc-sys/build/src/lib.rs b/webrtc-sys/build/src/lib.rs index f3eb2a5ea..cb137c799 100644 --- a/webrtc-sys/build/src/lib.rs +++ b/webrtc-sys/build/src/lib.rs @@ -26,7 +26,7 @@ use regex::Regex; use reqwest::StatusCode; pub const SCRATH_PATH: &str = "livekit_webrtc"; -pub const WEBRTC_TAG: &str = "webrtc-dac8015-5"; +pub const WEBRTC_TAG: &str = "webrtc-dac8015-6"; pub const IGNORE_DEFINES: [&str; 2] = ["CR_CLANG_REVISION", "CR_XCODE_VERSION"]; pub fn target_os() -> String { From c86758be9897bd3aedbfad4b1a5d06936bfca69b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Tue, 10 Dec 2024 21:01:31 +0100 Subject: [PATCH 093/274] move libyuv/imgproc from theomonnom/mikado (#508) --- .gitmodules | 3 + Cargo.toml | 4 + imgproc/Cargo.lock | 442 ++++++++++++ imgproc/Cargo.toml | 10 + imgproc/rustfmt.toml | 10 + imgproc/src/colorcvt/assert.rs | 164 +++++ imgproc/src/colorcvt/mod.rs | 1101 +++++++++++++++++++++++++++++ imgproc/src/lib.rs | 1 + livekit-ffi/Cargo.toml | 2 +- yuv-sys/Cargo.lock | 479 +++++++++++++ yuv-sys/Cargo.toml | 13 + yuv-sys/build.rs | 143 ++++ yuv-sys/libyuv | 1 + yuv-sys/src/lib.rs | 6 + yuv-sys/yuv_functions.txt | 1193 ++++++++++++++++++++++++++++++++ 15 files changed, 3571 insertions(+), 1 deletion(-) create mode 100644 imgproc/Cargo.lock create mode 100644 imgproc/Cargo.toml create mode 100644 imgproc/rustfmt.toml create mode 100644 imgproc/src/colorcvt/assert.rs create mode 100644 imgproc/src/colorcvt/mod.rs create mode 100644 imgproc/src/lib.rs create mode 100644 yuv-sys/Cargo.lock create mode 100644 yuv-sys/Cargo.toml create mode 100644 yuv-sys/build.rs create mode 160000 yuv-sys/libyuv create mode 100644 yuv-sys/src/lib.rs create mode 100644 yuv-sys/yuv_functions.txt diff --git a/.gitmodules b/.gitmodules index f13ae2153..b70842c2a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "livekit/protocol"] path = livekit-protocol/protocol url = https://github.com/livekit/protocol +[submodule "yuv-sys/libyuv"] + path = yuv-sys/libyuv + url = https://chromium.googlesource.com/libyuv/libyuv diff --git a/Cargo.toml b/Cargo.toml index 52650e01e..c0a232a25 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,11 +8,15 @@ members = [ "livekit-runtime", "libwebrtc", "soxr-sys", + "yuv-sys", + "imgproc", "webrtc-sys", "webrtc-sys/build", ] [workspace.dependencies] +imgproc = { version = "0.3.11", path = "imgproc" } +yuv-sys = { version = "0.3.6", path = "yuv-sys" } libwebrtc = { version = "0.3.7", path = "libwebrtc" } livekit-api = { version = "0.4.1", path = "livekit-api" } livekit-ffi = { version = "0.12.3", path = "livekit-ffi" } diff --git a/imgproc/Cargo.lock b/imgproc/Cargo.lock new file mode 100644 index 000000000..6bf8e288a --- /dev/null +++ b/imgproc/Cargo.lock @@ -0,0 +1,442 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "bindgen" +version = "0.69.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ffcebc3849946a7170a05992aac39da343a90676ab392c51a4280981d6379c2" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", + "which", +] + +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clang-sys" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "imgproc" +version = "0.3.6" +dependencies = [ + "yuv-sys", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" + +[[package]] +name = "libloading" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "prettyplease" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustix" +version = "0.38.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "shlex" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" + +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + +[[package]] +name = "yuv-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9a8a1a93479f1701a725ffeaaa0dc03d8096387490e820a12daf3f302065fe" +dependencies = [ + "bindgen", + "cc", + "regex", +] diff --git a/imgproc/Cargo.toml b/imgproc/Cargo.toml new file mode 100644 index 000000000..6f49dccd6 --- /dev/null +++ b/imgproc/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "imgproc" +version = "0.3.11" +edition = "2021" +authors = ["Theo Monnom "] +license = "MIT OR Apache-2.0" +description = "image processing library" + +[dependencies] +yuv-sys = { path = "../yuv-sys" } diff --git a/imgproc/rustfmt.toml b/imgproc/rustfmt.toml new file mode 100644 index 000000000..18a73acec --- /dev/null +++ b/imgproc/rustfmt.toml @@ -0,0 +1,10 @@ +comment_width = 100 +doc_comment_code_block_width = 100 +format_code_in_doc_comments = true +group_imports = "StdExternalCrate" +imports_granularity = "Crate" +max_width = 100 +use_small_heuristics = "Max" +wrap_comments = true +# Workaround for https://github.com/rust-lang/rust.vim/issues/464 +edition = "2021" diff --git a/imgproc/src/colorcvt/assert.rs b/imgproc/src/colorcvt/assert.rs new file mode 100644 index 000000000..0d184b442 --- /dev/null +++ b/imgproc/src/colorcvt/assert.rs @@ -0,0 +1,164 @@ +#[inline] +pub fn valid_420( + src_y: &[u8], + src_stride_y: u32, + src_u: &[u8], + src_stride_u: u32, + src_v: &[u8], + src_stride_v: u32, + width: u32, + height: u32, +) { + assert!(width > 0); + assert!(height > 0); + + let chroma_width = (width + 1) / 2; + let chroma_height = (height + 1) / 2; + + assert!(src_stride_y >= width); + assert!(src_stride_u >= chroma_width); + assert!(src_stride_v >= chroma_width); + assert!(src_y.len() >= (src_stride_y * height) as usize); + assert!(src_u.len() >= (src_stride_u * chroma_height) as usize); + assert!(src_v.len() >= (src_stride_v * chroma_height) as usize); +} + +#[inline] +pub fn valid_420a( + src_y: &[u8], + src_stride_y: u32, + src_u: &[u8], + src_stride_u: u32, + src_v: &[u8], + src_stride_v: u32, + src_a: &[u8], + src_stride_a: u32, + width: u32, + height: u32, +) { + assert!(width > 0); + assert!(height > 0); + + let chroma_width = (width + 1) / 2; + let chroma_height = (height + 1) / 2; + + assert!(src_stride_y >= width); + assert!(src_stride_u >= chroma_width); + assert!(src_stride_v >= chroma_width); + assert!(src_stride_a >= width); + assert!(src_y.len() >= (src_stride_y * height) as usize); + assert!(src_u.len() >= (src_stride_u * chroma_height) as usize); + assert!(src_v.len() >= (src_stride_v * chroma_height) as usize); + assert!(src_a.len() >= (src_stride_a * height) as usize); +} + +#[inline] +pub fn valid_422( + src_y: &[u8], + src_stride_y: u32, + src_u: &[u8], + src_stride_u: u32, + src_v: &[u8], + src_stride_v: u32, + width: u32, + height: u32, +) { + assert!(width > 0); + assert!(height > 0); + + let chroma_width = (width + 1) / 2; + let chroma_height = height; + + assert!(src_stride_y >= width); + assert!(src_stride_u >= chroma_width); + assert!(src_stride_v >= chroma_width); + assert!(src_y.len() >= (src_stride_y * height) as usize); + assert!(src_u.len() >= (src_stride_u * chroma_height) as usize); + assert!(src_v.len() >= (src_stride_v * chroma_height) as usize); +} + +#[inline] +pub fn valid_444( + src_y: &[u8], + src_stride_y: u32, + src_u: &[u8], + src_stride_u: u32, + src_v: &[u8], + src_stride_v: u32, + width: u32, + height: u32, +) { + assert!(height > 0); + assert!(width > 0); + + let chroma_width = width; + let chroma_height = height; + + assert!(src_stride_y >= width); + assert!(src_stride_u >= chroma_width); + assert!(src_stride_v >= chroma_width); + assert!(src_y.len() >= (src_stride_y * height) as usize); + assert!(src_u.len() >= (src_stride_u * chroma_height) as usize); + assert!(src_v.len() >= (src_stride_v * chroma_height) as usize); +} + +#[inline] +pub fn valid_010( + src_y: &[u16], + src_stride_y: u32, + src_u: &[u16], + src_stride_u: u32, + src_v: &[u16], + src_stride_v: u32, + width: u32, + height: u32, +) { + assert!(height > 0); + assert!(width > 0); + + let chroma_width = (width + 1) / 2; + let chroma_height = (height + 1) / 2; + + assert!(src_stride_y >= width); + assert!(src_stride_u >= chroma_width); + assert!(src_stride_v >= chroma_width); + assert!(src_y.len() >= (src_stride_y * height) as usize); + assert!(src_u.len() >= (src_stride_u * chroma_height) as usize); + assert!(src_v.len() >= (src_stride_v * chroma_height) as usize); +} + +#[inline] +pub fn valid_nv12( + src_y: &[u8], + src_stride_y: u32, + src_uv: &[u8], + src_stride_uv: u32, + width: u32, + height: u32, +) { + assert!(width > 0); + assert!(height > 0); + + let chroma_height = (height + 1) / 2; + + assert!(src_stride_y >= width); + assert!(src_stride_uv >= width + width % 2); + assert!(src_y.len() >= (src_stride_y * height) as usize); + assert!(src_uv.len() >= (src_stride_uv * chroma_height) as usize); +} + +#[inline] +pub fn valid_rgba(src_rgba: &[u8], src_stride_rgba: u32, width: u32, height: u32) { + assert!(width > 0); + assert!(height > 0); + assert!(src_stride_rgba >= width * 4); + assert!(src_rgba.len() >= (src_stride_rgba * height) as usize); +} + +#[inline] +pub fn valid_rgb(src_rgb: &[u8], src_stride_rgb: u32, width: u32, height: u32) { + assert!(width > 0); + assert!(height > 0); + assert!(src_stride_rgb >= width * 3); + assert!(src_rgb.len() >= (src_stride_rgb * height) as usize); +} diff --git a/imgproc/src/colorcvt/mod.rs b/imgproc/src/colorcvt/mod.rs new file mode 100644 index 000000000..5c192ee8f --- /dev/null +++ b/imgproc/src/colorcvt/mod.rs @@ -0,0 +1,1101 @@ +mod assert; + +macro_rules! x420_to_rgba { + ($rust_fnc:ident, $yuv_sys_fnc:ident) => { + pub fn $rust_fnc( + src_y: &[u8], + stride_y: u32, + src_u: &[u8], + stride_u: u32, + src_v: &[u8], + stride_v: u32, + dst_rgba: &mut [u8], + dst_stride_rgba: u32, + width: u32, + height: u32, + flip_y: bool, + ) { + assert::valid_420(src_y, stride_y, src_u, stride_u, src_v, stride_v, width, height); + assert::valid_rgba(dst_rgba, dst_stride_rgba, width, height); + + let height = height as i32 * if flip_y { -1 } else { 1 }; + + assert!(unsafe { + yuv_sys::$yuv_sys_fnc( + src_y.as_ptr(), + stride_y as i32, + src_u.as_ptr(), + stride_u as i32, + src_v.as_ptr(), + stride_v as i32, + dst_rgba.as_mut_ptr(), + dst_stride_rgba as i32, + width as i32, + height, + ) == 0 + }); + } + }; +} + +x420_to_rgba!(i420_to_rgba, rs_I420ToRGBA); +x420_to_rgba!(i420_to_abgr, rs_I420ToABGR); +x420_to_rgba!(i420_to_bgra, rs_I420ToBGRA); +x420_to_rgba!(i420_to_argb, rs_I420ToARGB); +x420_to_rgba!(j420_to_argb, rs_J420ToARGB); +x420_to_rgba!(j420_to_abgr, rs_J420ToABGR); +x420_to_rgba!(h420_to_argb, rs_H420ToARGB); +x420_to_rgba!(h420_to_abgr, rs_H420ToABGR); +x420_to_rgba!(u420_to_argb, rs_U420ToARGB); +x420_to_rgba!(u420_to_abgr, rs_U420ToABGR); + +pub fn i420_to_rgb24( + src_y: &[u8], + src_stride_y: u32, + src_u: &[u8], + src_stride_u: u32, + src_v: &[u8], + src_stride_v: u32, + dst_rgb24: &mut [u8], + dst_stride_rgb24: u32, + width: u32, + height: u32, + flip_y: bool, +) { + assert::valid_420(src_y, src_stride_y, src_u, src_stride_u, src_v, src_stride_v, width, height); + assert::valid_rgb(dst_rgb24, dst_stride_rgb24, width, height); + + let height = height as i32 * if flip_y { -1 } else { 1 }; + + assert!(unsafe { + yuv_sys::rs_I420ToRGB24( + src_y.as_ptr(), + src_stride_y as i32, + src_u.as_ptr(), + src_stride_u as i32, + src_v.as_ptr(), + src_stride_v as i32, + dst_rgb24.as_mut_ptr(), + dst_stride_rgb24 as i32, + width as i32, + height, + ) == 0 + }); +} + +pub fn i420_to_raw( + src_y: &[u8], + src_stride_y: u32, + src_u: &[u8], + src_stride_u: u32, + src_v: &[u8], + src_stride_v: u32, + dst_raw: &mut [u8], + dst_stride_raw: u32, + width: u32, + height: u32, + flip_y: bool, +) { + assert::valid_420(src_y, src_stride_y, src_u, src_stride_u, src_v, src_stride_v, width, height); + assert::valid_rgb(dst_raw, dst_stride_raw, width, height); + + let height = height as i32 * if flip_y { -1 } else { 1 }; + + assert!(unsafe { + yuv_sys::rs_I420ToRAW( + src_y.as_ptr(), + src_stride_y as i32, + src_u.as_ptr(), + src_stride_u as i32, + src_v.as_ptr(), + src_stride_v as i32, + dst_raw.as_mut_ptr(), + dst_stride_raw as i32, + width as i32, + height, + ) == 0 + }); +} + +macro_rules! rgba_to_rgba { + ($rust_fnc:ident, $yuv_sys_fnc:ident) => { + pub fn $rust_fnc( + src_abgr: &[u8], + src_stride_abgr: u32, + dst_argb: &mut [u8], + dst_stride_argb: u32, + width: u32, + height: u32, + flip_y: bool, + ) { + assert::valid_rgba(src_abgr, src_stride_abgr, width, height); + assert::valid_rgba(dst_argb, dst_stride_argb, width, height); + + let height = height as i32 * if flip_y { -1 } else { 1 }; + + assert!(unsafe { + yuv_sys::$yuv_sys_fnc( + src_abgr.as_ptr(), + src_stride_abgr as i32, + dst_argb.as_mut_ptr(), + dst_stride_argb as i32, + width as i32, + height, + ) == 0 + }); + } + }; +} + +rgba_to_rgba!(abgr_to_argb, rs_ABGRToARGB); +rgba_to_rgba!(argb_to_abgr, rs_ARGBToABGR); +rgba_to_rgba!(rgba_to_argb, rs_RGBAToARGB); +rgba_to_rgba!(bgra_to_argb, rs_BGRAToARGB); + +macro_rules! rgba_to_420 { + ($rust_fnc:ident, $yuv_sys_fnc:ident) => { + pub fn $rust_fnc( + src_rgba: &[u8], + src_stride_rgba: u32, + dst_y: &mut [u8], + dst_stride_y: u32, + dst_u: &mut [u8], + dst_stride_u: u32, + dst_v: &mut [u8], + dst_stride_v: u32, + width: u32, + height: u32, + flip_y: bool, + ) { + assert::valid_rgba(src_rgba, src_stride_rgba, width, height); + assert::valid_420( + dst_y, + dst_stride_y, + dst_u, + dst_stride_u, + dst_v, + dst_stride_v, + width, + height, + ); + + let height = height as i32 * if flip_y { -1 } else { 1 }; + + assert!(unsafe { + yuv_sys::$yuv_sys_fnc( + src_rgba.as_ptr(), + src_stride_rgba as i32, + dst_y.as_mut_ptr(), + dst_stride_y as i32, + dst_u.as_mut_ptr(), + dst_stride_u as i32, + dst_v.as_mut_ptr(), + dst_stride_v as i32, + width as i32, + height, + ) == 0 + }); + } + }; +} + +rgba_to_420!(rgba_to_i420, rs_RGBAToI420); +rgba_to_420!(bgra_to_i420, rs_BGRAToI420); +rgba_to_420!(argb_to_i420, rs_ARGBToI420); +rgba_to_420!(abgr_to_i420, rs_ABGRToI420); + +pub fn raw_to_i420( + src_raw: &[u8], + src_stride_raw: u32, + dst_y: &mut [u8], + dst_stride_y: u32, + dst_u: &mut [u8], + dst_stride_u: u32, + dst_v: &mut [u8], + dst_stride_v: u32, + width: u32, + height: u32, + flip_y: bool, +) { + assert::valid_rgb(src_raw, src_stride_raw, width, height); + assert::valid_420(dst_y, dst_stride_y, dst_u, dst_stride_u, dst_v, dst_stride_v, width, height); + + let height = height as i32 * if flip_y { -1 } else { 1 }; + + unsafe { + yuv_sys::rs_RAWToI420( + src_raw.as_ptr(), + src_stride_raw as i32, + dst_y.as_mut_ptr(), + dst_stride_y as i32, + dst_u.as_mut_ptr(), + dst_stride_u as i32, + dst_v.as_mut_ptr(), + dst_stride_v as i32, + width as i32, + height, + ) + }; +} + +pub fn i422_to_i420( + src_y: &[u8], + src_stride_y: u32, + src_u: &[u8], + src_stride_u: u32, + src_v: &[u8], + src_stride_v: u32, + dst_y: &mut [u8], + dst_stride_y: u32, + dst_u: &mut [u8], + dst_stride_u: u32, + dst_v: &mut [u8], + dst_stride_v: u32, + width: u32, + height: u32, + flip_y: bool, +) { + assert::valid_422(src_y, src_stride_y, src_u, src_stride_u, src_v, src_stride_v, width, height); + assert::valid_420(dst_y, dst_stride_y, dst_u, dst_stride_u, dst_v, dst_stride_v, width, height); + + let height = height as i32 * if flip_y { -1 } else { 1 }; + + assert!(unsafe { + yuv_sys::rs_I422ToI420( + src_y.as_ptr(), + src_stride_y as i32, + src_u.as_ptr(), + src_stride_u as i32, + src_v.as_ptr(), + src_stride_v as i32, + dst_y.as_mut_ptr(), + dst_stride_y as i32, + dst_u.as_mut_ptr(), + dst_stride_u as i32, + dst_v.as_mut_ptr(), + dst_stride_v as i32, + width as i32, + height, + ) == 0 + }); +} + +pub fn i444_to_i420( + src_y: &[u8], + src_stride_y: u32, + src_u: &[u8], + src_stride_u: u32, + src_v: &[u8], + src_stride_v: u32, + dst_y: &mut [u8], + dst_stride_y: u32, + dst_u: &mut [u8], + dst_stride_u: u32, + dst_v: &mut [u8], + dst_stride_v: u32, + width: u32, + height: u32, + flip_y: bool, +) { + assert::valid_444(src_y, src_stride_y, src_u, src_stride_u, src_v, src_stride_v, width, height); + assert::valid_420(dst_y, dst_stride_y, dst_u, dst_stride_u, dst_v, dst_stride_v, width, height); + + let height = height as i32 * if flip_y { -1 } else { 1 }; + + assert!(unsafe { + yuv_sys::rs_I444ToI420( + src_y.as_ptr(), + src_stride_y as i32, + src_u.as_ptr(), + src_stride_u as i32, + src_v.as_ptr(), + src_stride_v as i32, + dst_y.as_mut_ptr(), + dst_stride_y as i32, + dst_u.as_mut_ptr(), + dst_stride_u as i32, + dst_v.as_mut_ptr(), + dst_stride_v as i32, + width as i32, + height, + ) == 0 + }); +} + +pub fn i010_to_i420( + src_y: &[u16], + src_stride_y: u32, + src_u: &[u16], + src_stride_u: u32, + src_v: &[u16], + src_stride_v: u32, + dst_y: &mut [u8], + dst_stride_y: u32, + dst_u: &mut [u8], + dst_stride_u: u32, + dst_v: &mut [u8], + dst_stride_v: u32, + width: u32, + height: u32, + flip_y: bool, +) { + assert::valid_010(src_y, src_stride_y, src_u, src_stride_u, src_v, src_stride_v, width, height); + assert::valid_420(dst_y, dst_stride_y, dst_u, dst_stride_u, dst_v, dst_stride_v, width, height); + + let height = height as i32 * if flip_y { -1 } else { 1 }; + + assert!(unsafe { + yuv_sys::rs_I010ToI420( + src_y.as_ptr(), + src_stride_y as i32, + src_u.as_ptr(), + src_stride_u as i32, + src_v.as_ptr(), + src_stride_v as i32, + dst_y.as_mut_ptr(), + dst_stride_y as i32, + dst_u.as_mut_ptr(), + dst_stride_u as i32, + dst_v.as_mut_ptr(), + dst_stride_v as i32, + width as i32, + height, + ) == 0 + }); +} + +pub fn nv12_to_i420( + src_y: &[u8], + src_stride_y: u32, + src_uv: &[u8], + src_stride_uv: u32, + dst_y: &mut [u8], + dst_stride_y: u32, + dst_u: &mut [u8], + dst_stride_u: u32, + dst_v: &mut [u8], + dst_stride_v: u32, + width: u32, + height: u32, + flip_y: bool, +) { + assert::valid_nv12(src_y, src_stride_y, src_uv, src_stride_uv, width, height); + assert::valid_420(dst_y, dst_stride_y, dst_u, dst_stride_u, dst_v, dst_stride_v, width, height); + + let height = height as i32 * if flip_y { -1 } else { 1 }; + + assert!(unsafe { + yuv_sys::rs_NV12ToI420( + src_y.as_ptr(), + src_stride_y as i32, + src_uv.as_ptr(), + src_stride_uv as i32, + dst_y.as_mut_ptr(), + dst_stride_y as i32, + dst_u.as_mut_ptr(), + dst_stride_u as i32, + dst_v.as_mut_ptr(), + dst_stride_v as i32, + width as i32, + height, + ) == 0 + }); +} + +pub fn i422_to_raw( + src_y: &[u8], + src_stride_y: u32, + src_u: &[u8], + src_stride_u: u32, + src_v: &[u8], + src_stride_v: u32, + dst_raw: &mut [u8], + dst_stride_raw: u32, + width: u32, + height: u32, + flip_y: bool, +) { + assert::valid_422(src_y, src_stride_y, src_u, src_stride_u, src_v, src_stride_v, width, height); + assert::valid_rgb(dst_raw, dst_stride_raw, width, height); + + let height = height as i32 * if flip_y { -1 } else { 1 }; + + assert!(unsafe { + yuv_sys::rs_I422ToRAW( + src_y.as_ptr(), + src_stride_y as i32, + src_u.as_ptr(), + src_stride_u as i32, + src_v.as_ptr(), + src_stride_v as i32, + dst_raw.as_mut_ptr(), + dst_stride_raw as i32, + width as i32, + height, + ) == 0 + }); +} + +pub fn i422_to_rgb24( + src_y: &[u8], + src_stride_y: u32, + src_u: &[u8], + src_stride_u: u32, + src_v: &[u8], + src_stride_v: u32, + dst_rgb24: &mut [u8], + dst_stride_rgb24: u32, + width: u32, + height: u32, + flip_y: bool, +) { + assert::valid_422(src_y, src_stride_y, src_u, src_stride_u, src_v, src_stride_v, width, height); + assert::valid_rgb(dst_rgb24, dst_stride_rgb24, width, height); + + let height = height as i32 * if flip_y { -1 } else { 1 }; + + assert!(unsafe { + yuv_sys::rs_I422ToRGB24( + src_y.as_ptr(), + src_stride_y as i32, + src_u.as_ptr(), + src_stride_u as i32, + src_v.as_ptr(), + src_stride_v as i32, + dst_rgb24.as_mut_ptr(), + dst_stride_rgb24 as i32, + width as i32, + height, + ) == 0 + }); +} + +macro_rules! x422_to_rgba { + ($rust_fnc:ident, $yuv_sys_fnc:ident) => { + pub fn $rust_fnc( + src_y: &[u8], + src_stride_y: u32, + src_u: &[u8], + src_stride_u: u32, + src_v: &[u8], + src_stride_v: u32, + dst_rgba: &mut [u8], + dst_stride_rgba: u32, + width: u32, + height: u32, + flip_y: bool, + ) { + assert::valid_422( + src_y, + src_stride_y, + src_u, + src_stride_u, + src_v, + src_stride_v, + width, + height, + ); + assert::valid_rgba(dst_rgba, dst_stride_rgba, width, height); + + let height = height as i32 * if flip_y { -1 } else { 1 }; + + assert!(unsafe { + yuv_sys::$yuv_sys_fnc( + src_y.as_ptr(), + src_stride_y as i32, + src_u.as_ptr(), + src_stride_u as i32, + src_v.as_ptr(), + src_stride_v as i32, + dst_rgba.as_mut_ptr(), + dst_stride_rgba as i32, + width as i32, + height, + ) == 0 + }); + } + }; +} + +x422_to_rgba!(i422_to_abgr, rs_I422ToABGR); +x422_to_rgba!(j422_to_argb, rs_J422ToARGB); +x422_to_rgba!(i422_to_bgra, rs_I422ToBGRA); +x422_to_rgba!(i422_to_rgba, rs_I422ToRGBA); + +pub fn i444_to_raw( + src_y: &[u8], + src_stride_y: u32, + src_u: &[u8], + src_stride_u: u32, + src_v: &[u8], + src_stride_v: u32, + dst_raw: &mut [u8], + dst_stride_raw: u32, + width: u32, + height: u32, + flip_y: bool, +) { + assert::valid_444(src_y, src_stride_y, src_u, src_stride_u, src_v, src_stride_v, width, height); + assert::valid_rgb(dst_raw, dst_stride_raw, width, height); + + let height = height as i32 * if flip_y { -1 } else { 1 }; + + assert!(unsafe { + yuv_sys::rs_I444ToRAW( + src_y.as_ptr(), + src_stride_y as i32, + src_u.as_ptr(), + src_stride_u as i32, + src_v.as_ptr(), + src_stride_v as i32, + dst_raw.as_mut_ptr(), + dst_stride_raw as i32, + width as i32, + height, + ) == 0 + }); +} + +pub fn i444_to_rgb24( + src_y: &[u8], + src_stride_y: u32, + src_u: &[u8], + src_stride_u: u32, + src_v: &[u8], + src_stride_v: u32, + dst_rgb24: &mut [u8], + dst_stride_rgb24: u32, + width: u32, + height: u32, + flip_y: bool, +) { + assert::valid_444(src_y, src_stride_y, src_u, src_stride_u, src_v, src_stride_v, width, height); + assert::valid_rgb(dst_rgb24, dst_stride_rgb24, width, height); + + let height = height as i32 * if flip_y { -1 } else { 1 }; + + assert!(unsafe { + yuv_sys::rs_I444ToRGB24( + src_y.as_ptr(), + src_stride_y as i32, + src_u.as_ptr(), + src_stride_u as i32, + src_v.as_ptr(), + src_stride_v as i32, + dst_rgb24.as_mut_ptr(), + dst_stride_rgb24 as i32, + width as i32, + height, + ) == 0 + }); +} + +macro_rules! x444_to_rgba { + ($rust_fnc:ident, $yuv_sys_fnc:ident) => { + pub fn $rust_fnc( + src_y: &[u8], + src_stride_y: u32, + src_u: &[u8], + src_stride_u: u32, + src_v: &[u8], + src_stride_v: u32, + dst_rgba: &mut [u8], + dst_stride_rgba: u32, + width: u32, + height: u32, + flip_y: bool, + ) { + assert::valid_444( + src_y, + src_stride_y, + src_u, + src_stride_u, + src_v, + src_stride_v, + width, + height, + ); + assert::valid_rgba(dst_rgba, dst_stride_rgba, width, height); + + let height = height as i32 * if flip_y { -1 } else { 1 }; + + assert!(unsafe { + yuv_sys::$yuv_sys_fnc( + src_y.as_ptr(), + src_stride_y as i32, + src_u.as_ptr(), + src_stride_u as i32, + src_v.as_ptr(), + src_stride_v as i32, + dst_rgba.as_mut_ptr(), + dst_stride_rgba as i32, + width as i32, + height, + ) == 0 + }); + } + }; +} + +x444_to_rgba!(i444_to_abgr, rs_I444ToABGR); +x444_to_rgba!(i444_to_argb, rs_I444ToARGB); + +macro_rules! x010_to_rgba { + ($rust_fnc:ident, $yuv_sys_fnc:ident) => { + pub fn $rust_fnc( + src_y: &[u16], + src_stride_y: u32, + src_u: &[u16], + src_stride_u: u32, + src_v: &[u16], + src_stride_v: u32, + dst_abgr: &mut [u8], + dst_stride_abgr: u32, + width: u32, + height: u32, + flip_y: bool, + ) { + assert::valid_010( + src_y, + src_stride_y, + src_u, + src_stride_u, + src_v, + src_stride_v, + width, + height, + ); + assert::valid_rgba(dst_abgr, dst_stride_abgr, width, height); + + let height = height as i32 * if flip_y { -1 } else { 1 }; + + assert!(unsafe { + yuv_sys::$yuv_sys_fnc( + src_y.as_ptr(), + src_stride_y as i32, + src_u.as_ptr(), + src_stride_u as i32, + src_v.as_ptr(), + src_stride_v as i32, + dst_abgr.as_mut_ptr(), + dst_stride_abgr as i32, + width as i32, + height, + ) == 0 + }); + } + }; +} + +x010_to_rgba!(i010_to_abgr, rs_I010ToABGR); +x010_to_rgba!(i010_to_argb, rs_I010ToARGB); + +pub fn nv12_to_raw( + src_y: &[u8], + src_stride_y: u32, + src_uv: &[u8], + src_stride_uv: u32, + dst_raw: &mut [u8], + dst_stride_raw: u32, + width: u32, + height: u32, + flip_y: bool, +) { + assert::valid_nv12(src_y, src_stride_y, src_uv, src_stride_uv, width, height); + assert::valid_rgb(dst_raw, dst_stride_raw, width, height); + + let height = height as i32 * if flip_y { -1 } else { 1 }; + + assert!(unsafe { + yuv_sys::rs_NV12ToRAW( + src_y.as_ptr(), + src_stride_y as i32, + src_uv.as_ptr(), + src_stride_uv as i32, + dst_raw.as_mut_ptr(), + dst_stride_raw as i32, + width as i32, + height, + ) == 0 + }); +} + +pub fn nv12_to_rgb24( + src_y: &[u8], + src_stride_y: u32, + src_uv: &[u8], + src_stride_uv: u32, + dst_rgb24: &mut [u8], + dst_stride_rgb24: u32, + width: u32, + height: u32, + flip_y: bool, +) { + assert::valid_nv12(src_y, src_stride_y, src_uv, src_stride_uv, width, height); + assert::valid_rgb(dst_rgb24, dst_stride_rgb24, width, height); + + let height = height as i32 * if flip_y { -1 } else { 1 }; + + assert!(unsafe { + yuv_sys::rs_NV12ToRGB24( + src_y.as_ptr(), + src_stride_y as i32, + src_uv.as_ptr(), + src_stride_uv as i32, + dst_rgb24.as_mut_ptr(), + dst_stride_rgb24 as i32, + width as i32, + height, + ) == 0 + }); +} + +macro_rules! nv12_to_rgba { + ($rust_fnc:ident, $yuv_sys_fnc:ident) => { + pub fn $rust_fnc( + src_y: &[u8], + src_stride_y: u32, + src_uv: &[u8], + src_stride_uv: u32, + dst_rgba: &mut [u8], + dst_stride_rgba: u32, + width: u32, + height: u32, + flip_y: bool, + ) { + assert::valid_nv12(src_y, src_stride_y, src_uv, src_stride_uv, width, height); + assert::valid_rgba(dst_rgba, dst_stride_rgba, width, height); + + let height = height as i32 * if flip_y { -1 } else { 1 }; + + assert!(unsafe { + yuv_sys::$yuv_sys_fnc( + src_y.as_ptr(), + src_stride_y as i32, + src_uv.as_ptr(), + src_stride_uv as i32, + dst_rgba.as_mut_ptr(), + dst_stride_rgba as i32, + width as i32, + height, + ) == 0 + }); + } + }; +} + +nv12_to_rgba!(nv12_to_abgr, rs_NV12ToABGR); +nv12_to_rgba!(nv12_to_argb, rs_NV12ToARGB); + +pub fn i420_copy( + src_y: &[u8], + src_stride_y: u32, + src_u: &[u8], + src_stride_u: u32, + src_v: &[u8], + src_stride_v: u32, + dst_y: &mut [u8], + dst_stride_y: u32, + dst_u: &mut [u8], + dst_stride_u: u32, + dst_v: &mut [u8], + dst_stride_v: u32, + width: u32, + height: u32, + flip_y: bool, +) { + assert::valid_420(src_y, src_stride_y, src_u, src_stride_u, src_v, src_stride_v, width, height); + assert::valid_420(dst_y, dst_stride_y, dst_u, dst_stride_u, dst_v, dst_stride_v, width, height); + + let height = height as i32 * if flip_y { -1 } else { 1 }; + + assert!(unsafe { + yuv_sys::rs_I420Copy( + src_y.as_ptr(), + src_stride_y as i32, + src_u.as_ptr(), + src_stride_u as i32, + src_v.as_ptr(), + src_stride_v as i32, + dst_y.as_mut_ptr(), + dst_stride_y as i32, + dst_u.as_mut_ptr(), + dst_stride_u as i32, + dst_v.as_mut_ptr(), + dst_stride_v as i32, + width as i32, + height, + ) == 0 + }); +} + +pub fn i420a_copy( + src_y: &[u8], + src_stride_y: u32, + src_u: &[u8], + src_stride_u: u32, + src_v: &[u8], + src_stride_v: u32, + src_a: &[u8], + src_stride_a: u32, + dst_y: &mut [u8], + dst_stride_y: u32, + dst_u: &mut [u8], + dst_stride_u: u32, + dst_v: &mut [u8], + dst_stride_v: u32, + dst_a: &mut [u8], + dst_stride_a: u32, + width: u32, + height: u32, + flip_y: bool, +) { + assert::valid_420a( + src_y, + src_stride_y, + src_u, + src_stride_u, + src_v, + src_stride_v, + src_a, + src_stride_a, + width, + height, + ); + + assert::valid_420a( + dst_y, + dst_stride_y, + dst_u, + dst_stride_u, + dst_v, + dst_stride_v, + dst_a, + dst_stride_a, + width, + height, + ); + + i420_copy( + src_y, + src_stride_y, + src_u, + src_stride_u, + src_v, + src_stride_v, + dst_y, + dst_stride_y, + dst_u, + dst_stride_u, + dst_v, + dst_stride_v, + width, + height, + flip_y, + ); + + let height = height as i32 * if flip_y { -1 } else { 1 }; + + unsafe { + yuv_sys::rs_CopyPlane( + src_a.as_ptr(), + src_stride_a as i32, + dst_a.as_mut_ptr(), + dst_stride_a as i32, + width as i32, + height, + ) + } +} + +pub fn i422_copy( + src_y: &[u8], + src_stride_y: u32, + src_u: &[u8], + src_stride_u: u32, + src_v: &[u8], + src_stride_v: u32, + dst_y: &mut [u8], + dst_stride_y: u32, + dst_u: &mut [u8], + dst_stride_u: u32, + dst_v: &mut [u8], + dst_stride_v: u32, + width: u32, + height: u32, + flip_y: bool, +) { + assert::valid_422(src_y, src_stride_y, src_u, src_stride_u, src_v, src_stride_v, width, height); + assert::valid_422(dst_y, dst_stride_y, dst_u, dst_stride_u, dst_v, dst_stride_v, width, height); + + let height = height as i32 * if flip_y { -1 } else { 1 }; + + assert!(unsafe { + yuv_sys::rs_I422Copy( + src_y.as_ptr(), + src_stride_y as i32, + src_u.as_ptr(), + src_stride_u as i32, + src_v.as_ptr(), + src_stride_v as i32, + dst_y.as_mut_ptr(), + dst_stride_y as i32, + dst_u.as_mut_ptr(), + dst_stride_u as i32, + dst_v.as_mut_ptr(), + dst_stride_v as i32, + width as i32, + height, + ) == 0 + }); +} + +pub fn i444_copy( + src_y: &[u8], + src_stride_y: u32, + src_u: &[u8], + src_stride_u: u32, + src_v: &[u8], + src_stride_v: u32, + dst_y: &mut [u8], + dst_stride_y: u32, + dst_u: &mut [u8], + dst_stride_u: u32, + dst_v: &mut [u8], + dst_stride_v: u32, + width: u32, + height: u32, + flip_y: bool, +) { + assert::valid_444(src_y, src_stride_y, src_u, src_stride_u, src_v, src_stride_v, width, height); + assert::valid_444(dst_y, dst_stride_y, dst_u, dst_stride_u, dst_v, dst_stride_v, width, height); + + let height = height as i32 * if flip_y { -1 } else { 1 }; + + assert!(unsafe { + yuv_sys::rs_I444Copy( + src_y.as_ptr(), + src_stride_y as i32, + src_u.as_ptr(), + src_stride_u as i32, + src_v.as_ptr(), + src_stride_v as i32, + dst_y.as_mut_ptr(), + dst_stride_y as i32, + dst_u.as_mut_ptr(), + dst_stride_u as i32, + dst_v.as_mut_ptr(), + dst_stride_v as i32, + width as i32, + height, + ) == 0 + }); +} + +pub fn i010_copy( + src_y: &[u16], + src_stride_y: u32, + src_u: &[u16], + src_stride_u: u32, + src_v: &[u16], + src_stride_v: u32, + dst_y: &mut [u16], + dst_stride_y: u32, + dst_u: &mut [u16], + dst_stride_u: u32, + dst_v: &mut [u16], + dst_stride_v: u32, + width: u32, + height: u32, + flip_y: bool, +) { + assert::valid_010(src_y, src_stride_y, src_u, src_stride_u, src_v, src_stride_v, width, height); + assert::valid_010(dst_y, dst_stride_y, dst_u, dst_stride_u, dst_v, dst_stride_v, width, height); + + let height = height as i32 * if flip_y { -1 } else { 1 }; + + assert!(unsafe { + yuv_sys::rs_I010Copy( + src_y.as_ptr(), + src_stride_y as i32, + src_u.as_ptr(), + src_stride_u as i32, + src_v.as_ptr(), + src_stride_v as i32, + dst_y.as_mut_ptr(), + dst_stride_y as i32, + dst_u.as_mut_ptr(), + dst_stride_u as i32, + dst_v.as_mut_ptr(), + dst_stride_v as i32, + width as i32, + height, + ) == 0 + }); +} + +pub fn nv12_copy( + src_y: &[u8], + src_stride_y: u32, + src_uv: &[u8], + src_stride_uv: u32, + dst_y: &mut [u8], + dst_stride_y: u32, + dst_uv: &mut [u8], + dst_stride_uv: u32, + width: u32, + height: u32, + flip_y: bool, +) { + assert::valid_nv12(src_y, src_stride_y, src_uv, src_stride_uv, width, height); + assert::valid_nv12(dst_y, dst_stride_y, dst_uv, dst_stride_uv, width, height); + + let height = height as i32 * if flip_y { -1 } else { 1 }; + + assert!( + unsafe { + yuv_sys::rs_NV12Copy( + src_y.as_ptr(), + src_stride_y as i32, + src_uv.as_ptr(), + src_stride_uv as i32, + dst_y.as_mut_ptr(), + dst_stride_y as i32, + dst_uv.as_mut_ptr(), + dst_stride_uv as i32, + width as i32, + height, + ) + } == 0 + ); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_data() { + const WIDTH: usize = 160; + const HEIGHT: usize = 90; + + let dst_abgr = &mut [0u8; WIDTH * HEIGHT * 4]; + let src_y = &[0u8; WIDTH * HEIGHT]; + let src_u = &[0u8; WIDTH * HEIGHT + 1 / 2]; + let src_v = &[0u8; WIDTH * HEIGHT + 1 / 2]; + + i420_to_abgr( + src_y, + WIDTH as u32, + src_u, + WIDTH as u32 + 1 / 2, + src_v, + WIDTH as u32 + 1 / 2, + dst_abgr, + WIDTH as u32 * 4, + WIDTH as u32, + HEIGHT as u32, + false, + ); + } +} diff --git a/imgproc/src/lib.rs b/imgproc/src/lib.rs new file mode 100644 index 000000000..68b896203 --- /dev/null +++ b/imgproc/src/lib.rs @@ -0,0 +1 @@ +pub mod colorcvt; diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 01d743237..b7cab7fda 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -20,6 +20,7 @@ tracing = ["tokio/tracing", "console-subscriber"] [dependencies] livekit = { workspace = true } soxr-sys = { path = "../soxr-sys" } +imgproc = { path = "../imgproc" } livekit-protocol = { workspace = true } tokio = { version = "1", features = ["full", "parking_lot"] } futures-util = { version = "0.3", default-features = false, features = ["sink"] } @@ -33,7 +34,6 @@ dashmap = "5.4" env_logger = "0.10" downcast-rs = "1.2" console-subscriber = { version = "0.1", features = ["parking_lot"], optional = true } -imgproc = "0.3.11" [target.'cfg(target_os = "android")'.dependencies] jni = "0.21.1" diff --git a/yuv-sys/Cargo.lock b/yuv-sys/Cargo.lock new file mode 100644 index 000000000..2cda4831d --- /dev/null +++ b/yuv-sys/Cargo.lock @@ -0,0 +1,479 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "bindgen" +version = "0.69.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ffcebc3849946a7170a05992aac39da343a90676ab392c51a4280981d6379c2" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", + "which", +] + +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clang-sys" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" + +[[package]] +name = "libloading" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "prettyplease" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rayon" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustix" +version = "0.38.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "shlex" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" + +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + +[[package]] +name = "yuv-sys" +version = "0.3.5" +dependencies = [ + "bindgen", + "cc", + "rayon", + "regex", +] diff --git a/yuv-sys/Cargo.toml b/yuv-sys/Cargo.toml new file mode 100644 index 000000000..384815730 --- /dev/null +++ b/yuv-sys/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "yuv-sys" +version = "0.3.6" +edition = "2021" +authors = ["Theo Monnom "] +license = "MIT OR Apache-2.0" +description = "libyuv bindings" + +[build-dependencies] +bindgen = "0.69" +cc = "1.0" +regex = "1" +rayon = "1.8" diff --git a/yuv-sys/build.rs b/yuv-sys/build.rs new file mode 100644 index 000000000..34a936108 --- /dev/null +++ b/yuv-sys/build.rs @@ -0,0 +1,143 @@ +use cc; +use rayon::prelude::*; +use regex::Regex; +use std::borrow::Cow; +use std::path::Path; +use std::{env, path::PathBuf}; +use std::{fs, io}; + +//const LIBYUV_REPO: &str = "https://chromium.googlesource.com/libyuv/libyuv"; +//const LIBYUV_COMMIT: &str = "af6ac82"; +const FNC_PREFIX: &str = "rs_"; + +/*fn run_git_cmd(current_dir: &PathBuf, args: &[&str]) -> ExitStatus { + Command::new("git") + .current_dir(current_dir) + .args(args) + .status() + .unwrap() +}*/ + +fn rename_symbols( + fnc_list: &[&str], + include_files: &[fs::DirEntry], + source_files: &[fs::DirEntry], +) { + // Find all occurences of the function in every header and source files + // and prefix it with FNC_PREFIX + include_files.par_iter().chain(source_files).for_each(|file| { + let mut content = fs::read_to_string(&file.path()).unwrap(); + for line in fnc_list { + let fnc = line.trim(); + if fnc.is_empty() { + continue; + } + + // Split line using space as delimiter (If there is two words, the second word is the new name instead of using prefix) + let split: Vec<&str> = fnc.split_whitespace().collect(); + let fnc = split[0]; + + let new_name = if split.len() > 1 { + split[1].to_owned() + } else { + format!("{}{}", FNC_PREFIX, fnc) + }; + + let re = Regex::new(&format!(r"\b{}\b", fnc)).unwrap(); + if let Cow::Owned(c) = re.replace_all(&content, &new_name) { + content = c + } + } + + fs::write(&file.path(), content.to_string()).unwrap(); + }); +} + +fn copy_dir(source: impl AsRef, destination: impl AsRef) -> io::Result<()> { + fs::create_dir_all(&destination)?; + for entry in fs::read_dir(source)? { + let entry = entry?; + if entry.file_type()?.is_dir() { + copy_dir(entry.path(), destination.as_ref().join(entry.file_name()))?; + } else { + fs::copy(entry.path(), destination.as_ref().join(entry.file_name()))?; + } + } + Ok(()) +} + +fn clone_if_needed(_output_dir: &PathBuf, libyuv_dir: &PathBuf) -> bool { + if libyuv_dir.exists() { + return false; // Already cloned + } + + /*let status = run_git_cmd(output_dir, &["clone", LIBYUV_REPO]); + if !status.success() { + fs::remove_dir_all(&libyuv_dir).unwrap(); + panic!("failed to clone libyuv, is git installed?"); + } + + let status = run_git_cmd(&libyuv_dir, &["checkout", LIBYUV_COMMIT]); + if !status.success() { + fs::remove_dir_all(&libyuv_dir).unwrap(); + panic!("failed to checkout to {}", LIBYUV_COMMIT); + }*/ + + if let Err(err) = copy_dir("libyuv", libyuv_dir) { + fs::remove_dir_all(&libyuv_dir).unwrap(); + panic!("failed to copy libyuv: {:?}", err); + } + + true +} + +fn main() { + let output_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + let libyuv_dir = output_dir.join("libyuv"); + let include_dir = libyuv_dir.join("include"); + let source_dir = libyuv_dir.join("source"); + + let cloned = clone_if_needed(&output_dir, &libyuv_dir); + + let include_files = fs::read_dir(include_dir.join("libyuv")) + .unwrap() + .map(Result::unwrap) + .filter(|f| f.path().extension().unwrap() == "h") + .collect::>(); + + let source_files = fs::read_dir(source_dir) + .unwrap() + .map(Result::unwrap) + .filter(|f| f.path().extension().unwrap() == "cc") + .collect::>(); + + let fnc_content = fs::read_to_string("yuv_functions.txt").unwrap(); + let fnc_list = fnc_content.lines().collect::>(); + + if cloned { + // Rename symbols to avoid conflicts with other libraries + // that have libyuv statically linked (e.g libwebrtc). + rename_symbols(&fnc_list, &include_files, &source_files); + } + + cc::Build::new() + .warnings(false) + .include(libyuv_dir.join("include")) + .files(source_files.iter().map(|f| f.path())) + .compile("yuv"); + + let mut bindgen = bindgen::Builder::default() + .header(include_dir.join("libyuv.h").to_string_lossy()) + .clang_arg(format!("-I{}", include_dir.to_str().unwrap())); + + for fnc in fnc_list { + let new_name = format!("{}{}", FNC_PREFIX, fnc); + bindgen = bindgen.allowlist_function(&new_name); + } + + let output = bindgen.generate().unwrap(); + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("yuv.rs"); + output.write_to_file(out_path).unwrap(); + + println!("cargo:rerun-if-changed=yuv_functions.txt"); +} diff --git a/yuv-sys/libyuv b/yuv-sys/libyuv new file mode 160000 index 000000000..3a0ad00ed --- /dev/null +++ b/yuv-sys/libyuv @@ -0,0 +1 @@ +Subproject commit 3a0ad00ed34afe3a43eb742579d53e9e7c597ae3 diff --git a/yuv-sys/src/lib.rs b/yuv-sys/src/lib.rs new file mode 100644 index 000000000..6839d5eae --- /dev/null +++ b/yuv-sys/src/lib.rs @@ -0,0 +1,6 @@ +#![allow(non_camel_case_types)] +#![allow(non_upper_case_globals)] +#![allow(non_snake_case)] +#![allow(dead_code)] + +include!(concat!(env!("OUT_DIR"), "/yuv.rs")); diff --git a/yuv-sys/yuv_functions.txt b/yuv-sys/yuv_functions.txt new file mode 100644 index 000000000..cecd256ba --- /dev/null +++ b/yuv-sys/yuv_functions.txt @@ -0,0 +1,1193 @@ +NAME##ToYRow_C rs_##NAME##ToYRow_C +NAME##ToYJRow_C rs_##NAME##ToYJRow_C +NAME##ToUVRow_C rs_##NAME##ToUVRow_C +NAME##ToUVJRow_C rs_##NAME##ToUVJRow_C +kYuv##name##Constants +kYvu##name##Constants +ScalePlane +ScalePlane_16 +ScalePlane_12 +I420Scale +I420Scale_16 +I420Scale_12 +I444Scale +I444Scale_16 +I444Scale_12 +I422Scale +I422Scale_16 +I422Scale_12 +NV12Scale +Scale +CopyPlane +CopyPlane_16 +Convert16To8Plane +Convert8To16Plane +SetPlane +DetilePlane +DetilePlane_16 +DetileSplitUVPlane +DetileToYUY2 +SplitUVPlane +MergeUVPlane +SplitUVPlane_16 +MergeUVPlane_16 +ConvertToMSBPlane_16 +ConvertToLSBPlane_16 +HalfMergeUVPlane +SwapUVPlane +SplitRGBPlane +MergeRGBPlane +SplitARGBPlane +MergeARGBPlane +MergeXR30Plane +MergeAR64Plane +MergeARGB16To8Plane +I400ToI400 +I422Copy +I444Copy +I210Copy +I410Copy +NV12Copy +NV21Copy +YUY2ToI422 +UYVYToI422 +YUY2ToNV12 +UYVYToNV12 +NV21ToNV12 +YUY2ToY +UYVYToY +I420ToI400 +I420Mirror +I400Mirror +NV12Mirror +ARGBMirror +RGB24Mirror +MirrorPlane +MirrorUVPlane +RAWToRGB24 +I420Rect +ARGBRect +ARGBGrayTo +ARGBGray +ARGBSepia +ARGBColorMatrix +RGBColorMatrix +ARGBColorTable +RGBColorTable +ARGBLumaColorTable +ARGBPolynomial +HalfFloatPlane +ByteToFloat +ARGBQuantize +ARGBCopy +ARGBCopyAlpha +ARGBExtractAlpha +ARGBCopyYToAlpha +ARGBBlend +BlendPlane +I420Blend +ARGBMultiply +ARGBAdd +ARGBSubtract +I422ToYUY2 +I422ToUYVY +ARGBAttenuate +ARGBUnattenuate +ARGBComputeCumulativeSum +ARGBBlur +GaussPlane_F32 +ARGBShade +InterpolatePlane +InterpolatePlane_16 +ARGBInterpolate +I420Interpolate +ARGBAffineRow_C +ARGBAffineRow_SSE2 +ARGBShuffle +AR64Shuffle +ARGBSobelToPlane +ARGBSobel +ARGBSobelXY +ARGBScale +ARGBScaleClip +YUVToARGBScaleClip +HashDjb2 +ComputeHammingDistance +ARGBDetect +ComputeSumSquareError +ComputeSumSquareErrorPlane +SumSquareErrorToPsnr +CalcFramePsnr +I420Psnr +CalcFrameSsim +I420Ssim +I420ToARGB +I420ToABGR +J420ToARGB +J420ToABGR +H420ToARGB +H420ToABGR +U420ToARGB +U420ToABGR +I422ToARGB +I422ToABGR +J422ToARGB +J422ToABGR +H422ToARGB +H422ToABGR +U422ToARGB +U422ToABGR +I444ToARGB +I444ToABGR +J444ToARGB +J444ToABGR +H444ToARGB +H444ToABGR +U444ToARGB +U444ToABGR +I444ToRGB24 +I444ToRAW +I010ToARGB +I010ToABGR +H010ToARGB +H010ToABGR +U010ToARGB +U010ToABGR +I210ToARGB +I210ToABGR +H210ToARGB +H210ToABGR +U210ToARGB +U210ToABGR +I420AlphaToARGB +I420AlphaToABGR +I422AlphaToARGB +I422AlphaToABGR +I444AlphaToARGB +I444AlphaToABGR +I400ToARGB +J400ToARGB +NV12ToARGB +NV21ToARGB +NV12ToABGR +NV21ToABGR +NV12ToRGB24 +NV21ToRGB24 +NV21ToYUV24 +NV12ToRAW +NV21ToRAW +YUY2ToARGB +UYVYToARGB +I010ToAR30 +H010ToAR30 +I010ToAB30 +H010ToAB30 +U010ToAR30 +U010ToAB30 +I210ToAR30 +I210ToAB30 +H210ToAR30 +H210ToAB30 +U210ToAR30 +U210ToAB30 +BGRAToARGB +ABGRToARGB +RGBAToARGB +RGB24ToARGB +RAWToARGB +RAWToRGBA +RGB565ToARGB +ARGB1555ToARGB +ARGB4444ToARGB +AR30ToARGB +AR30ToABGR +AR30ToAB30 +AR64ToARGB +AB64ToARGB +AR64ToAB64 +MJPGToARGB +Android420ToARGB +Android420ToABGR +NV12ToRGB565 +I422ToBGRA +I422ToRGBA +I420ToBGRA +I420ToRGBA +I420ToRGB24 +I420ToRAW +H420ToRGB24 +H420ToRAW +J420ToRGB24 +J420ToRAW +I422ToRGB24 +I422ToRAW +I420ToRGB565 +J420ToRGB565 +H420ToRGB565 +I422ToRGB565 +I420ToRGB565Dither +I420ToARGB1555 +I420ToARGB4444 +I420ToAR30 +I420ToAB30 +H420ToAR30 +H420ToAB30 +I420ToARGBMatrix +I422ToARGBMatrix +I444ToARGBMatrix +I444ToRGB24Matrix +I010ToAR30Matrix +I210ToAR30Matrix +I410ToAR30Matrix +I010ToARGBMatrix +I012ToAR30Matrix +I012ToARGBMatrix +I210ToARGBMatrix +I410ToARGBMatrix +P010ToARGBMatrix +P210ToARGBMatrix +P010ToAR30Matrix +P210ToAR30Matrix +I420AlphaToARGBMatrix +I422AlphaToARGBMatrix +I444AlphaToARGBMatrix +I010AlphaToARGBMatrix +I210AlphaToARGBMatrix +I410AlphaToARGBMatrix +NV12ToARGBMatrix +NV21ToARGBMatrix +NV12ToRGB565Matrix +NV12ToRGB24Matrix +NV21ToRGB24Matrix +Android420ToARGBMatrix +I422ToRGBAMatrix +I420ToRGBAMatrix +I420ToRGB24Matrix +I422ToRGB24Matrix +I420ToRGB565Matrix +I422ToRGB565Matrix +I420ToAR30Matrix +I400ToARGBMatrix +I420ToARGBMatrixFilter +I422ToARGBMatrixFilter +I422ToRGB24MatrixFilter +I420ToRGB24MatrixFilter +I010ToAR30MatrixFilter +I210ToAR30MatrixFilter +I010ToARGBMatrixFilter +I210ToARGBMatrixFilter +I420AlphaToARGBMatrixFilter +I422AlphaToARGBMatrixFilter +I010AlphaToARGBMatrixFilter +I210AlphaToARGBMatrixFilter +P010ToARGBMatrixFilter +P210ToARGBMatrixFilter +P010ToAR30MatrixFilter +P210ToAR30MatrixFilter +ConvertToARGB +I420Rotate +I422Rotate +I444Rotate +I010Rotate +I210Rotate +I410Rotate +NV12ToI420Rotate +Android420ToI420Rotate +RotatePlane +RotatePlane90 +RotatePlane180 +RotatePlane270 +RotatePlane_16 +SplitRotateUV +SplitRotateUV90 +SplitRotateUV180 +SplitRotateUV270 +TransposePlane +SplitTransposeUV +ARGBToBGRA +ARGBToABGR +ARGBToRGBA +ABGRToAR30 +ARGBToAR30 +ARGBToRGB24 +ARGBToRAW +ARGBToRGB565 +ARGBToRGB565Dither +ARGBToARGB1555 +ARGBToARGB4444 +ARGBToI444 +ARGBToAR64 +ARGBToAB64 +ARGBToI422 +ARGBToJ420 +ARGBToJ422 +ARGBToJ400 +ABGRToJ420 +ABGRToJ422 +ABGRToJ400 +RGBAToJ400 +ARGBToI400 +ARGBToG +ARGBToNV12 +ARGBToNV21 +ABGRToNV12 +ABGRToNV21 +ARGBToYUY2 +ARGBToUYVY +RAWToJNV21 +UVScale +UVScale_16 +I420ToI010 +I420ToI012 +I420ToI422 +I420ToI444 +I400Copy +I420ToNV12 +I420ToNV21 +I420ToYUY2 +I420ToUYVY +ConvertFromI420 +I444ToI420 +I444ToNV12 +I444ToNV21 +I422ToI420 +I422ToI444 +I422ToI210 +MM21ToNV12 +MM21ToI420 +MM21ToYUY2 +MT2TToP010 +I422ToNV21 +I420Copy +I010Copy +I010ToI420 +I210ToI420 +I210ToI422 +I410ToI420 +I410ToI444 +I012ToI420 +I212ToI422 +I212ToI420 +I412ToI444 +I412ToI420 +I410ToI010 +I210ToI010 +I010ToI410 +I210ToI410 +I010ToP010 +I210ToP210 +I012ToP012 +I212ToP212 +I400ToI420 +I400ToNV21 +NV12ToI420 +NV21ToI420 +NV12ToNV24 +NV16ToNV24 +P010ToI010 +P012ToI012 +P010ToP410 +P210ToP410 +YUY2ToI420 +UYVYToI420 +AYUVToNV12 +AYUVToNV21 +Android420ToI420 +ARGBToI420 +ARGBToI420Alpha +BGRAToI420 +ABGRToI420 +RGBAToI420 +RGB24ToI420 +RGB24ToJ420 +RAWToI420 +RAWToJ420 +RGB565ToI420 +ARGB1555ToI420 +ARGB4444ToI420 +RGB24ToJ400 +RAWToJ400 +MJPGToI420 +MJPGToNV21 +MJPGToNV12 +MJPGSize +ConvertToI420 +ARGBRotate +RGBScale +DetileToYUY2_Any_SSE2 +DetileSplitUVRow_Any_SSSE3 +DetileRow_16_Any_AVX +DetileRow_16_Any_SSE2 +DetileRow_Any_SSE2 +UYVYToUVRow_Any_SSE2 +YUY2ToUVRow_Any_SSE2 +UYVYToUVRow_Any_AVX2 +RGBAToUVRow_Any_SSSE3 +ABGRToUVRow_Any_SSSE3 +BGRAToUVRow_Any_SSSE3 +ARGBToUVRow_Any_SSSE3 +ABGRToUVJRow_Any_SSSE3 +ARGBToUVJRow_Any_SSSE3 +ABGRToUVJRow_Any_AVX2 +ARGBToUVJRow_Any_AVX2 +ABGRToUVRow_Any_AVX2 +ARGBToUVRow_Any_AVX2 +SplitARGBRow_Any_AVX2 +SplitARGBRow_Any_SSSE3 +SplitARGBRow_Any_SSE2 +SplitXRGBRow_Any_AVX2 +SplitXRGBRow_Any_SSSE3 +SplitXRGBRow_Any_SSE2 +SplitRGBRow_Any_SSSE3 +SplitUVRow_16_Any_AVX2 +UYVYToUV422Row_Any_SSE2 +YUY2ToUV422Row_Any_SSE2 +UYVYToUV422Row_Any_AVX2 +YUY2ToUV422Row_Any_AVX2 +ARGBToUV444Row_Any_SSSE3 +SplitUVRow_Any_AVX2 +SplitUVRow_Any_SSE2 +SetRow_Any_X86 +RGB24MirrorRow_Any_SSSE3 +ARGBMirrorRow_Any_SSE2 +ARGBMirrorRow_Any_AVX2 +MirrorUVRow_Any_SSSE3 +MirrorUVRow_Any_AVX2 +MirrorRow_Any_SSSE3 +MirrorRow_Any_AVX2 +InterpolateRow_16To8_Any_AVX2 +InterpolateRow_Any_SSSE3 +InterpolateRow_Any_AVX2 +UYVYToARGBRow_Any_AVX2 +ScalePlaneDown2_16To8 +J400ToARGBRow_SSE2 +RGB24ToARGBRow_SSSE3 +RAWToARGBRow_SSSE3 +RAWToRGBARow_SSSE3 +RAWToRGB24Row_SSSE3 +RGB565ToARGBRow_SSE2 +ARGB1555ToARGBRow_SSE2 +ARGB4444ToARGBRow_SSE2 +ARGBToRGB24Row_SSSE3 +ARGBToRAWRow_SSSE3 +ARGBToRGB24Row_AVX2 +ARGBToRAWRow_AVX2 +ARGBToRGB565Row_SSE2 +ARGBToRGB565DitherRow_SSE2 +ARGBToRGB565DitherRow_AVX2 +ARGBToARGB1555Row_SSE2 +ARGBToARGB4444Row_SSE2 +ARGBToAR30Row_SSSE3 +ABGRToAR30Row_SSSE3 +ARGBToAR30Row_AVX2 +ABGRToAR30Row_AVX2 +ARGBToAR64Row_SSSE3 +ARGBToAB64Row_SSSE3 +AR64ToARGBRow_SSSE3 +AB64ToARGBRow_SSSE3 +ARGBToAR64Row_AVX2 +ARGBToAB64Row_AVX2 +AR64ToARGBRow_AVX2 +AB64ToARGBRow_AVX2 +ARGBToYRow_SSSE3 +ARGBToYJRow_SSSE3 +ABGRToYJRow_SSSE3 +RGBAToYJRow_SSSE3 +ARGBToYRow_AVX2 +ABGRToYRow_AVX2 +ARGBToYJRow_AVX2 +ABGRToYJRow_AVX2 +RGBAToYJRow_AVX2 +ARGBToUVRow_SSSE3 +ARGBToUVRow_AVX2 +ABGRToUVRow_AVX2 +ARGBToUVJRow_AVX2 +ABGRToUVJRow_AVX2 +ARGBToUVJRow_SSSE3 +ABGRToUVJRow_SSSE3 +ARGBToUV444Row_SSSE3 +BGRAToYRow_SSSE3 +BGRAToUVRow_SSSE3 +ABGRToYRow_SSSE3 +RGBAToYRow_SSSE3 +ABGRToUVRow_SSSE3 +RGBAToUVRow_SSSE3 +I444ToARGBRow_SSSE3 +I444AlphaToARGBRow_SSSE3 +I422ToRGB24Row_SSSE3 +I444ToRGB24Row_SSSE3 +I422ToARGBRow_SSSE3 +I422ToAR30Row_SSSE3 +I210ToARGBRow_SSSE3 +I212ToARGBRow_SSSE3 +I210ToAR30Row_SSSE3 +I212ToAR30Row_SSSE3 +I410ToARGBRow_SSSE3 +I210AlphaToARGBRow_SSSE3 +I410AlphaToARGBRow_SSSE3 +I410ToAR30Row_SSSE3 +I422AlphaToARGBRow_SSSE3 +NV12ToARGBRow_SSSE3 +NV21ToARGBRow_SSSE3 +YUY2ToARGBRow_SSSE3 +UYVYToARGBRow_SSSE3 +P210ToARGBRow_SSSE3 +P410ToARGBRow_SSSE3 +P210ToAR30Row_SSSE3 +P410ToAR30Row_SSSE3 +I422ToRGBARow_SSSE3 +I444ToARGBRow_AVX2 +I422ToARGBRow_AVX2 +I422ToAR30Row_AVX2 +I210ToARGBRow_AVX2 +I212ToARGBRow_AVX2 +I210ToAR30Row_AVX2 +I212ToAR30Row_AVX2 +I410ToARGBRow_AVX2 +I210AlphaToARGBRow_AVX2 +I410AlphaToARGBRow_AVX2 +I410ToAR30Row_AVX2 +I444AlphaToARGBRow_AVX2 +I422AlphaToARGBRow_AVX2 +I422ToRGBARow_AVX2 +NV12ToARGBRow_AVX2 +NV21ToARGBRow_AVX2 +YUY2ToARGBRow_AVX2 +UYVYToARGBRow_AVX2 +P210ToARGBRow_AVX2 +P410ToARGBRow_AVX2 +P210ToAR30Row_AVX2 +P410ToAR30Row_AVX2 +I400ToARGBRow_SSE2 +I400ToARGBRow_AVX2 +MirrorRow_SSSE3 +MirrorRow_AVX2 +MirrorUVRow_SSSE3 +MirrorUVRow_AVX2 +MirrorSplitUVRow_SSSE3 +RGB24MirrorRow_SSSE3 +ARGBMirrorRow_SSE2 +ARGBMirrorRow_AVX2 +SplitUVRow_AVX2 +SplitUVRow_SSE2 +DetileRow_SSE2 +DetileRow_16_SSE2 +DetileRow_16_AVX +DetileToYUY2_SSE2 +DetileSplitUVRow_SSSE3 +MergeUVRow_AVX2 +MergeUVRow_SSE2 +MergeUVRow_16_AVX2 +SplitUVRow_16_AVX2 +MultiplyRow_16_AVX2 +DivideRow_16_AVX2 +Convert16To8Row_SSSE3 +Convert16To8Row_AVX2 +Convert8To16Row_SSE2 +Convert8To16Row_AVX2 +SplitRGBRow_SSSE3 +MergeRGBRow_SSSE3 +MergeARGBRow_SSE2 +MergeXRGBRow_SSE2 +MergeARGBRow_AVX2 +MergeXRGBRow_AVX2 +SplitARGBRow_SSE2 +SplitXRGBRow_SSE2 +SplitARGBRow_SSSE3 +SplitXRGBRow_SSSE3 +SplitARGBRow_AVX2 +SplitXRGBRow_AVX2 +MergeXR30Row_AVX2 +MergeAR64Row_AVX2 +MergeXR64Row_AVX2 +MergeARGB16To8Row_AVX2 +MergeXRGB16To8Row_AVX2 +CopyRow_SSE2 +CopyRow_AVX +CopyRow_ERMS +ARGBCopyAlphaRow_SSE2 +ARGBCopyAlphaRow_AVX2 +ARGBExtractAlphaRow_SSE2 +ARGBExtractAlphaRow_AVX2 +ARGBCopyYToAlphaRow_SSE2 +ARGBCopyYToAlphaRow_AVX2 +SetRow_X86 +SetRow_ERMS +ARGBSetRow_X86 +YUY2ToYRow_SSE2 +YUY2ToNVUVRow_SSE2 +YUY2ToUVRow_SSE2 +YUY2ToUV422Row_SSE2 +UYVYToYRow_SSE2 +UYVYToUVRow_SSE2 +UYVYToUV422Row_SSE2 +YUY2ToYRow_AVX2 +YUY2ToNVUVRow_AVX2 +YUY2ToUVRow_AVX2 +YUY2ToUV422Row_AVX2 +UYVYToYRow_AVX2 +UYVYToUVRow_AVX2 +UYVYToUV422Row_AVX2 +ARGBBlendRow_SSSE3 +BlendPlaneRow_SSSE3 +BlendPlaneRow_AVX2 +ARGBAttenuateRow_SSSE3 +ARGBAttenuateRow_AVX2 +ARGBUnattenuateRow_SSE2 +ARGBUnattenuateRow_AVX2 +ARGBGrayRow_SSSE3 +ARGBSepiaRow_SSSE3 +ARGBColorMatrixRow_SSSE3 +ARGBQuantizeRow_SSE2 +ARGBShadeRow_SSE2 +ARGBMultiplyRow_SSE2 +ARGBMultiplyRow_AVX2 +ARGBAddRow_SSE2 +ARGBAddRow_AVX2 +ARGBSubtractRow_SSE2 +ARGBSubtractRow_AVX2 +SobelXRow_SSE2 +SobelYRow_SSE2 +SobelRow_SSE2 +SobelToPlaneRow_SSE2 +SobelXYRow_SSE2 +ComputeCumulativeSumRow_SSE2 +CumulativeSumToAverageRow_SSE2 +InterpolateRow_SSSE3 +InterpolateRow_AVX2 +ARGBShuffleRow_SSSE3 +ARGBShuffleRow_AVX2 +I422ToYUY2Row_SSE2 +I422ToUYVYRow_SSE2 +I422ToYUY2Row_AVX2 +I422ToUYVYRow_AVX2 +ARGBPolynomialRow_SSE2 +ARGBPolynomialRow_AVX2 +HalfFloatRow_SSE2 +HalfFloatRow_AVX2 +ARGBColorTableRow_X86 +RGBColorTableRow_X86 +ARGBLumaColorTableRow_SSSE3 +NV21ToYUV24Row_SSSE3 +NV21ToYUV24Row_AVX2 +SwapUVRow_SSSE3 +SwapUVRow_AVX2 +HalfMergeUVRow_SSSE3 +HalfMergeUVRow_AVX2 +ClampFloatToZero_SSE2 +MergeARGBRow_Any_SSE2 +MergeARGBRow_Any_AVX2 +I444AlphaToARGBRow_Any_SSSE3 +I444AlphaToARGBRow_Any_AVX2 +I422AlphaToARGBRow_Any_SSSE3 +I422AlphaToARGBRow_Any_AVX2 +I210AlphaToARGBRow_Any_SSSE3 +I210AlphaToARGBRow_Any_AVX2 +I410AlphaToARGBRow_Any_SSSE3 +I410AlphaToARGBRow_Any_AVX2 +MergeAR64Row_Any_AVX2 +MergeARGB16To8Row_Any_AVX2 +MergeRGBRow_Any_SSSE3 +MergeXRGBRow_Any_SSE2 +MergeXRGBRow_Any_AVX2 +I422ToYUY2Row_Any_SSE2 +I422ToUYVYRow_Any_SSE2 +I422ToYUY2Row_Any_AVX2 +I422ToUYVYRow_Any_AVX2 +BlendPlaneRow_Any_AVX2 +BlendPlaneRow_Any_SSSE3 +I422ToARGBRow_Any_SSSE3 +I422ToRGBARow_Any_SSSE3 +I422ToARGB4444Row_Any_SSSE3 +I422ToARGB1555Row_Any_SSSE3 +I422ToRGB565Row_Any_SSSE3 +I422ToRGB24Row_Any_SSSE3 +I422ToAR30Row_Any_SSSE3 +I422ToAR30Row_Any_AVX2 +I444ToARGBRow_Any_SSSE3 +I444ToRGB24Row_Any_SSSE3 +I422ToRGB24Row_Any_AVX2 +I422ToARGBRow_Any_AVX2 +I422ToRGBARow_Any_AVX2 +I444ToARGBRow_Any_AVX2 +I444ToRGB24Row_Any_AVX2 +I422ToARGB4444Row_Any_AVX2 +I422ToARGB1555Row_Any_AVX2 +I422ToRGB565Row_Any_AVX2 +I210ToAR30Row_Any_SSSE3 +I210ToARGBRow_Any_SSSE3 +I210ToARGBRow_Any_AVX2 +I210ToAR30Row_Any_AVX2 +I410ToAR30Row_Any_SSSE3 +I410ToARGBRow_Any_SSSE3 +I410ToARGBRow_Any_AVX2 +I410ToAR30Row_Any_AVX2 +I212ToAR30Row_Any_SSSE3 +I212ToARGBRow_Any_SSSE3 +I212ToARGBRow_Any_AVX2 +I212ToAR30Row_Any_AVX2 +MergeXR30Row_Any_AVX2 +MergeXR64Row_Any_AVX2 +MergeXRGB16To8Row_Any_AVX2 +MergeUVRow_Any_SSE2 +MergeUVRow_Any_AVX2 +NV21ToYUV24Row_Any_SSSE3 +NV21ToYUV24Row_Any_AVX2 +ARGBMultiplyRow_Any_SSE2 +ARGBAddRow_Any_SSE2 +ARGBSubtractRow_Any_SSE2 +ARGBMultiplyRow_Any_AVX2 +ARGBAddRow_Any_AVX2 +ARGBSubtractRow_Any_AVX2 +SobelRow_Any_SSE2 +SobelToPlaneRow_Any_SSE2 +SobelXYRow_Any_SSE2 +YUY2ToNVUVRow_Any_SSE2 +YUY2ToNVUVRow_Any_AVX2 +NV12ToARGBRow_Any_SSSE3 +NV12ToARGBRow_Any_AVX2 +NV21ToARGBRow_Any_SSSE3 +NV21ToARGBRow_Any_AVX2 +NV12ToRGB24Row_Any_SSSE3 +NV21ToRGB24Row_Any_SSSE3 +NV12ToRGB24Row_Any_AVX2 +NV21ToRGB24Row_Any_AVX2 +NV12ToRGB565Row_Any_SSSE3 +NV12ToRGB565Row_Any_AVX2 +P210ToAR30Row_Any_SSSE3 +P210ToARGBRow_Any_SSSE3 +P210ToARGBRow_Any_AVX2 +P210ToAR30Row_Any_AVX2 +P410ToAR30Row_Any_SSSE3 +P410ToARGBRow_Any_SSSE3 +P410ToARGBRow_Any_AVX2 +P410ToAR30Row_Any_AVX2 +MergeUVRow_16_Any_AVX2 +CopyRow_Any_AVX +CopyRow_Any_SSE2 +ARGBToRGB24Row_Any_SSSE3 +ARGBToRAWRow_Any_SSSE3 +ARGBToRGB565Row_Any_SSE2 +ARGBToARGB1555Row_Any_SSE2 +ARGBToARGB4444Row_Any_SSE2 +ARGBToRGB24Row_Any_AVX2 +ARGBToRAWRow_Any_AVX2 +ABGRToAR30Row_Any_SSSE3 +ARGBToAR30Row_Any_SSSE3 +ABGRToAR30Row_Any_AVX2 +ARGBToAR30Row_Any_AVX2 +J400ToARGBRow_Any_SSE2 +RGB24ToARGBRow_Any_SSSE3 +RAWToARGBRow_Any_SSSE3 +RGB565ToARGBRow_Any_SSE2 +ARGB1555ToARGBRow_Any_SSE2 +ARGB4444ToARGBRow_Any_SSE2 +RAWToRGBARow_Any_SSSE3 +RAWToRGB24Row_Any_SSSE3 +ARGBToYRow_Any_AVX2 +ABGRToYRow_Any_AVX2 +ARGBToYJRow_Any_AVX2 +ABGRToYJRow_Any_AVX2 +RGBAToYJRow_Any_AVX2 +UYVYToYRow_Any_AVX2 +YUY2ToYRow_Any_AVX2 +ARGBToYRow_Any_SSSE3 +BGRAToYRow_Any_SSSE3 +ABGRToYRow_Any_SSSE3 +RGBAToYRow_Any_SSSE3 +YUY2ToYRow_Any_SSE2 +UYVYToYRow_Any_SSE2 +ARGBToYJRow_Any_SSSE3 +ABGRToYJRow_Any_SSSE3 +RGBAToYJRow_Any_SSSE3 +RGB24ToYJRow_Any_AVX2 +RGB24ToYJRow_Any_SSSE3 +RAWToYJRow_Any_AVX2 +RAWToYJRow_Any_SSSE3 +SwapUVRow_Any_SSSE3 +SwapUVRow_Any_AVX2 +ARGBAttenuateRow_Any_SSSE3 +ARGBUnattenuateRow_Any_SSE2 +ARGBAttenuateRow_Any_AVX2 +ARGBUnattenuateRow_Any_AVX2 +ARGBExtractAlphaRow_Any_SSE2 +ARGBExtractAlphaRow_Any_AVX2 +ARGBCopyAlphaRow_Any_AVX2 +ARGBCopyAlphaRow_Any_SSE2 +ARGBCopyYToAlphaRow_Any_AVX2 +ARGBCopyYToAlphaRow_Any_SSE2 +I400ToARGBRow_Any_SSE2 +I400ToARGBRow_Any_AVX2 +ARGBToRGB565DitherRow_Any_SSE2 +ARGBToRGB565DitherRow_Any_AVX2 +ARGBShuffleRow_Any_SSSE3 +ARGBShuffleRow_Any_AVX2 +ARGBToAR64Row_Any_SSSE3 +ARGBToAB64Row_Any_SSSE3 +AR64ToARGBRow_Any_SSSE3 +AB64ToARGBRow_Any_SSSE3 +ARGBToAR64Row_Any_AVX2 +ARGBToAB64Row_Any_AVX2 +AR64ToARGBRow_Any_AVX2 +AB64ToARGBRow_Any_AVX2 +Convert16To8Row_Any_SSSE3 +Convert16To8Row_Any_AVX2 +Convert8To16Row_Any_SSE2 +Convert8To16Row_Any_AVX2 +MultiplyRow_16_Any_AVX2 +DivideRow_16_Any_AVX2 +HalfFloatRow_Any_SSE2 +HalfFloatRow_Any_AVX2 +YUY2ToARGBRow_Any_SSSE3 +UYVYToARGBRow_Any_SSSE3 +YUY2ToARGBRow_Any_AVX2 +ScaleRowUp2_Linear_Any_C +ScaleRowUp2_Linear_16_Any_C +ScaleRowUp2_Bilinear_Any_C +ScaleRowUp2_Bilinear_16_Any_C +ScaleUVRowUp2_Linear_Any_C +ScaleUVRowUp2_Linear_16_Any_C +ScaleUVRowUp2_Bilinear_Any_C +ScaleUVRowUp2_Bilinear_16_Any_C +RGB24ToARGBRow_C +RAWToARGBRow_C +RAWToRGBARow_C +RAWToRGB24Row_C +RGB565ToARGBRow_C +ARGB1555ToARGBRow_C +ARGB4444ToARGBRow_C +AR30ToARGBRow_C +AR30ToABGRRow_C +AR30ToAB30Row_C +ARGBToRGB24Row_C +ARGBToRAWRow_C +ARGBToRGB565Row_C +ARGBToRGB565DitherRow_C +ARGBToARGB1555Row_C +ARGBToARGB4444Row_C +ABGRToAR30Row_C +ARGBToAR30Row_C +ARGBToAR64Row_C +ARGBToAB64Row_C +AR64ToARGBRow_C +AB64ToARGBRow_C +AR64ShuffleRow_C +ARGBToYRow_C +ARGBToUVRow_C +BGRAToYRow_C +BGRAToUVRow_C +ABGRToYRow_C +ABGRToUVRow_C +RGBAToYRow_C +RGBAToUVRow_C +RGB24ToYRow_C +RGB24ToUVRow_C +RAWToYRow_C +RAWToUVRow_C +ARGBToYJRow_C +ARGBToUVJRow_C +ABGRToYJRow_C +ABGRToUVJRow_C +RGBAToYJRow_C +RGBAToUVJRow_C +RGB24ToYJRow_C +RGB24ToUVJRow_C +RAWToYJRow_C +RAWToUVJRow_C +RGB565ToYRow_C +ARGB1555ToYRow_C +ARGB4444ToYRow_C +RGB565ToUVRow_C +ARGB1555ToUVRow_C +ARGB4444ToUVRow_C +ARGBToUV444Row_C +ARGBGrayRow_C +ARGBSepiaRow_C +ARGBColorMatrixRow_C +ARGBColorTableRow_C +RGBColorTableRow_C +ARGBQuantizeRow_C +ARGBShadeRow_C +ARGBMultiplyRow_C +ARGBAddRow_C +ARGBSubtractRow_C +SobelXRow_C +SobelYRow_C +SobelRow_C +SobelToPlaneRow_C +SobelXYRow_C +J400ToARGBRow_C +I444ToARGBRow_C +I444ToRGB24Row_C +I422ToARGBRow_C +I210ToARGBRow_C +I410ToARGBRow_C +I210AlphaToARGBRow_C +I410AlphaToARGBRow_C +I212ToARGBRow_C +I210ToAR30Row_C +I212ToAR30Row_C +I410ToAR30Row_C +P210ToARGBRow_C +P410ToARGBRow_C +P210ToAR30Row_C +P410ToAR30Row_C +I422ToAR30Row_C +I444AlphaToARGBRow_C +I422AlphaToARGBRow_C +I422ToRGB24Row_C +I422ToARGB4444Row_C +I422ToARGB1555Row_C +I422ToRGB565Row_C +NV12ToARGBRow_C +NV21ToARGBRow_C +NV12ToRGB24Row_C +NV21ToRGB24Row_C +NV12ToRGB565Row_C +YUY2ToARGBRow_C +UYVYToARGBRow_C +I422ToRGBARow_C +I400ToARGBRow_C +MirrorRow_C +MirrorRow_16_C +MirrorUVRow_C +MirrorSplitUVRow_C +ARGBMirrorRow_C +RGB24MirrorRow_C +SplitUVRow_C +MergeUVRow_C +DetileRow_C +DetileRow_16_C +DetileSplitUVRow_C +DetileToYUY2_C +UnpackMT2T_C +SplitRGBRow_C +MergeRGBRow_C +SplitARGBRow_C +MergeARGBRow_C +MergeXR30Row_C +MergeAR64Row_C +MergeARGB16To8Row_C +MergeXR64Row_C +MergeXRGB16To8Row_C +SplitXRGBRow_C +MergeXRGBRow_C +MergeUVRow_16_C +SplitUVRow_16_C +MultiplyRow_16_C +DivideRow_16_C +Convert16To8Row_C +Convert8To16Row_C +CopyRow_C +CopyRow_16_C +SetRow_C +ARGBSetRow_C +YUY2ToUVRow_C +YUY2ToNVUVRow_C +YUY2ToUV422Row_C +YUY2ToYRow_C +UYVYToUVRow_C +UYVYToUV422Row_C +UYVYToYRow_C +ARGBBlendRow_C +BlendPlaneRow_C +ARGBAttenuateRow_C +ARGBUnattenuateRow_C +ComputeCumulativeSumRow_C +CumulativeSumToAverageRow_C +InterpolateRow_C +InterpolateRow_16_C +InterpolateRow_16To8_C +ARGBShuffleRow_C +I422ToYUY2Row_C +I422ToUYVYRow_C +ARGBPolynomialRow_C +HalfFloatRow_C +ByteToFloatRow_C +ARGBLumaColorTableRow_C +ARGBCopyAlphaRow_C +ARGBExtractAlphaRow_C +ARGBCopyYToAlphaRow_C +ScaleSumSamples_C +ScaleMaxSamples_C +ScaleSamples_C +GaussRow_C +GaussCol_C +GaussRow_F32_C +GaussCol_F32_C +NV21ToYUV24Row_C +AYUVToUVRow_C +AYUVToVURow_C +AYUVToYRow_C +SwapUVRow_C +HalfMergeUVRow_C +kYuvI601Constants +kYvuI601Constants +kYuvJPEGConstants +kYvuJPEGConstants +kYuvH709Constants +kYvuH709Constants +kYuvF709Constants +kYvuF709Constants +kYuv2020Constants +kYvu2020Constants +kYuvV2020Constants +kYvuV2020Constants +fixed_invtbl8 +YUY2ToUVRow_Any_AVX2 +ScaleRowDown2_Any_SSSE3 +ScaleRowDown2Linear_Any_SSSE3 +ScaleRowDown2Box_Any_SSSE3 +ScaleRowDown2Box_Odd_SSSE3 +ScaleUVRowDown2Box_Any_SSSE3 +ScaleUVRowDown2Box_Any_AVX2 +ScaleRowDown2_Any_AVX2 +ScaleRowDown2Linear_Any_AVX2 +ScaleRowDown2Box_Any_AVX2 +ScaleRowDown2Box_Odd_AVX2 +ScaleRowDown4_Any_SSSE3 +ScaleRowDown4Box_Any_SSSE3 +ScaleRowDown4_Any_AVX2 +ScaleRowDown4Box_Any_AVX2 +ScaleRowDown34_Any_SSSE3 +ScaleRowDown34_0_Box_Any_SSSE3 +ScaleRowDown34_1_Box_Any_SSSE3 +ScaleRowDown38_Any_SSSE3 +ScaleRowDown38_3_Box_Any_SSSE3 +ScaleRowDown38_2_Box_Any_SSSE3 +ScaleARGBRowDown2_Any_SSE2 +ScaleARGBRowDown2Linear_Any_SSE2 +ScaleARGBRowDown2Box_Any_SSE2 +ScaleARGBRowDownEven_Any_SSE2 +ScaleARGBRowDownEvenBox_Any_SSE2 +ScaleAddRow_Any_SSE2 +ScaleAddRow_Any_AVX2 +ScaleRowUp2_Linear_Any_SSE2 +ScaleRowUp2_Linear_Any_SSSE3 +ScaleRowUp2_Linear_12_Any_SSSE3 +ScaleRowUp2_Linear_16_Any_SSE2 +ScaleRowUp2_Linear_Any_AVX2 +ScaleRowUp2_Linear_12_Any_AVX2 +ScaleRowUp2_Linear_16_Any_AVX2 +ScaleRowUp2_Bilinear_Any_SSE2 +ScaleRowUp2_Bilinear_12_Any_SSSE3 +ScaleRowUp2_Bilinear_16_Any_SSE2 +ScaleRowUp2_Bilinear_Any_SSSE3 +ScaleRowUp2_Bilinear_Any_AVX2 +ScaleRowUp2_Bilinear_12_Any_AVX2 +ScaleRowUp2_Bilinear_16_Any_AVX2 +ScaleUVRowUp2_Linear_Any_SSSE3 +ScaleUVRowUp2_Linear_Any_AVX2 +ScaleUVRowUp2_Linear_16_Any_SSE41 +ScaleUVRowUp2_Linear_16_Any_AVX2 +ScaleUVRowUp2_Bilinear_Any_SSSE3 +ScaleUVRowUp2_Bilinear_Any_AVX2 +ScaleUVRowUp2_Bilinear_16_Any_SSE41 +ScaleUVRowUp2_Bilinear_16_Any_AVX2 +ScaleRowDown2_Any_NEON +ScaleRowDown2Linear_Any_NEON +ScaleRowDown2Box_Any_NEON +ScaleRowDown2Box_Odd_NEON +ScaleUVRowDown2_Any_NEON +ScaleUVRowDown2Linear_Any_NEON +ScaleUVRowDown2Box_Any_NEON +ScaleRowDown4_Any_NEON +ScaleRowDown4Box_Any_NEON +ScaleRowDown34_Any_NEON +ScaleRowDown34_0_Box_Any_NEON +ScaleRowDown34_1_Box_Any_NEON +ScaleRowDown38_Any_NEON +ScaleRowDown38_3_Box_Any_NEON +ScaleRowDown38_2_Box_Any_NEON +ScaleARGBRowDown2_Any_NEON +ScaleARGBRowDown2Linear_Any_NEON +ScaleARGBRowDown2Box_Any_NEON +ScaleARGBRowDownEven_Any_NEON +ScaleARGBRowDownEvenBox_Any_NEON +ScaleRowDown2_Any_NEON +ScaleRowDown2Linear_Any_NEON +ScaleRowDown2Box_Any_NEON +ScaleRowDown2Box_Odd_NEON +ScaleUVRowDown2_Any_NEON +ScaleUVRowDown2Linear_Any_NEON +ScaleUVRowDown2Box_Any_NEON +ScaleRowDown4_Any_NEON +ScaleRowDown4Box_Any_NEON +ScaleRowDown34_Any_NEON +ScaleRowDown34_0_Box_Any_NEON +ScaleRowDown34_1_Box_Any_NEON +ScaleRowDown38_Any_NEON +ScaleRowDown38_3_Box_Any_NEON +ScaleRowDown38_2_Box_Any_NEON +ScaleARGBRowDown2_Any_NEON +ScaleARGBRowDown2Linear_Any_NEON +ScaleARGBRowDown2Box_Any_NEON +ScaleARGBRowDownEven_Any_NEON +ScaleARGBRowDownEvenBox_Any_NEON +ScaleUVRowDownEven_Any_NEON +ScaleAddRow_Any_NEON +ScaleFilterCols_Any_NEON +ScaleARGBCols_Any_NEON +ScaleARGBFilterCols_Any_NEON +ScaleRowUp2_Linear_Any_NEON +ScaleRowUp2_Linear_12_Any_NEON +ScaleRowUp2_Linear_16_Any_NEON +ScaleRowUp2_Bilinear_Any_NEON +ScaleRowUp2_Bilinear_12_Any_NEON +ScaleRowUp2_Bilinear_16_Any_NEON +ScaleUVRowUp2_Linear_Any_NEON +ScaleUVRowUp2_Linear_16_Any_NEON +ScaleUVRowUp2_Bilinear_Any_NEON +ScaleUVRowUp2_Bilinear_16_Any_NEON +I422ToARGBRow_Any_AVX512BWa +MergeUVRow_Any_AVX512BW +ARGBToRGB24Row_Any_AVX512VBMI +YUY2ToUVRow_Any_AVX2 +I422ToRGB565Row_SSSE3 +I422ToARGB1555Row_SSSE3 +I422ToARGB4444Row_SSSE3 +NV12ToRGB565Row_SSSE3 +NV12ToRGB24Row_SSSE3 +NV21ToRGB24Row_SSSE3 +NV12ToRGB24Row_AVX2 +NV21ToRGB24Row_AVX2 +I422ToARGB1555Row_AVX2 +I422ToARGB4444Row_AVX2 +I422ToRGB24Row_AVX2 +I444ToRGB24Row_AVX2 +NV12ToRGB565Row_AVX2 +RGB24ToYJRow_AVX2 +RAWToYJRow_AVX2 +RGB24ToYJRow_SSSE3 +RAWToYJRow_SSSE3 +InterpolateRow_16To8_AVX2 +I422ToARGBRow_Any_AVX512BW +I422ToRGB565Row_SSSE3 +I422ToARGB1555Row_SSSE3 +I422ToARGB4444Row_SSSE3 +NV12ToRGB565Row_SSSE3 +NV12ToRGB24Row_SSSE3 +NV21ToRGB24Row_SSSE3 +NV12ToRGB24Row_AVX2 +NV21ToRGB24Row_AVX2 +I422ToRGB565Row_AVX2 +I422ToARGB1555Row_AVX2 +I422ToARGB4444Row_AVX2 +I422ToRGB24Row_AVX2 +I444ToRGB24Row_AVX2 +NV12ToRGB565Row_AVX2 +RGB24ToYJRow_AVX2 +RAWToYJRow_AVX2 +RGB24ToYJRow_SSSE3 +RAWToYJRow_SSSE3 +InterpolateRow_16To8_AVX2 +ARGBToRGB24Row_AVX512VBMI +I422ToARGBRow_AVX512BW +MergeUVRow_AVX512BW +ARGBToABGRRow_C +ARGBToBGRARow_C +ARGBToRGBARow_C +RGBAToARGBRow_C +AR64ToAB64Row_C +YUY2ToARGBMatrix +UYVYToARGBMatrix From 2ba0a2e7e976f86a5e3f1aeb66685f419741efef Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 10 Dec 2024 13:01:23 -0800 Subject: [PATCH 094/274] Adding disconnect reason (#507) * Update protocol to v1.29.3 * generated protobuf * adding disconnect reason, pass through to FFI used for `ParticipantDisconnected` events --------- Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- Cargo.lock | 3 +- livekit-api/Cargo.toml | 1 + livekit-api/src/services/sip.rs | 123 +- livekit-ffi/protocol/participant.proto | 31 + livekit-ffi/protocol/room.proto | 36 +- livekit-ffi/src/conversion/participant.rs | 24 + livekit-ffi/src/conversion/room.rs | 3 + livekit-ffi/src/livekit.proto.rs | 936 ++++-- livekit-protocol/protocol | 2 +- livekit-protocol/src/livekit.rs | 342 +- livekit-protocol/src/livekit.serde.rs | 2966 +++++++++++++---- livekit/src/prelude.rs | 8 +- livekit/src/proto.rs | 21 + livekit/src/room/mod.rs | 1 + .../src/room/participant/local_participant.rs | 4 + livekit/src/room/participant/mod.rs | 22 + .../room/participant/remote_participant.rs | 4 + 17 files changed, 3520 insertions(+), 1007 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d907dc782..6154cfb8b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -1628,6 +1628,7 @@ dependencies = [ "livekit-runtime", "log", "parking_lot", + "pbjson-types", "prost 0.12.3", "reqwest", "scopeguard", diff --git a/livekit-api/Cargo.toml b/livekit-api/Cargo.toml index 99538ca4a..cca769851 100644 --- a/livekit-api/Cargo.toml +++ b/livekit-api/Cargo.toml @@ -73,6 +73,7 @@ url = "2.3" log = "0.4" parking_lot = { version = "0.12" } prost = "0.12" +pbjson-types = "0.6" # webhooks serde_json = { version = "1.0", optional = true } diff --git a/livekit-api/src/services/sip.rs b/livekit-api/src/services/sip.rs index e2517be9d..aca2d11e9 100644 --- a/livekit-api/src/services/sip.rs +++ b/livekit-api/src/services/sip.rs @@ -14,11 +14,13 @@ use livekit_protocol as proto; use std::collections::HashMap; +use std::time::Duration; use crate::access_token::SIPGrants; use crate::get_env_keys; use crate::services::twirp_client::TwirpClient; use crate::services::{ServiceBase, ServiceResult, LIVEKIT_PACKAGE}; +use pbjson_types::Duration as ProtoDuration; const SVC: &str = "SIP"; @@ -58,18 +60,24 @@ pub struct CreateSIPTrunkOptions { #[derive(Default, Clone, Debug)] pub struct CreateSIPInboundTrunkOptions { /// Optional free-form metadata. - pub metadata: String, + pub metadata: Option, /// CIDR or IPs that traffic is accepted from /// An empty list means all inbound traffic is accepted. - pub allowed_addresses: Vec, + pub allowed_addresses: Option>, /// Accepted `To` values. This Trunk will only accept a call made to /// these numbers. This allows you to have distinct Trunks for different phone /// numbers at the same provider. - pub allowed_numbers: Vec, + pub allowed_numbers: Option>, /// Username and password used to authenticate inbound SIP invites /// May be empty to have no Authentication - pub auth_username: String, - pub auth_password: String, + pub auth_username: Option, + pub auth_password: Option, + pub headers: Option>, + pub headers_to_attributes: Option>, + pub attributes_to_headers: Option>, + pub max_call_duration: Option, + pub ringing_timeout: Option, + pub krisp_enabled: Option, } #[derive(Default, Clone, Debug)] @@ -81,6 +89,10 @@ pub struct CreateSIPOutboundTrunkOptions { /// May be empty to have no Authentication pub auth_username: String, pub auth_password: String, + + pub headers: Option>, + pub headers_to_attributes: Option>, + pub attributes_to_headers: Option>, } #[deprecated] @@ -119,16 +131,21 @@ pub struct CreateSIPParticipantOptions { /// Optional identity of the participant in LiveKit room pub participant_identity: String, /// Optionally set the name of the participant in a LiveKit room - pub participant_name: String, + pub participant_name: Option, /// Optionally set the free-form metadata of the participant in a LiveKit room - pub participant_metadata: String, - pub participant_attributes: HashMap, + pub participant_metadata: Option, + pub participant_attributes: Option>, + // What number should be dialed via SIP + pub sip_number: Option, /// Optionally send following DTMF digits (extension codes) when making a call. /// Character 'w' can be used to add a 0.5 sec delay. - pub dtmf: String, - /// Optionally play ringtone in the room as an audible indicator for existing participants - pub play_ringtone: bool, - pub hide_phone_number: bool, + pub dtmf: Option, + /// Optionally play dialtone in the room as an audible indicator for existing participants + pub play_dialtone: Option, + pub hide_phone_number: Option, + pub ringing_timeout: Option, + pub max_call_duration: Option, + pub enable_krisp: Option, } impl SIPClient { @@ -144,38 +161,8 @@ impl SIPClient { Ok(Self::with_api_key(host, &api_key, &api_secret)) } - #[deprecated] - pub async fn create_sip_trunk( - &self, - number: String, - options: CreateSIPTrunkOptions, - ) -> ServiceResult { - self.client - .request( - SVC, - "CreateSIPTrunk", - proto::CreateSipTrunkRequest { - name: options.name, - metadata: options.metadata, - - outbound_number: number.to_owned(), - outbound_address: options.outbound_address.to_owned(), - outbound_username: options.outbound_username.to_owned(), - outbound_password: options.outbound_password.to_owned(), - - inbound_numbers: options.inbound_numbers.to_owned(), - inbound_numbers_regex: Vec::new(), - inbound_addresses: options.inbound_addresses.to_owned(), - inbound_username: options.inbound_username.to_owned(), - inbound_password: options.inbound_password.to_owned(), - }, - self.base.auth_header( - Default::default(), - Some(SIPGrants { admin: true, ..Default::default() }), - )?, - ) - .await - .map_err(Into::into) + fn duration_to_proto(d: Option) -> Option { + d.map(|d| ProtoDuration { seconds: d.as_secs() as i64, nanos: d.subsec_nanos() as i32 }) } pub async fn create_sip_inbound_trunk( @@ -193,15 +180,17 @@ impl SIPClient { sip_trunk_id: Default::default(), name, numbers, - metadata: options.metadata, - - allowed_numbers: options.allowed_numbers.to_owned(), - allowed_addresses: options.allowed_addresses.to_owned(), - auth_username: options.auth_username.to_owned(), - auth_password: options.auth_password.to_owned(), - - headers: Default::default(), - headers_to_attributes: Default::default(), + metadata: options.metadata.unwrap_or_default(), + allowed_numbers: options.allowed_numbers.unwrap_or_default(), + allowed_addresses: options.allowed_addresses.unwrap_or_default(), + auth_username: options.auth_username.unwrap_or_default(), + auth_password: options.auth_password.unwrap_or_default(), + headers: options.headers.unwrap_or_default(), + headers_to_attributes: options.headers_to_attributes.unwrap_or_default(), + attributes_to_headers: options.attributes_to_headers.unwrap_or_default(), + krisp_enabled: options.krisp_enabled.unwrap_or(false), + max_call_duration: Self::duration_to_proto(options.max_call_duration), + ringing_timeout: Self::duration_to_proto(options.ringing_timeout), }), }, self.base.auth_header( @@ -236,8 +225,9 @@ impl SIPClient { auth_username: options.auth_username.to_owned(), auth_password: options.auth_password.to_owned(), - headers: Default::default(), - headers_to_attributes: Default::default(), + headers: options.headers.unwrap_or_default(), + headers_to_attributes: options.headers_to_attributes.unwrap_or_default(), + attributes_to_headers: options.attributes_to_headers.unwrap_or_default(), }), }, self.base.auth_header( @@ -406,14 +396,25 @@ impl SIPClient { proto::CreateSipParticipantRequest { sip_trunk_id: sip_trunk_id.to_owned(), sip_call_to: call_to.to_owned(), + sip_number: options.sip_number.to_owned().unwrap_or_default(), room_name: room_name.to_owned(), participant_identity: options.participant_identity.to_owned(), - participant_name: options.participant_name.to_owned(), - participant_metadata: options.participant_metadata.to_owned(), - participant_attributes: options.participant_attributes.to_owned(), - dtmf: options.dtmf.to_owned(), - play_ringtone: options.play_ringtone, - hide_phone_number: options.hide_phone_number, + participant_name: options.participant_name.to_owned().unwrap_or_default(), + participant_metadata: options + .participant_metadata + .to_owned() + .unwrap_or_default(), + participant_attributes: options + .participant_attributes + .to_owned() + .unwrap_or_default(), + dtmf: options.dtmf.to_owned().unwrap_or_default(), + play_ringtone: options.play_dialtone.unwrap_or(false), + play_dialtone: options.play_dialtone.unwrap_or(false), + hide_phone_number: options.hide_phone_number.unwrap_or(false), + max_call_duration: Self::duration_to_proto(options.max_call_duration), + ringing_timeout: Self::duration_to_proto(options.ringing_timeout), + enable_krisp: options.enable_krisp.unwrap_or(false), }, self.base.auth_header( Default::default(), diff --git a/livekit-ffi/protocol/participant.proto b/livekit-ffi/protocol/participant.proto index c0a480f2a..3f1e2720c 100644 --- a/livekit-ffi/protocol/participant.proto +++ b/livekit-ffi/protocol/participant.proto @@ -26,6 +26,7 @@ message ParticipantInfo { required string metadata = 4; map attributes = 5; required ParticipantKind kind = 6; + required DisconnectReason disconnect_reason = 7; } message OwnedParticipant { @@ -39,4 +40,34 @@ enum ParticipantKind { PARTICIPANT_KIND_EGRESS = 2; PARTICIPANT_KIND_SIP = 3; PARTICIPANT_KIND_AGENT = 4; +} + +enum DisconnectReason { + UNKNOWN_REASON = 0; + // the client initiated the disconnect + CLIENT_INITIATED = 1; + // another participant with the same identity has joined the room + DUPLICATE_IDENTITY = 2; + // the server instance is shutting down + SERVER_SHUTDOWN = 3; + // RoomService.RemoveParticipant was called + PARTICIPANT_REMOVED = 4; + // RoomService.DeleteRoom was called + ROOM_DELETED = 5; + // the client is attempting to resume a session, but server is not aware of it + STATE_MISMATCH = 6; + // client was unable to connect fully + JOIN_FAILURE = 7; + // Cloud-only, the server requested Participant to migrate the connection elsewhere + MIGRATION = 8; + // the signal websocket was closed unexpectedly + SIGNAL_CLOSE = 9; + // the room was closed, due to all Standard and Ingress participants having left + ROOM_CLOSED = 10; + // SIP callee did not respond in time + USER_UNAVAILABLE = 11; + // SIP callee rejected the call (busy) + USER_REJECTED = 12; + // SIP protocol failure or unexpected response + SIP_TRUNK_FAILURE = 13; } \ No newline at end of file diff --git a/livekit-ffi/protocol/room.proto b/livekit-ffi/protocol/room.proto index edae5c007..4d0f3d92d 100644 --- a/livekit-ffi/protocol/room.proto +++ b/livekit-ffi/protocol/room.proto @@ -53,7 +53,7 @@ message ConnectCallback { string error = 2; Result result = 3; } - + } // Disconnect from the a room @@ -76,7 +76,7 @@ message PublishTrackCallback { string error = 2; OwnedTrackPublication publication = 3; } - + } // Unpublish a track from the room @@ -316,30 +316,6 @@ enum DataPacketKind { KIND_RELIABLE = 1; } -enum DisconnectReason { - UNKNOWN_REASON = 0; - // the client initiated the disconnect - CLIENT_INITIATED = 1; - // another participant with the same identity has joined the room - DUPLICATE_IDENTITY = 2; - // the server instance is shutting down - SERVER_SHUTDOWN = 3; - // RoomService.RemoveParticipant was called - PARTICIPANT_REMOVED = 4; - // RoomService.DeleteRoom was called - ROOM_DELETED = 5; - // the client is attempting to resume a session, but server is not aware of it - STATE_MISMATCH = 6; - // client was unable to connect fully - JOIN_FAILURE = 7; - // Cloud-only, the server requested Participant to migrate the connection elsewhere - MIGRATION = 8; - // the signal websocket was closed unexpectedly - SIGNAL_CLOSE = 9; - // the room was closed, due to all Standard and Ingress participants having left - ROOM_CLOSED = 10; -} - message TranscriptionSegment { required string id = 1; required string text = 2; @@ -407,7 +383,7 @@ message OwnedRoom { message ParticipantConnected { required OwnedParticipant info = 1; } -message ParticipantDisconnected { +message ParticipantDisconnected { required string participant_identity = 1; } @@ -471,7 +447,7 @@ message E2eeStateChanged { message ActiveSpeakersChanged { repeated string participant_identities = 1; } -message RoomMetadataChanged { +message RoomMetadataChanged { required string metadata = 1; } @@ -479,7 +455,7 @@ message RoomSidChanged { required string sid = 1; } -message ParticipantMetadataChanged { +message ParticipantMetadataChanged { required string participant_identity = 1; required string metadata = 2; } @@ -490,7 +466,7 @@ message ParticipantAttributesChanged { repeated AttributesEntry changed_attributes = 3; } -message ParticipantNameChanged { +message ParticipantNameChanged { required string participant_identity = 1; required string name = 2; } diff --git a/livekit-ffi/src/conversion/participant.rs b/livekit-ffi/src/conversion/participant.rs index 755f0bd06..c80453cd9 100644 --- a/livekit-ffi/src/conversion/participant.rs +++ b/livekit-ffi/src/conversion/participant.rs @@ -13,6 +13,7 @@ // limitations under the License. use crate::{proto, server::participant::FfiParticipant}; +use livekit::DisconnectReason; use livekit::ParticipantKind; impl From<&FfiParticipant> for proto::ParticipantInfo { @@ -25,6 +26,8 @@ impl From<&FfiParticipant> for proto::ParticipantInfo { metadata: participant.metadata(), attributes: participant.attributes(), kind: proto::ParticipantKind::from(participant.kind()).into(), + disconnect_reason: proto::DisconnectReason::from(participant.disconnect_reason()) + .into(), } } } @@ -40,3 +43,24 @@ impl From for proto::ParticipantKind { } } } + +impl From for proto::DisconnectReason { + fn from(reason: DisconnectReason) -> Self { + match reason { + DisconnectReason::UnknownReason => proto::DisconnectReason::UnknownReason, + DisconnectReason::ClientInitiated => proto::DisconnectReason::ClientInitiated, + DisconnectReason::DuplicateIdentity => proto::DisconnectReason::DuplicateIdentity, + DisconnectReason::ServerShutdown => proto::DisconnectReason::ServerShutdown, + DisconnectReason::ParticipantRemoved => proto::DisconnectReason::ParticipantRemoved, + DisconnectReason::RoomDeleted => proto::DisconnectReason::RoomDeleted, + DisconnectReason::StateMismatch => proto::DisconnectReason::StateMismatch, + DisconnectReason::JoinFailure => proto::DisconnectReason::JoinFailure, + DisconnectReason::Migration => proto::DisconnectReason::Migration, + DisconnectReason::SignalClose => proto::DisconnectReason::SignalClose, + DisconnectReason::RoomClosed => proto::DisconnectReason::RoomClosed, + DisconnectReason::UserUnavailable => proto::DisconnectReason::UserUnavailable, + DisconnectReason::UserRejected => proto::DisconnectReason::UserRejected, + DisconnectReason::SipTrunkFailure => proto::DisconnectReason::SipTrunkFailure, + } + } +} diff --git a/livekit-ffi/src/conversion/room.rs b/livekit-ffi/src/conversion/room.rs index 2dc32170d..dac00067d 100644 --- a/livekit-ffi/src/conversion/room.rs +++ b/livekit-ffi/src/conversion/room.rs @@ -95,6 +95,9 @@ impl From for proto::DisconnectReason { DisconnectReason::Migration => Self::Migration, DisconnectReason::SignalClose => Self::SignalClose, DisconnectReason::RoomClosed => Self::RoomClosed, + DisconnectReason::UserUnavailable => Self::UserUnavailable, + DisconnectReason::UserRejected => Self::UserRejected, + DisconnectReason::SipTrunkFailure => Self::SipTrunkFailure, } } } diff --git a/livekit-ffi/src/livekit.proto.rs b/livekit-ffi/src/livekit.proto.rs index 077dc4aeb..700666903 100644 --- a/livekit-ffi/src/livekit.proto.rs +++ b/livekit-ffi/src/livekit.proto.rs @@ -1,5 +1,5 @@ // @generated -// This file is @generated by prost-build. +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FrameCryptor { #[prost(string, required, tag="1")] @@ -11,6 +11,7 @@ pub struct FrameCryptor { #[prost(bool, required, tag="4")] pub enabled: bool, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct KeyProviderOptions { /// Only specify if you want to use a shared_key @@ -24,6 +25,7 @@ pub struct KeyProviderOptions { #[prost(int32, required, tag="4")] pub failure_tolerance: i32, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct E2eeOptions { #[prost(enumeration="EncryptionType", required, tag="1")] @@ -31,22 +33,27 @@ pub struct E2eeOptions { #[prost(message, required, tag="2")] pub key_provider_options: KeyProviderOptions, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct E2eeManagerSetEnabledRequest { #[prost(bool, required, tag="1")] pub enabled: bool, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct E2eeManagerSetEnabledResponse { } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct E2eeManagerGetFrameCryptorsRequest { } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct E2eeManagerGetFrameCryptorsResponse { #[prost(message, repeated, tag="1")] pub frame_cryptors: ::prost::alloc::vec::Vec, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FrameCryptorSetEnabledRequest { #[prost(string, required, tag="1")] @@ -56,9 +63,11 @@ pub struct FrameCryptorSetEnabledRequest { #[prost(bool, required, tag="3")] pub enabled: bool, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct FrameCryptorSetEnabledResponse { } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FrameCryptorSetKeyIndexRequest { #[prost(string, required, tag="1")] @@ -68,9 +77,11 @@ pub struct FrameCryptorSetKeyIndexRequest { #[prost(int32, required, tag="3")] pub key_index: i32, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct FrameCryptorSetKeyIndexResponse { } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SetSharedKeyRequest { #[prost(bytes="vec", required, tag="1")] @@ -78,29 +89,35 @@ pub struct SetSharedKeyRequest { #[prost(int32, required, tag="2")] pub key_index: i32, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct SetSharedKeyResponse { } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct RatchetSharedKeyRequest { #[prost(int32, required, tag="1")] pub key_index: i32, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RatchetSharedKeyResponse { #[prost(bytes="vec", optional, tag="1")] pub new_key: ::core::option::Option<::prost::alloc::vec::Vec>, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct GetSharedKeyRequest { #[prost(int32, required, tag="1")] pub key_index: i32, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetSharedKeyResponse { #[prost(bytes="vec", optional, tag="1")] pub key: ::core::option::Option<::prost::alloc::vec::Vec>, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SetKeyRequest { #[prost(string, required, tag="1")] @@ -110,9 +127,11 @@ pub struct SetKeyRequest { #[prost(int32, required, tag="3")] pub key_index: i32, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct SetKeyResponse { } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RatchetKeyRequest { #[prost(string, required, tag="1")] @@ -120,11 +139,13 @@ pub struct RatchetKeyRequest { #[prost(int32, required, tag="2")] pub key_index: i32, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RatchetKeyResponse { #[prost(bytes="vec", optional, tag="1")] pub new_key: ::core::option::Option<::prost::alloc::vec::Vec>, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetKeyRequest { #[prost(string, required, tag="1")] @@ -132,11 +153,13 @@ pub struct GetKeyRequest { #[prost(int32, required, tag="2")] pub key_index: i32, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetKeyResponse { #[prost(bytes="vec", optional, tag="1")] pub key: ::core::option::Option<::prost::alloc::vec::Vec>, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct E2eeRequest { #[prost(uint64, required, tag="1")] @@ -146,7 +169,8 @@ pub struct E2eeRequest { } /// Nested message and enum types in `E2eeRequest`. pub mod e2ee_request { - #[derive(Clone, PartialEq, ::prost::Oneof)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Message { #[prost(message, tag="2")] ManagerSetEnabled(super::E2eeManagerSetEnabledRequest), @@ -170,6 +194,7 @@ pub mod e2ee_request { GetKey(super::GetKeyRequest), } } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct E2eeResponse { #[prost(oneof="e2ee_response::Message", tags="1, 2, 3, 4, 5, 6, 7, 8, 9, 10")] @@ -177,7 +202,8 @@ pub struct E2eeResponse { } /// Nested message and enum types in `E2eeResponse`. pub mod e2ee_response { - #[derive(Clone, PartialEq, ::prost::Oneof)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Message { #[prost(message, tag="1")] ManagerSetEnabled(super::E2eeManagerSetEnabledResponse), @@ -217,9 +243,9 @@ impl EncryptionType { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::None => "NONE", - Self::Gcm => "GCM", - Self::Custom => "CUSTOM", + EncryptionType::None => "NONE", + EncryptionType::Gcm => "GCM", + EncryptionType::Custom => "CUSTOM", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -250,13 +276,13 @@ impl EncryptionState { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::New => "NEW", - Self::Ok => "OK", - Self::EncryptionFailed => "ENCRYPTION_FAILED", - Self::DecryptionFailed => "DECRYPTION_FAILED", - Self::MissingKey => "MISSING_KEY", - Self::KeyRatcheted => "KEY_RATCHETED", - Self::InternalError => "INTERNAL_ERROR", + EncryptionState::New => "NEW", + EncryptionState::Ok => "OK", + EncryptionState::EncryptionFailed => "ENCRYPTION_FAILED", + EncryptionState::DecryptionFailed => "DECRYPTION_FAILED", + EncryptionState::MissingKey => "MISSING_KEY", + EncryptionState::KeyRatcheted => "KEY_RATCHETED", + EncryptionState::InternalError => "INTERNAL_ERROR", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -282,11 +308,13 @@ impl EncryptionState { /// /// When refering to a handle without owning it, we just use a uint32 without this message. /// (the variable name is suffixed with "_handle") -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiOwnedHandle { #[prost(uint64, required, tag="1")] pub id: u64, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RtcStats { #[prost(oneof="rtc_stats::Stats", tags="3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18")] @@ -294,14 +322,16 @@ pub struct RtcStats { } /// Nested message and enum types in `RtcStats`. pub mod rtc_stats { - #[derive(Clone, PartialEq, ::prost::Message)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct Codec { #[prost(message, required, tag="1")] pub rtc: super::RtcStatsData, #[prost(message, required, tag="2")] pub codec: super::CodecStats, } - #[derive(Clone, PartialEq, ::prost::Message)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct InboundRtp { #[prost(message, required, tag="1")] pub rtc: super::RtcStatsData, @@ -312,7 +342,8 @@ pub mod rtc_stats { #[prost(message, required, tag="4")] pub inbound: super::InboundRtpStreamStats, } - #[derive(Clone, PartialEq, ::prost::Message)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct OutboundRtp { #[prost(message, required, tag="1")] pub rtc: super::RtcStatsData, @@ -323,7 +354,8 @@ pub mod rtc_stats { #[prost(message, required, tag="4")] pub outbound: super::OutboundRtpStreamStats, } - #[derive(Clone, PartialEq, ::prost::Message)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct RemoteInboundRtp { #[prost(message, required, tag="1")] pub rtc: super::RtcStatsData, @@ -334,7 +366,8 @@ pub mod rtc_stats { #[prost(message, required, tag="4")] pub remote_inbound: super::RemoteInboundRtpStreamStats, } - #[derive(Clone, PartialEq, ::prost::Message)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct RemoteOutboundRtp { #[prost(message, required, tag="1")] pub rtc: super::RtcStatsData, @@ -345,7 +378,8 @@ pub mod rtc_stats { #[prost(message, required, tag="4")] pub remote_outbound: super::RemoteOutboundRtpStreamStats, } - #[derive(Clone, PartialEq, ::prost::Message)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct MediaSource { #[prost(message, required, tag="1")] pub rtc: super::RtcStatsData, @@ -356,63 +390,72 @@ pub mod rtc_stats { #[prost(message, required, tag="4")] pub video: super::VideoSourceStats, } - #[derive(Clone, PartialEq, ::prost::Message)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct MediaPlayout { #[prost(message, required, tag="1")] pub rtc: super::RtcStatsData, #[prost(message, required, tag="2")] pub audio_playout: super::AudioPlayoutStats, } - #[derive(Clone, PartialEq, ::prost::Message)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct PeerConnection { #[prost(message, required, tag="1")] pub rtc: super::RtcStatsData, #[prost(message, required, tag="2")] pub pc: super::PeerConnectionStats, } - #[derive(Clone, PartialEq, ::prost::Message)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct DataChannel { #[prost(message, required, tag="1")] pub rtc: super::RtcStatsData, #[prost(message, required, tag="2")] pub dc: super::DataChannelStats, } - #[derive(Clone, PartialEq, ::prost::Message)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct Transport { #[prost(message, required, tag="1")] pub rtc: super::RtcStatsData, #[prost(message, required, tag="2")] pub transport: super::TransportStats, } - #[derive(Clone, PartialEq, ::prost::Message)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct CandidatePair { #[prost(message, required, tag="1")] pub rtc: super::RtcStatsData, #[prost(message, required, tag="2")] pub candidate_pair: super::CandidatePairStats, } - #[derive(Clone, PartialEq, ::prost::Message)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct LocalCandidate { #[prost(message, required, tag="1")] pub rtc: super::RtcStatsData, #[prost(message, required, tag="2")] pub candidate: super::IceCandidateStats, } - #[derive(Clone, PartialEq, ::prost::Message)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct RemoteCandidate { #[prost(message, required, tag="1")] pub rtc: super::RtcStatsData, #[prost(message, required, tag="2")] pub candidate: super::IceCandidateStats, } - #[derive(Clone, PartialEq, ::prost::Message)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct Certificate { #[prost(message, required, tag="1")] pub rtc: super::RtcStatsData, #[prost(message, required, tag="2")] pub certificate: super::CertificateStats, } - #[derive(Clone, PartialEq, ::prost::Message)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct Stream { #[prost(message, required, tag="1")] pub rtc: super::RtcStatsData, @@ -420,10 +463,12 @@ pub mod rtc_stats { pub stream: super::StreamStats, } /// Deprecated - #[derive(Clone, Copy, PartialEq, ::prost::Message)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct Track { } - #[derive(Clone, PartialEq, ::prost::Oneof)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Stats { #[prost(message, tag="3")] Codec(Codec), @@ -459,6 +504,7 @@ pub mod rtc_stats { Track(Track), } } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RtcStatsData { #[prost(string, required, tag="1")] @@ -466,6 +512,7 @@ pub struct RtcStatsData { #[prost(int64, required, tag="2")] pub timestamp: i64, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CodecStats { #[prost(uint32, required, tag="1")] @@ -481,6 +528,7 @@ pub struct CodecStats { #[prost(string, required, tag="6")] pub sdp_fmtp_line: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RtpStreamStats { #[prost(uint32, required, tag="1")] @@ -492,7 +540,8 @@ pub struct RtpStreamStats { #[prost(string, required, tag="4")] pub codec_id: ::prost::alloc::string::String, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct ReceivedRtpStreamStats { #[prost(uint64, required, tag="1")] pub packets_received: u64, @@ -501,6 +550,7 @@ pub struct ReceivedRtpStreamStats { #[prost(double, required, tag="3")] pub jitter: f64, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct InboundRtpStreamStats { #[prost(string, required, tag="1")] @@ -610,13 +660,15 @@ pub struct InboundRtpStreamStats { #[prost(uint32, required, tag="53")] pub fec_ssrc: u32, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct SentRtpStreamStats { #[prost(uint64, required, tag="1")] pub packets_sent: u64, #[prost(uint64, required, tag="2")] pub bytes_sent: u64, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct OutboundRtpStreamStats { #[prost(string, required, tag="1")] @@ -680,6 +732,7 @@ pub struct OutboundRtpStreamStats { #[prost(string, required, tag="30")] pub scalability_mode: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RemoteInboundRtpStreamStats { #[prost(string, required, tag="1")] @@ -693,6 +746,7 @@ pub struct RemoteInboundRtpStreamStats { #[prost(uint64, required, tag="5")] pub round_trip_time_measurements: u64, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RemoteOutboundRtpStreamStats { #[prost(string, required, tag="1")] @@ -708,6 +762,7 @@ pub struct RemoteOutboundRtpStreamStats { #[prost(uint64, required, tag="6")] pub round_trip_time_measurements: u64, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct MediaSourceStats { #[prost(string, required, tag="1")] @@ -715,7 +770,8 @@ pub struct MediaSourceStats { #[prost(string, required, tag="2")] pub kind: ::prost::alloc::string::String, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct AudioSourceStats { #[prost(double, required, tag="1")] pub audio_level: f64, @@ -736,7 +792,8 @@ pub struct AudioSourceStats { #[prost(uint64, required, tag="9")] pub total_samples_captured: u64, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct VideoSourceStats { #[prost(uint32, required, tag="1")] pub width: u32, @@ -747,6 +804,7 @@ pub struct VideoSourceStats { #[prost(double, required, tag="4")] pub frames_per_second: f64, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct AudioPlayoutStats { #[prost(string, required, tag="1")] @@ -762,13 +820,15 @@ pub struct AudioPlayoutStats { #[prost(uint64, required, tag="6")] pub total_samples_count: u64, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct PeerConnectionStats { #[prost(uint32, required, tag="1")] pub data_channels_opened: u32, #[prost(uint32, required, tag="2")] pub data_channels_closed: u32, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct DataChannelStats { #[prost(string, required, tag="1")] @@ -788,6 +848,7 @@ pub struct DataChannelStats { #[prost(uint64, required, tag="8")] pub bytes_received: u64, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TransportStats { #[prost(uint64, required, tag="1")] @@ -823,6 +884,7 @@ pub struct TransportStats { #[prost(uint32, required, tag="16")] pub selected_candidate_pair_changes: u32, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CandidatePairStats { #[prost(string, required, tag="1")] @@ -870,6 +932,7 @@ pub struct CandidatePairStats { #[prost(uint64, required, tag="22")] pub bytes_discarded_on_send: u64, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct IceCandidateStats { #[prost(string, required, tag="1")] @@ -899,6 +962,7 @@ pub struct IceCandidateStats { #[prost(enumeration="IceTcpCandidateType", optional, tag="13")] pub tcp_type: ::core::option::Option, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CertificateStats { #[prost(string, required, tag="1")] @@ -910,6 +974,7 @@ pub struct CertificateStats { #[prost(string, required, tag="4")] pub issuer_certificate_id: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct StreamStats { #[prost(string, required, tag="1")] @@ -933,10 +998,10 @@ impl DataChannelState { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::DcConnecting => "DC_CONNECTING", - Self::DcOpen => "DC_OPEN", - Self::DcClosing => "DC_CLOSING", - Self::DcClosed => "DC_CLOSED", + DataChannelState::DcConnecting => "DC_CONNECTING", + DataChannelState::DcOpen => "DC_OPEN", + DataChannelState::DcClosing => "DC_CLOSING", + DataChannelState::DcClosed => "DC_CLOSED", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -965,10 +1030,10 @@ impl QualityLimitationReason { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::LimitationNone => "LIMITATION_NONE", - Self::LimitationCpu => "LIMITATION_CPU", - Self::LimitationBandwidth => "LIMITATION_BANDWIDTH", - Self::LimitationOther => "LIMITATION_OTHER", + QualityLimitationReason::LimitationNone => "LIMITATION_NONE", + QualityLimitationReason::LimitationCpu => "LIMITATION_CPU", + QualityLimitationReason::LimitationBandwidth => "LIMITATION_BANDWIDTH", + QualityLimitationReason::LimitationOther => "LIMITATION_OTHER", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -996,9 +1061,9 @@ impl IceRole { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::IceUnknown => "ICE_UNKNOWN", - Self::IceControlling => "ICE_CONTROLLING", - Self::IceControlled => "ICE_CONTROLLED", + IceRole::IceUnknown => "ICE_UNKNOWN", + IceRole::IceControlling => "ICE_CONTROLLING", + IceRole::IceControlled => "ICE_CONTROLLED", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1027,11 +1092,11 @@ impl DtlsTransportState { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::DtlsTransportNew => "DTLS_TRANSPORT_NEW", - Self::DtlsTransportConnecting => "DTLS_TRANSPORT_CONNECTING", - Self::DtlsTransportConnected => "DTLS_TRANSPORT_CONNECTED", - Self::DtlsTransportClosed => "DTLS_TRANSPORT_CLOSED", - Self::DtlsTransportFailed => "DTLS_TRANSPORT_FAILED", + DtlsTransportState::DtlsTransportNew => "DTLS_TRANSPORT_NEW", + DtlsTransportState::DtlsTransportConnecting => "DTLS_TRANSPORT_CONNECTING", + DtlsTransportState::DtlsTransportConnected => "DTLS_TRANSPORT_CONNECTED", + DtlsTransportState::DtlsTransportClosed => "DTLS_TRANSPORT_CLOSED", + DtlsTransportState::DtlsTransportFailed => "DTLS_TRANSPORT_FAILED", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1064,13 +1129,13 @@ impl IceTransportState { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::IceTransportNew => "ICE_TRANSPORT_NEW", - Self::IceTransportChecking => "ICE_TRANSPORT_CHECKING", - Self::IceTransportConnected => "ICE_TRANSPORT_CONNECTED", - Self::IceTransportCompleted => "ICE_TRANSPORT_COMPLETED", - Self::IceTransportDisconnected => "ICE_TRANSPORT_DISCONNECTED", - Self::IceTransportFailed => "ICE_TRANSPORT_FAILED", - Self::IceTransportClosed => "ICE_TRANSPORT_CLOSED", + IceTransportState::IceTransportNew => "ICE_TRANSPORT_NEW", + IceTransportState::IceTransportChecking => "ICE_TRANSPORT_CHECKING", + IceTransportState::IceTransportConnected => "ICE_TRANSPORT_CONNECTED", + IceTransportState::IceTransportCompleted => "ICE_TRANSPORT_COMPLETED", + IceTransportState::IceTransportDisconnected => "ICE_TRANSPORT_DISCONNECTED", + IceTransportState::IceTransportFailed => "ICE_TRANSPORT_FAILED", + IceTransportState::IceTransportClosed => "ICE_TRANSPORT_CLOSED", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1101,9 +1166,9 @@ impl DtlsRole { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::DtlsClient => "DTLS_CLIENT", - Self::DtlsServer => "DTLS_SERVER", - Self::DtlsUnknown => "DTLS_UNKNOWN", + DtlsRole::DtlsClient => "DTLS_CLIENT", + DtlsRole::DtlsServer => "DTLS_SERVER", + DtlsRole::DtlsUnknown => "DTLS_UNKNOWN", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1132,11 +1197,11 @@ impl IceCandidatePairState { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::PairFrozen => "PAIR_FROZEN", - Self::PairWaiting => "PAIR_WAITING", - Self::PairInProgress => "PAIR_IN_PROGRESS", - Self::PairFailed => "PAIR_FAILED", - Self::PairSucceeded => "PAIR_SUCCEEDED", + IceCandidatePairState::PairFrozen => "PAIR_FROZEN", + IceCandidatePairState::PairWaiting => "PAIR_WAITING", + IceCandidatePairState::PairInProgress => "PAIR_IN_PROGRESS", + IceCandidatePairState::PairFailed => "PAIR_FAILED", + IceCandidatePairState::PairSucceeded => "PAIR_SUCCEEDED", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1166,10 +1231,10 @@ impl IceCandidateType { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::Host => "HOST", - Self::Srflx => "SRFLX", - Self::Prflx => "PRFLX", - Self::Relay => "RELAY", + IceCandidateType::Host => "HOST", + IceCandidateType::Srflx => "SRFLX", + IceCandidateType::Prflx => "PRFLX", + IceCandidateType::Relay => "RELAY", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1197,9 +1262,9 @@ impl IceServerTransportProtocol { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::TransportUdp => "TRANSPORT_UDP", - Self::TransportTcp => "TRANSPORT_TCP", - Self::TransportTls => "TRANSPORT_TLS", + IceServerTransportProtocol::TransportUdp => "TRANSPORT_UDP", + IceServerTransportProtocol::TransportTcp => "TRANSPORT_TCP", + IceServerTransportProtocol::TransportTls => "TRANSPORT_TLS", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1226,9 +1291,9 @@ impl IceTcpCandidateType { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::CandidateActive => "CANDIDATE_ACTIVE", - Self::CandidatePassive => "CANDIDATE_PASSIVE", - Self::CandidateSo => "CANDIDATE_SO", + IceTcpCandidateType::CandidateActive => "CANDIDATE_ACTIVE", + IceTcpCandidateType::CandidatePassive => "CANDIDATE_PASSIVE", + IceTcpCandidateType::CandidateSo => "CANDIDATE_SO", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1242,6 +1307,7 @@ impl IceTcpCandidateType { } } /// Create a new VideoTrack from a VideoSource +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CreateVideoTrackRequest { #[prost(string, required, tag="1")] @@ -1249,12 +1315,14 @@ pub struct CreateVideoTrackRequest { #[prost(uint64, required, tag="2")] pub source_handle: u64, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CreateVideoTrackResponse { #[prost(message, required, tag="1")] pub track: OwnedTrack, } /// Create a new AudioTrack from a AudioSource +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CreateAudioTrackRequest { #[prost(string, required, tag="1")] @@ -1262,21 +1330,25 @@ pub struct CreateAudioTrackRequest { #[prost(uint64, required, tag="2")] pub source_handle: u64, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CreateAudioTrackResponse { #[prost(message, required, tag="1")] pub track: OwnedTrack, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct GetStatsRequest { #[prost(uint64, required, tag="1")] pub track_handle: u64, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct GetStatsResponse { #[prost(uint64, required, tag="1")] pub async_id: u64, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetStatsCallback { #[prost(uint64, required, tag="1")] @@ -1290,9 +1362,11 @@ pub struct GetStatsCallback { // Track // -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackEvent { } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackPublicationInfo { #[prost(string, required, tag="1")] @@ -1318,6 +1392,7 @@ pub struct TrackPublicationInfo { #[prost(enumeration="EncryptionType", required, tag="11")] pub encryption_type: i32, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct OwnedTrackPublication { #[prost(message, required, tag="1")] @@ -1325,6 +1400,7 @@ pub struct OwnedTrackPublication { #[prost(message, required, tag="2")] pub info: TrackPublicationInfo, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackInfo { #[prost(string, required, tag="1")] @@ -1340,6 +1416,7 @@ pub struct TrackInfo { #[prost(bool, required, tag="6")] pub remote: bool, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct OwnedTrack { #[prost(message, required, tag="1")] @@ -1348,27 +1425,31 @@ pub struct OwnedTrack { pub info: TrackInfo, } /// Mute/UnMute a track -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct LocalTrackMuteRequest { #[prost(uint64, required, tag="1")] pub track_handle: u64, #[prost(bool, required, tag="2")] pub mute: bool, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct LocalTrackMuteResponse { #[prost(bool, required, tag="1")] pub muted: bool, } /// Enable/Disable a remote track -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct EnableRemoteTrackRequest { #[prost(uint64, required, tag="1")] pub track_handle: u64, #[prost(bool, required, tag="2")] pub enabled: bool, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct EnableRemoteTrackResponse { #[prost(bool, required, tag="1")] pub enabled: bool, @@ -1387,9 +1468,9 @@ impl TrackKind { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::KindUnknown => "KIND_UNKNOWN", - Self::KindAudio => "KIND_AUDIO", - Self::KindVideo => "KIND_VIDEO", + TrackKind::KindUnknown => "KIND_UNKNOWN", + TrackKind::KindAudio => "KIND_AUDIO", + TrackKind::KindVideo => "KIND_VIDEO", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1418,11 +1499,11 @@ impl TrackSource { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::SourceUnknown => "SOURCE_UNKNOWN", - Self::SourceCamera => "SOURCE_CAMERA", - Self::SourceMicrophone => "SOURCE_MICROPHONE", - Self::SourceScreenshare => "SOURCE_SCREENSHARE", - Self::SourceScreenshareAudio => "SOURCE_SCREENSHARE_AUDIO", + TrackSource::SourceUnknown => "SOURCE_UNKNOWN", + TrackSource::SourceCamera => "SOURCE_CAMERA", + TrackSource::SourceMicrophone => "SOURCE_MICROPHONE", + TrackSource::SourceScreenshare => "SOURCE_SCREENSHARE", + TrackSource::SourceScreenshareAudio => "SOURCE_SCREENSHARE_AUDIO", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1451,9 +1532,9 @@ impl StreamState { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::StateUnknown => "STATE_UNKNOWN", - Self::StateActive => "STATE_ACTIVE", - Self::StatePaused => "STATE_PAUSED", + StreamState::StateUnknown => "STATE_UNKNOWN", + StreamState::StateActive => "STATE_ACTIVE", + StreamState::StatePaused => "STATE_PAUSED", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1467,18 +1548,21 @@ impl StreamState { } } /// Enable/Disable a remote track publication -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct EnableRemoteTrackPublicationRequest { #[prost(uint64, required, tag="1")] pub track_publication_handle: u64, #[prost(bool, required, tag="2")] pub enabled: bool, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct EnableRemoteTrackPublicationResponse { } /// update a remote track publication dimension -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct UpdateRemoteTrackPublicationDimensionRequest { #[prost(uint64, required, tag="1")] pub track_publication_handle: u64, @@ -1487,9 +1571,11 @@ pub struct UpdateRemoteTrackPublicationDimensionRequest { #[prost(uint32, required, tag="3")] pub height: u32, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct UpdateRemoteTrackPublicationDimensionResponse { } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ParticipantInfo { #[prost(string, required, tag="1")] @@ -1504,7 +1590,10 @@ pub struct ParticipantInfo { pub attributes: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, #[prost(enumeration="ParticipantKind", required, tag="6")] pub kind: i32, + #[prost(enumeration="DisconnectReason", required, tag="7")] + pub disconnect_reason: i32, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct OwnedParticipant { #[prost(message, required, tag="1")] @@ -1528,11 +1617,11 @@ impl ParticipantKind { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::Standard => "PARTICIPANT_KIND_STANDARD", - Self::Ingress => "PARTICIPANT_KIND_INGRESS", - Self::Egress => "PARTICIPANT_KIND_EGRESS", - Self::Sip => "PARTICIPANT_KIND_SIP", - Self::Agent => "PARTICIPANT_KIND_AGENT", + ParticipantKind::Standard => "PARTICIPANT_KIND_STANDARD", + ParticipantKind::Ingress => "PARTICIPANT_KIND_INGRESS", + ParticipantKind::Egress => "PARTICIPANT_KIND_EGRESS", + ParticipantKind::Sip => "PARTICIPANT_KIND_SIP", + ParticipantKind::Agent => "PARTICIPANT_KIND_AGENT", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1547,9 +1636,85 @@ impl ParticipantKind { } } } +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum DisconnectReason { + UnknownReason = 0, + /// the client initiated the disconnect + ClientInitiated = 1, + /// another participant with the same identity has joined the room + DuplicateIdentity = 2, + /// the server instance is shutting down + ServerShutdown = 3, + /// RoomService.RemoveParticipant was called + ParticipantRemoved = 4, + /// RoomService.DeleteRoom was called + RoomDeleted = 5, + /// the client is attempting to resume a session, but server is not aware of it + StateMismatch = 6, + /// client was unable to connect fully + JoinFailure = 7, + /// Cloud-only, the server requested Participant to migrate the connection elsewhere + Migration = 8, + /// the signal websocket was closed unexpectedly + SignalClose = 9, + /// the room was closed, due to all Standard and Ingress participants having left + RoomClosed = 10, + /// SIP callee did not respond in time + UserUnavailable = 11, + /// SIP callee rejected the call (busy) + UserRejected = 12, + /// SIP protocol failure or unexpected response + SipTrunkFailure = 13, +} +impl DisconnectReason { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + DisconnectReason::UnknownReason => "UNKNOWN_REASON", + DisconnectReason::ClientInitiated => "CLIENT_INITIATED", + DisconnectReason::DuplicateIdentity => "DUPLICATE_IDENTITY", + DisconnectReason::ServerShutdown => "SERVER_SHUTDOWN", + DisconnectReason::ParticipantRemoved => "PARTICIPANT_REMOVED", + DisconnectReason::RoomDeleted => "ROOM_DELETED", + DisconnectReason::StateMismatch => "STATE_MISMATCH", + DisconnectReason::JoinFailure => "JOIN_FAILURE", + DisconnectReason::Migration => "MIGRATION", + DisconnectReason::SignalClose => "SIGNAL_CLOSE", + DisconnectReason::RoomClosed => "ROOM_CLOSED", + DisconnectReason::UserUnavailable => "USER_UNAVAILABLE", + DisconnectReason::UserRejected => "USER_REJECTED", + DisconnectReason::SipTrunkFailure => "SIP_TRUNK_FAILURE", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "UNKNOWN_REASON" => Some(Self::UnknownReason), + "CLIENT_INITIATED" => Some(Self::ClientInitiated), + "DUPLICATE_IDENTITY" => Some(Self::DuplicateIdentity), + "SERVER_SHUTDOWN" => Some(Self::ServerShutdown), + "PARTICIPANT_REMOVED" => Some(Self::ParticipantRemoved), + "ROOM_DELETED" => Some(Self::RoomDeleted), + "STATE_MISMATCH" => Some(Self::StateMismatch), + "JOIN_FAILURE" => Some(Self::JoinFailure), + "MIGRATION" => Some(Self::Migration), + "SIGNAL_CLOSE" => Some(Self::SignalClose), + "ROOM_CLOSED" => Some(Self::RoomClosed), + "USER_UNAVAILABLE" => Some(Self::UserUnavailable), + "USER_REJECTED" => Some(Self::UserRejected), + "SIP_TRUNK_FAILURE" => Some(Self::SipTrunkFailure), + _ => None, + } + } +} /// Create a new VideoStream /// VideoStream is used to receive video frames from a track -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct NewVideoStreamRequest { #[prost(uint64, required, tag="1")] pub track_handle: u64, @@ -1562,13 +1727,15 @@ pub struct NewVideoStreamRequest { #[prost(bool, optional, tag="4")] pub normalize_stride: ::core::option::Option, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct NewVideoStreamResponse { #[prost(message, required, tag="1")] pub stream: OwnedVideoStream, } /// Request a video stream from a participant -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct VideoStreamFromParticipantRequest { #[prost(uint64, required, tag="1")] pub participant_handle: u64, @@ -1581,14 +1748,16 @@ pub struct VideoStreamFromParticipantRequest { #[prost(bool, optional, tag="5")] pub normalize_stride: ::core::option::Option, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct VideoStreamFromParticipantResponse { #[prost(message, required, tag="1")] pub stream: OwnedVideoStream, } /// Create a new VideoSource /// VideoSource is used to send video frame to a track -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct NewVideoSourceRequest { #[prost(enumeration="VideoSourceType", required, tag="1")] pub r#type: i32, @@ -1597,12 +1766,14 @@ pub struct NewVideoSourceRequest { #[prost(message, required, tag="2")] pub resolution: VideoSourceResolution, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct NewVideoSourceResponse { #[prost(message, required, tag="1")] pub source: OwnedVideoSource, } /// Push a frame to a VideoSource +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CaptureVideoFrameRequest { #[prost(uint64, required, tag="1")] @@ -1615,9 +1786,11 @@ pub struct CaptureVideoFrameRequest { #[prost(enumeration="VideoRotation", required, tag="4")] pub rotation: i32, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct CaptureVideoFrameResponse { } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct VideoConvertRequest { #[prost(bool, optional, tag="1")] @@ -1627,6 +1800,7 @@ pub struct VideoConvertRequest { #[prost(enumeration="VideoBufferType", required, tag="3")] pub dst_type: i32, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct VideoConvertResponse { #[prost(oneof="video_convert_response::Message", tags="1, 2")] @@ -1634,7 +1808,8 @@ pub struct VideoConvertResponse { } /// Nested message and enum types in `VideoConvertResponse`. pub mod video_convert_response { - #[derive(Clone, PartialEq, ::prost::Oneof)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Message { #[prost(string, tag="1")] Error(::prost::alloc::string::String), @@ -1646,7 +1821,8 @@ pub mod video_convert_response { // VideoFrame buffers // -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct VideoResolution { #[prost(uint32, required, tag="1")] pub width: u32, @@ -1655,6 +1831,7 @@ pub struct VideoResolution { #[prost(double, required, tag="3")] pub frame_rate: f64, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct VideoBufferInfo { #[prost(enumeration="VideoBufferType", required, tag="1")] @@ -1673,7 +1850,8 @@ pub struct VideoBufferInfo { } /// Nested message and enum types in `VideoBufferInfo`. pub mod video_buffer_info { - #[derive(Clone, Copy, PartialEq, ::prost::Message)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct ComponentInfo { #[prost(uint64, required, tag="1")] pub data_ptr: u64, @@ -1683,6 +1861,7 @@ pub mod video_buffer_info { pub size: u32, } } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct OwnedVideoBuffer { #[prost(message, required, tag="1")] @@ -1690,18 +1869,21 @@ pub struct OwnedVideoBuffer { #[prost(message, required, tag="2")] pub info: VideoBufferInfo, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct VideoStreamInfo { #[prost(enumeration="VideoStreamType", required, tag="1")] pub r#type: i32, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct OwnedVideoStream { #[prost(message, required, tag="1")] pub handle: FfiOwnedHandle, #[prost(message, required, tag="2")] pub info: VideoStreamInfo, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct VideoStreamEvent { #[prost(uint64, required, tag="1")] @@ -1711,7 +1893,8 @@ pub struct VideoStreamEvent { } /// Nested message and enum types in `VideoStreamEvent`. pub mod video_stream_event { - #[derive(Clone, PartialEq, ::prost::Oneof)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Message { #[prost(message, tag="2")] FrameReceived(super::VideoFrameReceived), @@ -1719,6 +1902,7 @@ pub mod video_stream_event { Eos(super::VideoStreamEos), } } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct VideoFrameReceived { #[prost(message, required, tag="1")] @@ -1729,26 +1913,30 @@ pub struct VideoFrameReceived { #[prost(enumeration="VideoRotation", required, tag="3")] pub rotation: i32, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct VideoStreamEos { } // // VideoSource // -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct VideoSourceResolution { #[prost(uint32, required, tag="1")] pub width: u32, #[prost(uint32, required, tag="2")] pub height: u32, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct VideoSourceInfo { #[prost(enumeration="VideoSourceType", required, tag="1")] pub r#type: i32, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct OwnedVideoSource { #[prost(message, required, tag="1")] pub handle: FfiOwnedHandle, @@ -1770,10 +1958,10 @@ impl VideoCodec { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::Vp8 => "VP8", - Self::H264 => "H264", - Self::Av1 => "AV1", - Self::Vp9 => "VP9", + VideoCodec::Vp8 => "VP8", + VideoCodec::H264 => "H264", + VideoCodec::Av1 => "AV1", + VideoCodec::Vp9 => "VP9", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1802,10 +1990,10 @@ impl VideoRotation { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::VideoRotation0 => "VIDEO_ROTATION_0", - Self::VideoRotation90 => "VIDEO_ROTATION_90", - Self::VideoRotation180 => "VIDEO_ROTATION_180", - Self::VideoRotation270 => "VIDEO_ROTATION_270", + VideoRotation::VideoRotation0 => "VIDEO_ROTATION_0", + VideoRotation::VideoRotation90 => "VIDEO_ROTATION_90", + VideoRotation::VideoRotation180 => "VIDEO_ROTATION_180", + VideoRotation::VideoRotation270 => "VIDEO_ROTATION_270", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1841,17 +2029,17 @@ impl VideoBufferType { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::Rgba => "RGBA", - Self::Abgr => "ABGR", - Self::Argb => "ARGB", - Self::Bgra => "BGRA", - Self::Rgb24 => "RGB24", - Self::I420 => "I420", - Self::I420a => "I420A", - Self::I422 => "I422", - Self::I444 => "I444", - Self::I010 => "I010", - Self::Nv12 => "NV12", + VideoBufferType::Rgba => "RGBA", + VideoBufferType::Abgr => "ABGR", + VideoBufferType::Argb => "ARGB", + VideoBufferType::Bgra => "BGRA", + VideoBufferType::Rgb24 => "RGB24", + VideoBufferType::I420 => "I420", + VideoBufferType::I420a => "I420A", + VideoBufferType::I422 => "I422", + VideoBufferType::I444 => "I444", + VideoBufferType::I010 => "I010", + VideoBufferType::Nv12 => "NV12", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1890,9 +2078,9 @@ impl VideoStreamType { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::VideoStreamNative => "VIDEO_STREAM_NATIVE", - Self::VideoStreamWebgl => "VIDEO_STREAM_WEBGL", - Self::VideoStreamHtml => "VIDEO_STREAM_HTML", + VideoStreamType::VideoStreamNative => "VIDEO_STREAM_NATIVE", + VideoStreamType::VideoStreamWebgl => "VIDEO_STREAM_WEBGL", + VideoStreamType::VideoStreamHtml => "VIDEO_STREAM_HTML", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1917,7 +2105,7 @@ impl VideoSourceType { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::VideoSourceNative => "VIDEO_SOURCE_NATIVE", + VideoSourceType::VideoSourceNative => "VIDEO_SOURCE_NATIVE", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1929,6 +2117,7 @@ impl VideoSourceType { } } /// Connect to a new LiveKit room +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ConnectRequest { #[prost(string, required, tag="1")] @@ -1938,11 +2127,13 @@ pub struct ConnectRequest { #[prost(message, required, tag="3")] pub options: RoomOptions, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct ConnectResponse { #[prost(uint64, required, tag="1")] pub async_id: u64, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ConnectCallback { #[prost(uint64, required, tag="1")] @@ -1952,7 +2143,8 @@ pub struct ConnectCallback { } /// Nested message and enum types in `ConnectCallback`. pub mod connect_callback { - #[derive(Clone, PartialEq, ::prost::Message)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct ParticipantWithTracks { #[prost(message, required, tag="1")] pub participant: super::OwnedParticipant, @@ -1961,7 +2153,8 @@ pub mod connect_callback { #[prost(message, repeated, tag="2")] pub publications: ::prost::alloc::vec::Vec, } - #[derive(Clone, PartialEq, ::prost::Message)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct Result { #[prost(message, required, tag="1")] pub room: super::OwnedRoom, @@ -1970,7 +2163,8 @@ pub mod connect_callback { #[prost(message, repeated, tag="3")] pub participants: ::prost::alloc::vec::Vec, } - #[derive(Clone, PartialEq, ::prost::Oneof)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Message { #[prost(string, tag="2")] Error(::prost::alloc::string::String), @@ -1979,22 +2173,26 @@ pub mod connect_callback { } } /// Disconnect from the a room -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct DisconnectRequest { #[prost(uint64, required, tag="1")] pub room_handle: u64, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct DisconnectResponse { #[prost(uint64, required, tag="1")] pub async_id: u64, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct DisconnectCallback { #[prost(uint64, required, tag="1")] pub async_id: u64, } /// Publish a track to the room +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PublishTrackRequest { #[prost(uint64, required, tag="1")] @@ -2004,11 +2202,13 @@ pub struct PublishTrackRequest { #[prost(message, required, tag="3")] pub options: TrackPublishOptions, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct PublishTrackResponse { #[prost(uint64, required, tag="1")] pub async_id: u64, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PublishTrackCallback { #[prost(uint64, required, tag="1")] @@ -2018,7 +2218,8 @@ pub struct PublishTrackCallback { } /// Nested message and enum types in `PublishTrackCallback`. pub mod publish_track_callback { - #[derive(Clone, PartialEq, ::prost::Oneof)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Message { #[prost(string, tag="2")] Error(::prost::alloc::string::String), @@ -2027,6 +2228,7 @@ pub mod publish_track_callback { } } /// Unpublish a track from the room +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct UnpublishTrackRequest { #[prost(uint64, required, tag="1")] @@ -2036,11 +2238,13 @@ pub struct UnpublishTrackRequest { #[prost(bool, required, tag="3")] pub stop_on_unpublish: bool, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct UnpublishTrackResponse { #[prost(uint64, required, tag="1")] pub async_id: u64, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct UnpublishTrackCallback { #[prost(uint64, required, tag="1")] @@ -2049,6 +2253,7 @@ pub struct UnpublishTrackCallback { pub error: ::core::option::Option<::prost::alloc::string::String>, } /// Publish data to other participants +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PublishDataRequest { #[prost(uint64, required, tag="1")] @@ -2067,11 +2272,13 @@ pub struct PublishDataRequest { #[prost(string, repeated, tag="7")] pub destination_identities: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct PublishDataResponse { #[prost(uint64, required, tag="1")] pub async_id: u64, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PublishDataCallback { #[prost(uint64, required, tag="1")] @@ -2080,6 +2287,7 @@ pub struct PublishDataCallback { pub error: ::core::option::Option<::prost::alloc::string::String>, } /// Publish transcription messages to room +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PublishTranscriptionRequest { #[prost(uint64, required, tag="1")] @@ -2091,11 +2299,13 @@ pub struct PublishTranscriptionRequest { #[prost(message, repeated, tag="4")] pub segments: ::prost::alloc::vec::Vec, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct PublishTranscriptionResponse { #[prost(uint64, required, tag="1")] pub async_id: u64, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PublishTranscriptionCallback { #[prost(uint64, required, tag="1")] @@ -2104,6 +2314,7 @@ pub struct PublishTranscriptionCallback { pub error: ::core::option::Option<::prost::alloc::string::String>, } /// Publish Sip DTMF messages to other participants +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PublishSipDtmfRequest { #[prost(uint64, required, tag="1")] @@ -2115,11 +2326,13 @@ pub struct PublishSipDtmfRequest { #[prost(string, repeated, tag="4")] pub destination_identities: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct PublishSipDtmfResponse { #[prost(uint64, required, tag="1")] pub async_id: u64, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PublishSipDtmfCallback { #[prost(uint64, required, tag="1")] @@ -2128,6 +2341,7 @@ pub struct PublishSipDtmfCallback { pub error: ::core::option::Option<::prost::alloc::string::String>, } /// Change the local participant's metadata +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SetLocalMetadataRequest { #[prost(uint64, required, tag="1")] @@ -2135,11 +2349,13 @@ pub struct SetLocalMetadataRequest { #[prost(string, required, tag="2")] pub metadata: ::prost::alloc::string::String, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct SetLocalMetadataResponse { #[prost(uint64, required, tag="1")] pub async_id: u64, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SetLocalMetadataCallback { #[prost(uint64, required, tag="1")] @@ -2147,6 +2363,7 @@ pub struct SetLocalMetadataCallback { #[prost(string, optional, tag="2")] pub error: ::core::option::Option<::prost::alloc::string::String>, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SendChatMessageRequest { #[prost(uint64, required, tag="1")] @@ -2158,6 +2375,7 @@ pub struct SendChatMessageRequest { #[prost(string, optional, tag="4")] pub sender_identity: ::core::option::Option<::prost::alloc::string::String>, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct EditChatMessageRequest { #[prost(uint64, required, tag="1")] @@ -2171,11 +2389,13 @@ pub struct EditChatMessageRequest { #[prost(string, optional, tag="5")] pub sender_identity: ::core::option::Option<::prost::alloc::string::String>, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct SendChatMessageResponse { #[prost(uint64, required, tag="1")] pub async_id: u64, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SendChatMessageCallback { #[prost(uint64, required, tag="1")] @@ -2185,7 +2405,8 @@ pub struct SendChatMessageCallback { } /// Nested message and enum types in `SendChatMessageCallback`. pub mod send_chat_message_callback { - #[derive(Clone, PartialEq, ::prost::Oneof)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Message { #[prost(string, tag="2")] Error(::prost::alloc::string::String), @@ -2194,6 +2415,7 @@ pub mod send_chat_message_callback { } } /// Change the local participant's attributes +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SetLocalAttributesRequest { #[prost(uint64, required, tag="1")] @@ -2201,6 +2423,7 @@ pub struct SetLocalAttributesRequest { #[prost(message, repeated, tag="2")] pub attributes: ::prost::alloc::vec::Vec, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct AttributesEntry { #[prost(string, required, tag="1")] @@ -2208,11 +2431,13 @@ pub struct AttributesEntry { #[prost(string, required, tag="2")] pub value: ::prost::alloc::string::String, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct SetLocalAttributesResponse { #[prost(uint64, required, tag="1")] pub async_id: u64, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SetLocalAttributesCallback { #[prost(uint64, required, tag="1")] @@ -2221,6 +2446,7 @@ pub struct SetLocalAttributesCallback { pub error: ::core::option::Option<::prost::alloc::string::String>, } /// Change the local participant's name +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SetLocalNameRequest { #[prost(uint64, required, tag="1")] @@ -2228,11 +2454,13 @@ pub struct SetLocalNameRequest { #[prost(string, required, tag="2")] pub name: ::prost::alloc::string::String, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct SetLocalNameResponse { #[prost(uint64, required, tag="1")] pub async_id: u64, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SetLocalNameCallback { #[prost(uint64, required, tag="1")] @@ -2241,26 +2469,31 @@ pub struct SetLocalNameCallback { pub error: ::core::option::Option<::prost::alloc::string::String>, } /// Change the "desire" to subs2ribe to a track -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct SetSubscribedRequest { #[prost(bool, required, tag="1")] pub subscribe: bool, #[prost(uint64, required, tag="2")] pub publication_handle: u64, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct SetSubscribedResponse { } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct GetSessionStatsRequest { #[prost(uint64, required, tag="1")] pub room_handle: u64, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct GetSessionStatsResponse { #[prost(uint64, required, tag="1")] pub async_id: u64, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetSessionStatsCallback { #[prost(uint64, required, tag="1")] @@ -2270,14 +2503,16 @@ pub struct GetSessionStatsCallback { } /// Nested message and enum types in `GetSessionStatsCallback`. pub mod get_session_stats_callback { - #[derive(Clone, PartialEq, ::prost::Message)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct Result { #[prost(message, repeated, tag="1")] pub publisher_stats: ::prost::alloc::vec::Vec, #[prost(message, repeated, tag="2")] pub subscriber_stats: ::prost::alloc::vec::Vec, } - #[derive(Clone, PartialEq, ::prost::Oneof)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Message { #[prost(string, tag="2")] Error(::prost::alloc::string::String), @@ -2289,18 +2524,21 @@ pub mod get_session_stats_callback { // Options // -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct VideoEncoding { #[prost(uint64, required, tag="1")] pub max_bitrate: u64, #[prost(double, required, tag="2")] pub max_framerate: f64, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct AudioEncoding { #[prost(uint64, required, tag="1")] pub max_bitrate: u64, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackPublishOptions { /// encodings are optional @@ -2321,6 +2559,7 @@ pub struct TrackPublishOptions { #[prost(string, optional, tag="8")] pub stream: ::core::option::Option<::prost::alloc::string::String>, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct IceServer { #[prost(string, repeated, tag="1")] @@ -2330,6 +2569,7 @@ pub struct IceServer { #[prost(string, optional, tag="3")] pub password: ::core::option::Option<::prost::alloc::string::String>, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RtcConfig { #[prost(enumeration="IceTransportType", optional, tag="1")] @@ -2340,6 +2580,7 @@ pub struct RtcConfig { #[prost(message, repeated, tag="3")] pub ice_servers: ::prost::alloc::vec::Vec, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RoomOptions { #[prost(bool, optional, tag="1")] @@ -2356,6 +2597,7 @@ pub struct RoomOptions { #[prost(uint32, optional, tag="6")] pub join_retries: ::core::option::Option, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TranscriptionSegment { #[prost(string, required, tag="1")] @@ -2371,20 +2613,23 @@ pub struct TranscriptionSegment { #[prost(string, required, tag="6")] pub language: ::prost::alloc::string::String, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct BufferInfo { #[prost(uint64, required, tag="1")] pub data_ptr: u64, #[prost(uint64, required, tag="2")] pub data_len: u64, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct OwnedBuffer { #[prost(message, required, tag="1")] pub handle: FfiOwnedHandle, #[prost(message, required, tag="2")] pub data: BufferInfo, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RoomEvent { #[prost(uint64, required, tag="1")] @@ -2394,7 +2639,8 @@ pub struct RoomEvent { } /// Nested message and enum types in `RoomEvent`. pub mod room_event { - #[derive(Clone, PartialEq, ::prost::Oneof)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Message { #[prost(message, tag="2")] ParticipantConnected(super::ParticipantConnected), @@ -2456,6 +2702,7 @@ pub mod room_event { ChatMessage(super::ChatMessageReceived), } } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RoomInfo { #[prost(string, optional, tag="1")] @@ -2465,6 +2712,7 @@ pub struct RoomInfo { #[prost(string, required, tag="3")] pub metadata: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct OwnedRoom { #[prost(message, required, tag="1")] @@ -2472,16 +2720,19 @@ pub struct OwnedRoom { #[prost(message, required, tag="2")] pub info: RoomInfo, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ParticipantConnected { #[prost(message, required, tag="1")] pub info: OwnedParticipant, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ParticipantDisconnected { #[prost(string, required, tag="1")] pub participant_identity: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct LocalTrackPublished { /// The TrackPublicationInfo comes from the PublishTrack response @@ -2489,16 +2740,19 @@ pub struct LocalTrackPublished { #[prost(string, required, tag="1")] pub track_sid: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct LocalTrackUnpublished { #[prost(string, required, tag="1")] pub publication_sid: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct LocalTrackSubscribed { #[prost(string, required, tag="2")] pub track_sid: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackPublished { #[prost(string, required, tag="1")] @@ -2506,6 +2760,7 @@ pub struct TrackPublished { #[prost(message, required, tag="2")] pub publication: OwnedTrackPublication, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackUnpublished { #[prost(string, required, tag="1")] @@ -2515,6 +2770,7 @@ pub struct TrackUnpublished { } /// Publication isn't needed for subscription events on the FFI /// The FFI will retrieve the publication using the Track sid +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackSubscribed { #[prost(string, required, tag="1")] @@ -2522,6 +2778,7 @@ pub struct TrackSubscribed { #[prost(message, required, tag="2")] pub track: OwnedTrack, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackUnsubscribed { /// The FFI language can dispose/remove the VideoSink here @@ -2530,6 +2787,7 @@ pub struct TrackUnsubscribed { #[prost(string, required, tag="2")] pub track_sid: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackSubscriptionFailed { #[prost(string, required, tag="1")] @@ -2539,6 +2797,7 @@ pub struct TrackSubscriptionFailed { #[prost(string, required, tag="3")] pub error: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackMuted { #[prost(string, required, tag="1")] @@ -2546,6 +2805,7 @@ pub struct TrackMuted { #[prost(string, required, tag="2")] pub track_sid: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TrackUnmuted { #[prost(string, required, tag="1")] @@ -2553,6 +2813,7 @@ pub struct TrackUnmuted { #[prost(string, required, tag="2")] pub track_sid: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct E2eeStateChanged { /// Using sid instead of identity for ffi communication @@ -2561,21 +2822,25 @@ pub struct E2eeStateChanged { #[prost(enumeration="EncryptionState", required, tag="2")] pub state: i32, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ActiveSpeakersChanged { #[prost(string, repeated, tag="1")] pub participant_identities: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RoomMetadataChanged { #[prost(string, required, tag="1")] pub metadata: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RoomSidChanged { #[prost(string, required, tag="1")] pub sid: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ParticipantMetadataChanged { #[prost(string, required, tag="1")] @@ -2583,6 +2848,7 @@ pub struct ParticipantMetadataChanged { #[prost(string, required, tag="2")] pub metadata: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ParticipantAttributesChanged { #[prost(string, required, tag="1")] @@ -2592,6 +2858,7 @@ pub struct ParticipantAttributesChanged { #[prost(message, repeated, tag="3")] pub changed_attributes: ::prost::alloc::vec::Vec, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ParticipantNameChanged { #[prost(string, required, tag="1")] @@ -2599,6 +2866,7 @@ pub struct ParticipantNameChanged { #[prost(string, required, tag="2")] pub name: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ConnectionQualityChanged { #[prost(string, required, tag="1")] @@ -2606,6 +2874,7 @@ pub struct ConnectionQualityChanged { #[prost(enumeration="ConnectionQuality", required, tag="2")] pub quality: i32, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct UserPacket { #[prost(message, required, tag="1")] @@ -2613,6 +2882,7 @@ pub struct UserPacket { #[prost(string, optional, tag="2")] pub topic: ::core::option::Option<::prost::alloc::string::String>, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ChatMessage { #[prost(string, required, tag="1")] @@ -2628,6 +2898,7 @@ pub struct ChatMessage { #[prost(bool, optional, tag="6")] pub generated: ::core::option::Option, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ChatMessageReceived { #[prost(message, required, tag="1")] @@ -2635,6 +2906,7 @@ pub struct ChatMessageReceived { #[prost(string, required, tag="2")] pub participant_identity: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SipDtmf { #[prost(uint32, required, tag="1")] @@ -2642,6 +2914,7 @@ pub struct SipDtmf { #[prost(string, optional, tag="2")] pub digit: ::core::option::Option<::prost::alloc::string::String>, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct DataPacketReceived { #[prost(enumeration="DataPacketKind", required, tag="1")] @@ -2654,7 +2927,8 @@ pub struct DataPacketReceived { } /// Nested message and enum types in `DataPacketReceived`. pub mod data_packet_received { - #[derive(Clone, PartialEq, ::prost::Oneof)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Value { #[prost(message, tag="4")] User(super::UserPacket), @@ -2662,6 +2936,7 @@ pub mod data_packet_received { SipDtmf(super::SipDtmf), } } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TranscriptionReceived { #[prost(string, optional, tag="1")] @@ -2671,26 +2946,32 @@ pub struct TranscriptionReceived { #[prost(message, repeated, tag="3")] pub segments: ::prost::alloc::vec::Vec, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct ConnectionStateChanged { #[prost(enumeration="ConnectionState", required, tag="1")] pub state: i32, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct Connected { } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct Disconnected { #[prost(enumeration="DisconnectReason", required, tag="1")] pub reason: i32, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct Reconnecting { } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct Reconnected { } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct RoomEos { } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] @@ -2707,9 +2988,9 @@ impl IceTransportType { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::TransportRelay => "TRANSPORT_RELAY", - Self::TransportNohost => "TRANSPORT_NOHOST", - Self::TransportAll => "TRANSPORT_ALL", + IceTransportType::TransportRelay => "TRANSPORT_RELAY", + IceTransportType::TransportNohost => "TRANSPORT_NOHOST", + IceTransportType::TransportAll => "TRANSPORT_ALL", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -2735,8 +3016,8 @@ impl ContinualGatheringPolicy { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::GatherOnce => "GATHER_ONCE", - Self::GatherContinually => "GATHER_CONTINUALLY", + ContinualGatheringPolicy::GatherOnce => "GATHER_ONCE", + ContinualGatheringPolicy::GatherContinually => "GATHER_CONTINUALLY", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -2767,10 +3048,10 @@ impl ConnectionQuality { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::QualityPoor => "QUALITY_POOR", - Self::QualityGood => "QUALITY_GOOD", - Self::QualityExcellent => "QUALITY_EXCELLENT", - Self::QualityLost => "QUALITY_LOST", + ConnectionQuality::QualityPoor => "QUALITY_POOR", + ConnectionQuality::QualityGood => "QUALITY_GOOD", + ConnectionQuality::QualityExcellent => "QUALITY_EXCELLENT", + ConnectionQuality::QualityLost => "QUALITY_LOST", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -2798,9 +3079,9 @@ impl ConnectionState { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::ConnDisconnected => "CONN_DISCONNECTED", - Self::ConnConnected => "CONN_CONNECTED", - Self::ConnReconnecting => "CONN_RECONNECTING", + ConnectionState::ConnDisconnected => "CONN_DISCONNECTED", + ConnectionState::ConnConnected => "CONN_CONNECTED", + ConnectionState::ConnReconnecting => "CONN_RECONNECTING", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -2826,8 +3107,8 @@ impl DataPacketKind { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::KindLossy => "KIND_LOSSY", - Self::KindReliable => "KIND_RELIABLE", + DataPacketKind::KindLossy => "KIND_LOSSY", + DataPacketKind::KindReliable => "KIND_RELIABLE", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -2839,72 +3120,10 @@ impl DataPacketKind { } } } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] -#[repr(i32)] -pub enum DisconnectReason { - UnknownReason = 0, - /// the client initiated the disconnect - ClientInitiated = 1, - /// another participant with the same identity has joined the room - DuplicateIdentity = 2, - /// the server instance is shutting down - ServerShutdown = 3, - /// RoomService.RemoveParticipant was called - ParticipantRemoved = 4, - /// RoomService.DeleteRoom was called - RoomDeleted = 5, - /// the client is attempting to resume a session, but server is not aware of it - StateMismatch = 6, - /// client was unable to connect fully - JoinFailure = 7, - /// Cloud-only, the server requested Participant to migrate the connection elsewhere - Migration = 8, - /// the signal websocket was closed unexpectedly - SignalClose = 9, - /// the room was closed, due to all Standard and Ingress participants having left - RoomClosed = 10, -} -impl DisconnectReason { - /// String value of the enum field names used in the ProtoBuf definition. - /// - /// The values are not transformed in any way and thus are considered stable - /// (if the ProtoBuf definition does not change) and safe for programmatic use. - pub fn as_str_name(&self) -> &'static str { - match self { - Self::UnknownReason => "UNKNOWN_REASON", - Self::ClientInitiated => "CLIENT_INITIATED", - Self::DuplicateIdentity => "DUPLICATE_IDENTITY", - Self::ServerShutdown => "SERVER_SHUTDOWN", - Self::ParticipantRemoved => "PARTICIPANT_REMOVED", - Self::RoomDeleted => "ROOM_DELETED", - Self::StateMismatch => "STATE_MISMATCH", - Self::JoinFailure => "JOIN_FAILURE", - Self::Migration => "MIGRATION", - Self::SignalClose => "SIGNAL_CLOSE", - Self::RoomClosed => "ROOM_CLOSED", - } - } - /// Creates an enum from field names used in the ProtoBuf definition. - pub fn from_str_name(value: &str) -> ::core::option::Option { - match value { - "UNKNOWN_REASON" => Some(Self::UnknownReason), - "CLIENT_INITIATED" => Some(Self::ClientInitiated), - "DUPLICATE_IDENTITY" => Some(Self::DuplicateIdentity), - "SERVER_SHUTDOWN" => Some(Self::ServerShutdown), - "PARTICIPANT_REMOVED" => Some(Self::ParticipantRemoved), - "ROOM_DELETED" => Some(Self::RoomDeleted), - "STATE_MISMATCH" => Some(Self::StateMismatch), - "JOIN_FAILURE" => Some(Self::JoinFailure), - "MIGRATION" => Some(Self::Migration), - "SIGNAL_CLOSE" => Some(Self::SignalClose), - "ROOM_CLOSED" => Some(Self::RoomClosed), - _ => None, - } - } -} /// Create a new AudioStream /// AudioStream is used to receive audio frames from a track -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct NewAudioStreamRequest { #[prost(uint64, required, tag="1")] pub track_handle: u64, @@ -2915,12 +3134,14 @@ pub struct NewAudioStreamRequest { #[prost(uint32, optional, tag="4")] pub num_channels: ::core::option::Option, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct NewAudioStreamResponse { #[prost(message, required, tag="1")] pub stream: OwnedAudioStream, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct AudioStreamFromParticipantRequest { #[prost(uint64, required, tag="1")] pub participant_handle: u64, @@ -2933,13 +3154,15 @@ pub struct AudioStreamFromParticipantRequest { #[prost(uint32, optional, tag="6")] pub num_channels: ::core::option::Option, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct AudioStreamFromParticipantResponse { #[prost(message, required, tag="1")] pub stream: OwnedAudioStream, } /// Create a new AudioSource -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct NewAudioSourceRequest { #[prost(enumeration="AudioSourceType", required, tag="1")] pub r#type: i32, @@ -2952,25 +3175,29 @@ pub struct NewAudioSourceRequest { #[prost(uint32, optional, tag="5")] pub queue_size_ms: ::core::option::Option, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct NewAudioSourceResponse { #[prost(message, required, tag="1")] pub source: OwnedAudioSource, } /// Push a frame to an AudioSource /// The data provided must be available as long as the client receive the callback. -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct CaptureAudioFrameRequest { #[prost(uint64, required, tag="1")] pub source_handle: u64, #[prost(message, required, tag="2")] pub buffer: AudioFrameBufferInfo, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct CaptureAudioFrameResponse { #[prost(uint64, required, tag="1")] pub async_id: u64, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CaptureAudioFrameCallback { #[prost(uint64, required, tag="1")] @@ -2978,25 +3205,30 @@ pub struct CaptureAudioFrameCallback { #[prost(string, optional, tag="2")] pub error: ::core::option::Option<::prost::alloc::string::String>, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct ClearAudioBufferRequest { #[prost(uint64, required, tag="1")] pub source_handle: u64, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct ClearAudioBufferResponse { } /// Create a new AudioResampler -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct NewAudioResamplerRequest { } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct NewAudioResamplerResponse { #[prost(message, required, tag="1")] pub resampler: OwnedAudioResampler, } /// Remix and resample an audio frame -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct RemixAndResampleRequest { #[prost(uint64, required, tag="1")] pub resampler_handle: u64, @@ -3007,14 +3239,16 @@ pub struct RemixAndResampleRequest { #[prost(uint32, required, tag="4")] pub sample_rate: u32, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct RemixAndResampleResponse { #[prost(message, required, tag="1")] pub buffer: OwnedAudioFrameBuffer, } // New resampler using SoX (much better quality) -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct NewSoxResamplerRequest { #[prost(double, required, tag="1")] pub input_rate: f64, @@ -3031,6 +3265,7 @@ pub struct NewSoxResamplerRequest { #[prost(uint32, optional, tag="7")] pub flags: ::core::option::Option, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct NewSoxResamplerResponse { #[prost(oneof="new_sox_resampler_response::Message", tags="1, 2")] @@ -3038,7 +3273,8 @@ pub struct NewSoxResamplerResponse { } /// Nested message and enum types in `NewSoxResamplerResponse`. pub mod new_sox_resampler_response { - #[derive(Clone, PartialEq, ::prost::Oneof)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Message { #[prost(message, tag="1")] Resampler(super::OwnedSoxResampler), @@ -3046,7 +3282,8 @@ pub mod new_sox_resampler_response { Error(::prost::alloc::string::String), } } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct PushSoxResamplerRequest { #[prost(uint64, required, tag="1")] pub resampler_handle: u64, @@ -3057,6 +3294,7 @@ pub struct PushSoxResamplerRequest { #[prost(uint32, required, tag="3")] pub size: u32, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PushSoxResamplerResponse { /// *const i16 (could be null) @@ -3068,11 +3306,13 @@ pub struct PushSoxResamplerResponse { #[prost(string, optional, tag="3")] pub error: ::core::option::Option<::prost::alloc::string::String>, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct FlushSoxResamplerRequest { #[prost(uint64, required, tag="1")] pub resampler_handle: u64, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FlushSoxResamplerResponse { /// *const i16 (could be null) @@ -3088,7 +3328,8 @@ pub struct FlushSoxResamplerResponse { // AudioFrame buffer // -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct AudioFrameBufferInfo { /// *const i16 #[prost(uint64, required, tag="1")] @@ -3100,26 +3341,30 @@ pub struct AudioFrameBufferInfo { #[prost(uint32, required, tag="4")] pub samples_per_channel: u32, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct OwnedAudioFrameBuffer { #[prost(message, required, tag="1")] pub handle: FfiOwnedHandle, #[prost(message, required, tag="2")] pub info: AudioFrameBufferInfo, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct AudioStreamInfo { #[prost(enumeration="AudioStreamType", required, tag="1")] pub r#type: i32, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct OwnedAudioStream { #[prost(message, required, tag="1")] pub handle: FfiOwnedHandle, #[prost(message, required, tag="2")] pub info: AudioStreamInfo, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct AudioStreamEvent { #[prost(uint64, required, tag="1")] pub stream_handle: u64, @@ -3128,7 +3373,8 @@ pub struct AudioStreamEvent { } /// Nested message and enum types in `AudioStreamEvent`. pub mod audio_stream_event { - #[derive(Clone, Copy, PartialEq, ::prost::Oneof)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Message { #[prost(message, tag="2")] FrameReceived(super::AudioFrameReceived), @@ -3136,19 +3382,22 @@ pub mod audio_stream_event { Eos(super::AudioStreamEos), } } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct AudioFrameReceived { #[prost(message, required, tag="1")] pub frame: OwnedAudioFrameBuffer, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct AudioStreamEos { } // // AudioSource // -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct AudioSourceOptions { #[prost(bool, required, tag="1")] pub echo_cancellation: bool, @@ -3157,12 +3406,14 @@ pub struct AudioSourceOptions { #[prost(bool, required, tag="3")] pub auto_gain_control: bool, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct AudioSourceInfo { #[prost(enumeration="AudioSourceType", required, tag="2")] pub r#type: i32, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct OwnedAudioSource { #[prost(message, required, tag="1")] pub handle: FfiOwnedHandle, @@ -3173,10 +3424,12 @@ pub struct OwnedAudioSource { // AudioResampler // -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct AudioResamplerInfo { } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct OwnedAudioResampler { #[prost(message, required, tag="1")] pub handle: FfiOwnedHandle, @@ -3187,10 +3440,12 @@ pub struct OwnedAudioResampler { // Sox AudioResampler // -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct SoxResamplerInfo { } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct OwnedSoxResampler { #[prost(message, required, tag="1")] pub handle: FfiOwnedHandle, @@ -3211,8 +3466,8 @@ impl SoxResamplerDataType { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::SoxrDatatypeInt16i => "SOXR_DATATYPE_INT16I", - Self::SoxrDatatypeInt16s => "SOXR_DATATYPE_INT16S", + SoxResamplerDataType::SoxrDatatypeInt16i => "SOXR_DATATYPE_INT16I", + SoxResamplerDataType::SoxrDatatypeInt16s => "SOXR_DATATYPE_INT16S", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -3240,11 +3495,11 @@ impl SoxQualityRecipe { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::SoxrQualityQuick => "SOXR_QUALITY_QUICK", - Self::SoxrQualityLow => "SOXR_QUALITY_LOW", - Self::SoxrQualityMedium => "SOXR_QUALITY_MEDIUM", - Self::SoxrQualityHigh => "SOXR_QUALITY_HIGH", - Self::SoxrQualityVeryhigh => "SOXR_QUALITY_VERYHIGH", + SoxQualityRecipe::SoxrQualityQuick => "SOXR_QUALITY_QUICK", + SoxQualityRecipe::SoxrQualityLow => "SOXR_QUALITY_LOW", + SoxQualityRecipe::SoxrQualityMedium => "SOXR_QUALITY_MEDIUM", + SoxQualityRecipe::SoxrQualityHigh => "SOXR_QUALITY_HIGH", + SoxQualityRecipe::SoxrQualityVeryhigh => "SOXR_QUALITY_VERYHIGH", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -3282,12 +3537,12 @@ impl SoxFlagBits { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::SoxrRolloffSmall => "SOXR_ROLLOFF_SMALL", - Self::SoxrRolloffMedium => "SOXR_ROLLOFF_MEDIUM", - Self::SoxrRolloffNone => "SOXR_ROLLOFF_NONE", - Self::SoxrHighPrecClock => "SOXR_HIGH_PREC_CLOCK", - Self::SoxrDoublePrecision => "SOXR_DOUBLE_PRECISION", - Self::SoxrVr => "SOXR_VR", + SoxFlagBits::SoxrRolloffSmall => "SOXR_ROLLOFF_SMALL", + SoxFlagBits::SoxrRolloffMedium => "SOXR_ROLLOFF_MEDIUM", + SoxFlagBits::SoxrRolloffNone => "SOXR_ROLLOFF_NONE", + SoxFlagBits::SoxrHighPrecClock => "SOXR_HIGH_PREC_CLOCK", + SoxFlagBits::SoxrDoublePrecision => "SOXR_DOUBLE_PRECISION", + SoxFlagBits::SoxrVr => "SOXR_VR", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -3320,8 +3575,8 @@ impl AudioStreamType { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::AudioStreamNative => "AUDIO_STREAM_NATIVE", - Self::AudioStreamHtml => "AUDIO_STREAM_HTML", + AudioStreamType::AudioStreamNative => "AUDIO_STREAM_NATIVE", + AudioStreamType::AudioStreamHtml => "AUDIO_STREAM_HTML", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -3345,7 +3600,7 @@ impl AudioSourceType { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::AudioSourceNative => "AUDIO_SOURCE_NATIVE", + AudioSourceType::AudioSourceNative => "AUDIO_SOURCE_NATIVE", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -3356,6 +3611,7 @@ impl AudioSourceType { } } } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RpcError { #[prost(uint32, required, tag="1")] @@ -3366,6 +3622,7 @@ pub struct RpcError { pub data: ::core::option::Option<::prost::alloc::string::String>, } /// FFI Requests +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PerformRpcRequest { #[prost(uint64, required, tag="1")] @@ -3379,6 +3636,7 @@ pub struct PerformRpcRequest { #[prost(uint32, optional, tag="5")] pub response_timeout_ms: ::core::option::Option, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RegisterRpcMethodRequest { #[prost(uint64, required, tag="1")] @@ -3386,6 +3644,7 @@ pub struct RegisterRpcMethodRequest { #[prost(string, required, tag="2")] pub method: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct UnregisterRpcMethodRequest { #[prost(uint64, required, tag="1")] @@ -3393,6 +3652,7 @@ pub struct UnregisterRpcMethodRequest { #[prost(string, required, tag="2")] pub method: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RpcMethodInvocationResponseRequest { #[prost(uint64, required, tag="1")] @@ -3405,23 +3665,28 @@ pub struct RpcMethodInvocationResponseRequest { pub error: ::core::option::Option, } /// FFI Responses -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct PerformRpcResponse { #[prost(uint64, required, tag="1")] pub async_id: u64, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct RegisterRpcMethodResponse { } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct UnregisterRpcMethodResponse { } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RpcMethodInvocationResponseResponse { #[prost(string, optional, tag="1")] pub error: ::core::option::Option<::prost::alloc::string::String>, } /// FFI Callbacks +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PerformRpcCallback { #[prost(uint64, required, tag="1")] @@ -3432,6 +3697,7 @@ pub struct PerformRpcCallback { pub error: ::core::option::Option, } /// FFI Events +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RpcMethodInvocationEvent { #[prost(uint64, required, tag="1")] @@ -3477,6 +3743,7 @@ pub struct RpcMethodInvocationEvent { /// This is the input of livekit_ffi_request function /// We always expect a response (FFIResponse, even if it's empty) +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiRequest { #[prost(oneof="ffi_request::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43")] @@ -3484,7 +3751,8 @@ pub struct FfiRequest { } /// Nested message and enum types in `FfiRequest`. pub mod ffi_request { - #[derive(Clone, PartialEq, ::prost::Oneof)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Message { #[prost(message, tag="2")] Dispose(super::DisposeRequest), @@ -3579,6 +3847,7 @@ pub mod ffi_request { } } /// This is the output of livekit_ffi_request function. +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiResponse { #[prost(oneof="ffi_response::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42")] @@ -3586,7 +3855,8 @@ pub struct FfiResponse { } /// Nested message and enum types in `FfiResponse`. pub mod ffi_response { - #[derive(Clone, PartialEq, ::prost::Oneof)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Message { #[prost(message, tag="2")] Dispose(super::DisposeResponse), @@ -3681,6 +3951,7 @@ pub mod ffi_response { /// To minimize complexity, participant events are not included in the protocol. /// It is easily deducible from the room events and it turned out that is is easier to implement /// on the ffi client side. +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiEvent { #[prost(oneof="ffi_event::Message", tags="1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24")] @@ -3688,7 +3959,8 @@ pub struct FfiEvent { } /// Nested message and enum types in `FfiEvent`. pub mod ffi_event { - #[derive(Clone, PartialEq, ::prost::Oneof)] + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Message { #[prost(message, tag="1")] RoomEvent(super::RoomEvent), @@ -3741,22 +4013,26 @@ pub mod ffi_event { /// Stop all rooms synchronously (Do we need async here?). /// e.g: This is used for the Unity Editor after each assemblies reload. /// TODO(theomonnom): Implement a debug mode where we can find all leaked handles? -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct DisposeRequest { #[prost(bool, required, tag="1")] pub r#async: bool, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct DisposeResponse { /// None if sync #[prost(uint64, optional, tag="1")] pub async_id: ::core::option::Option, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct DisposeCallback { #[prost(uint64, required, tag="1")] pub async_id: u64, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct LogRecord { #[prost(enumeration="LogLevel", required, tag="1")] @@ -3773,11 +4049,13 @@ pub struct LogRecord { #[prost(string, required, tag="6")] pub message: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct LogBatch { #[prost(message, repeated, tag="1")] pub records: ::prost::alloc::vec::Vec, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct Panic { #[prost(string, required, tag="1")] @@ -3799,11 +4077,11 @@ impl LogLevel { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - Self::LogError => "LOG_ERROR", - Self::LogWarn => "LOG_WARN", - Self::LogInfo => "LOG_INFO", - Self::LogDebug => "LOG_DEBUG", - Self::LogTrace => "LOG_TRACE", + LogLevel::LogError => "LOG_ERROR", + LogLevel::LogWarn => "LOG_WARN", + LogLevel::LogInfo => "LOG_INFO", + LogLevel::LogDebug => "LOG_DEBUG", + LogLevel::LogTrace => "LOG_TRACE", } } /// Creates an enum from field names used in the ProtoBuf definition. diff --git a/livekit-protocol/protocol b/livekit-protocol/protocol index a601adc5e..095606bc8 160000 --- a/livekit-protocol/protocol +++ b/livekit-protocol/protocol @@ -1 +1 @@ -Subproject commit a601adc5e9027820857a6d445b32a868b19d4184 +Subproject commit 095606bc8e0e73535c6bf4867645dfff0825f121 diff --git a/livekit-protocol/src/livekit.rs b/livekit-protocol/src/livekit.rs index 19181c255..9c3107e4a 100644 --- a/livekit-protocol/src/livekit.rs +++ b/livekit-protocol/src/livekit.rs @@ -124,6 +124,12 @@ pub enum MetricLabel { ClientVideoPublisherQualityLimitationDurationCpu = 15, /// total duration spent in other quality limitation ClientVideoPublisherQualityLimitationDurationOther = 16, + /// Publisher RTT (participant -> server) + PublisherRtt = 17, + /// RTT between publisher node and subscriber node (could involve intermedia node(s)) + ServerMeshRtt = 18, + /// Subscribe RTT (server -> participant) + SubscriberRtt = 19, PredefinedMaxValue = 4096, } impl MetricLabel { @@ -150,6 +156,9 @@ impl MetricLabel { MetricLabel::ClientVideoPublisherQualityLimitationDurationBandwidth => "CLIENT_VIDEO_PUBLISHER_QUALITY_LIMITATION_DURATION_BANDWIDTH", MetricLabel::ClientVideoPublisherQualityLimitationDurationCpu => "CLIENT_VIDEO_PUBLISHER_QUALITY_LIMITATION_DURATION_CPU", MetricLabel::ClientVideoPublisherQualityLimitationDurationOther => "CLIENT_VIDEO_PUBLISHER_QUALITY_LIMITATION_DURATION_OTHER", + MetricLabel::PublisherRtt => "PUBLISHER_RTT", + MetricLabel::ServerMeshRtt => "SERVER_MESH_RTT", + MetricLabel::SubscriberRtt => "SUBSCRIBER_RTT", MetricLabel::PredefinedMaxValue => "METRIC_LABEL_PREDEFINED_MAX_VALUE", } } @@ -173,6 +182,9 @@ impl MetricLabel { "CLIENT_VIDEO_PUBLISHER_QUALITY_LIMITATION_DURATION_BANDWIDTH" => Some(Self::ClientVideoPublisherQualityLimitationDurationBandwidth), "CLIENT_VIDEO_PUBLISHER_QUALITY_LIMITATION_DURATION_CPU" => Some(Self::ClientVideoPublisherQualityLimitationDurationCpu), "CLIENT_VIDEO_PUBLISHER_QUALITY_LIMITATION_DURATION_OTHER" => Some(Self::ClientVideoPublisherQualityLimitationDurationOther), + "PUBLISHER_RTT" => Some(Self::PublisherRtt), + "SERVER_MESH_RTT" => Some(Self::ServerMeshRtt), + "SUBSCRIBER_RTT" => Some(Self::SubscriberRtt), "METRIC_LABEL_PREDEFINED_MAX_VALUE" => Some(Self::PredefinedMaxValue), _ => None, } @@ -503,7 +515,7 @@ pub struct DataPacket { /// identities of participants who will receive the message (sent to all by default) #[prost(string, repeated, tag="5")] pub destination_identities: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, - #[prost(oneof="data_packet::Value", tags="2, 3, 6, 7, 8, 9, 10, 11, 12")] + #[prost(oneof="data_packet::Value", tags="2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14")] pub value: ::core::option::Option, } /// Nested message and enum types in `DataPacket`. @@ -555,6 +567,10 @@ pub mod data_packet { RpcAck(super::RpcAck), #[prost(message, tag="12")] RpcResponse(super::RpcResponse), + #[prost(message, tag="13")] + StreamHeader(super::data_stream::Header), + #[prost(message, tag="14")] + StreamChunk(super::data_stream::Chunk), } } #[allow(clippy::derive_partial_eq_without_eq)] @@ -1100,6 +1116,136 @@ pub struct TimedVersion { #[prost(int32, tag="2")] pub ticks: i32, } +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DataStream { +} +/// Nested message and enum types in `DataStream`. +pub mod data_stream { + /// header properties specific to text streams + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] + pub struct TextHeader { + #[prost(enumeration="OperationType", tag="1")] + pub operation_type: i32, + /// Optional: Version for updates/edits + #[prost(int32, tag="2")] + pub version: i32, + /// Optional: Reply to specific message + #[prost(string, tag="3")] + pub reply_to_stream_id: ::prost::alloc::string::String, + /// file attachments for text streams + #[prost(string, repeated, tag="4")] + pub attached_stream_ids: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + /// true if the text has been generated by an agent from a participant's audio transcription + #[prost(bool, tag="5")] + pub generated: bool, + } + /// header properties specific to file or image streams + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] + pub struct FileHeader { + /// name of the file + #[prost(string, tag="1")] + pub file_name: ::prost::alloc::string::String, + } + /// main DataStream.Header that contains a oneof for specific headers + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] + pub struct Header { + /// unique identifier for this data stream + #[prost(string, tag="1")] + pub stream_id: ::prost::alloc::string::String, + /// using int64 for Unix timestamp + #[prost(int64, tag="2")] + pub timestamp: i64, + #[prost(string, tag="3")] + pub topic: ::prost::alloc::string::String, + #[prost(string, tag="4")] + pub mime_type: ::prost::alloc::string::String, + /// only populated for finite streams, if it's a stream of unknown size this stays empty + #[prost(uint64, optional, tag="5")] + pub total_length: ::core::option::Option, + /// only populated for finite streams, if it's a stream of unknown size this stays empty + #[prost(uint64, optional, tag="6")] + pub total_chunks: ::core::option::Option, + /// defaults to NONE + #[prost(enumeration="super::encryption::Type", tag="7")] + pub encryption_type: i32, + /// user defined extensions map that can carry additional info + #[prost(map="string, string", tag="8")] + pub extensions: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + /// oneof to choose between specific header types + #[prost(oneof="header::ContentHeader", tags="9, 10")] + pub content_header: ::core::option::Option, + } + /// Nested message and enum types in `Header`. + pub mod header { + /// oneof to choose between specific header types + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum ContentHeader { + #[prost(message, tag="9")] + TextHeader(super::TextHeader), + #[prost(message, tag="10")] + FileHeader(super::FileHeader), + } + } + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] + pub struct Chunk { + /// unique identifier for this data stream to map it to the correct header + #[prost(string, tag="1")] + pub stream_id: ::prost::alloc::string::String, + #[prost(uint64, tag="2")] + pub chunk_index: u64, + /// content as binary (bytes) + #[prost(bytes="vec", tag="3")] + pub content: ::prost::alloc::vec::Vec, + /// true only if this is the last chunk of this stream - can also be sent with empty content + #[prost(bool, tag="4")] + pub complete: bool, + /// a version indicating that this chunk_index has been retroactively modified and the original one needs to be replaced + #[prost(int32, tag="5")] + pub version: i32, + /// optional, initialization vector for AES-GCM encryption + #[prost(bytes="vec", optional, tag="6")] + pub iv: ::core::option::Option<::prost::alloc::vec::Vec>, + } + /// enum for operation types (specific to TextHeader) + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] + #[repr(i32)] + pub enum OperationType { + Create = 0, + Update = 1, + Delete = 2, + Reaction = 3, + } + impl OperationType { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + OperationType::Create => "CREATE", + OperationType::Update => "UPDATE", + OperationType::Delete => "DELETE", + OperationType::Reaction => "REACTION", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "CREATE" => Some(Self::Create), + "UPDATE" => Some(Self::Update), + "DELETE" => Some(Self::Delete), + "REACTION" => Some(Self::Reaction), + _ => None, + } + } + } +} #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] pub enum AudioCodec { @@ -1371,6 +1517,12 @@ pub enum DisconnectReason { SignalClose = 9, /// the room was closed, due to all Standard and Ingress participants having left RoomClosed = 10, + /// SIP callee did not respond in time + UserUnavailable = 11, + /// SIP callee rejected the call (busy) + UserRejected = 12, + /// SIP protocol failure or unexpected response + SipTrunkFailure = 13, } impl DisconnectReason { /// String value of the enum field names used in the ProtoBuf definition. @@ -1390,6 +1542,9 @@ impl DisconnectReason { DisconnectReason::Migration => "MIGRATION", DisconnectReason::SignalClose => "SIGNAL_CLOSE", DisconnectReason::RoomClosed => "ROOM_CLOSED", + DisconnectReason::UserUnavailable => "USER_UNAVAILABLE", + DisconnectReason::UserRejected => "USER_REJECTED", + DisconnectReason::SipTrunkFailure => "SIP_TRUNK_FAILURE", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1406,6 +1561,9 @@ impl DisconnectReason { "MIGRATION" => Some(Self::Migration), "SIGNAL_CLOSE" => Some(Self::SignalClose), "ROOM_CLOSED" => Some(Self::RoomClosed), + "USER_UNAVAILABLE" => Some(Self::UserUnavailable), + "USER_REJECTED" => Some(Self::UserRejected), + "SIP_TRUNK_FAILURE" => Some(Self::SipTrunkFailure), _ => None, } } @@ -2073,6 +2231,11 @@ pub struct EgressInfo { pub segment_results: ::prost::alloc::vec::Vec, #[prost(message, repeated, tag="20")] pub image_results: ::prost::alloc::vec::Vec, + #[prost(string, tag="23")] + pub manifest_location: ::prost::alloc::string::String, + /// next ID: 26 + #[prost(bool, tag="25")] + pub backup_storage_used: bool, #[prost(oneof="egress_info::Request", tags="4, 14, 19, 5, 6")] pub request: ::core::option::Option, /// deprecated (use _result fields) @@ -3471,8 +3634,8 @@ pub struct UpdateWorkerStatus { /// optional string metadata = 2 \[deprecated=true\]; #[prost(float, tag="3")] pub load: f32, - #[prost(int32, tag="4")] - pub job_count: i32, + #[prost(uint32, tag="4")] + pub job_count: u32, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -3650,7 +3813,7 @@ pub struct CreateRoomRequest { pub name: ::prost::alloc::string::String, /// configuration to use for this room parameters. Setting parameters below override the config defaults. #[prost(string, tag="12")] - pub config_name: ::prost::alloc::string::String, + pub room_preset: ::prost::alloc::string::String, /// number of seconds to keep the room open if no one joins #[prost(uint32, tag="2")] pub empty_timeout: u32, @@ -3666,12 +3829,9 @@ pub struct CreateRoomRequest { /// metadata of room #[prost(string, tag="5")] pub metadata: ::prost::alloc::string::String, - /// egress + /// auto-egress configurations #[prost(message, optional, tag="6")] pub egress: ::core::option::Option, - /// agent - #[prost(message, optional, tag="11")] - pub agent: ::core::option::Option, /// playout delay of subscriber #[prost(uint32, tag="7")] pub min_playout_delay: u32, @@ -3682,10 +3842,13 @@ pub struct CreateRoomRequest { #[prost(bool, tag="9")] pub sync_streams: bool, /// replay - /// - /// NEXT-ID: 14 #[prost(bool, tag="13")] pub replay_enabled: bool, + /// Define agents that should be dispatched to this room + /// + /// NEXT-ID: 15 + #[prost(message, repeated, tag="14")] + pub agents: ::prost::alloc::vec::Vec, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -3863,15 +4026,12 @@ pub struct RoomConfiguration { /// number of seconds to keep the room open after everyone leaves #[prost(uint32, tag="3")] pub departure_timeout: u32, - /// limit number of participants that can be in a room + /// limit number of participants that can be in a room, excluding Egress and Ingress participants #[prost(uint32, tag="4")] pub max_participants: u32, /// egress #[prost(message, optional, tag="5")] pub egress: ::core::option::Option, - /// agent - #[prost(message, optional, tag="6")] - pub agent: ::core::option::Option, /// playout delay of subscriber #[prost(uint32, tag="7")] pub min_playout_delay: u32, @@ -3881,6 +4041,9 @@ pub struct RoomConfiguration { /// so not recommended for rooms with frequent subscription changes #[prost(bool, tag="9")] pub sync_streams: bool, + /// Define agents that should be dispatched to this room + #[prost(message, repeated, tag="10")] + pub agents: ::prost::alloc::vec::Vec, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -4469,6 +4632,18 @@ pub struct SipInboundTrunkInfo { /// Map SIP X-* headers from INVITE to SIP participant attributes. #[prost(map="string, string", tag="10")] pub headers_to_attributes: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + /// Map LiveKit attributes to SIP X-* headers when sending BYE or REFER requests. + /// Keys are the names of attributes and values are the names of X-* headers they will be mapped to. + #[prost(map="string, string", tag="14")] + pub attributes_to_headers: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + /// Max time for the caller to wait for track subscription. + #[prost(message, optional, tag="11")] + pub ringing_timeout: ::core::option::Option<::pbjson_types::Duration>, + /// Max call duration. + #[prost(message, optional, tag="12")] + pub max_call_duration: ::core::option::Option<::pbjson_types::Duration>, + #[prost(bool, tag="13")] + pub krisp_enabled: bool, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -4512,6 +4687,10 @@ pub struct SipOutboundTrunkInfo { /// Keys are the names of X-* headers and values are the names of attributes they will be mapped to. #[prost(map="string, string", tag="10")] pub headers_to_attributes: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + /// Map LiveKit attributes to SIP X-* headers when sending BYE or REFER requests. + /// Keys are the names of attributes and values are the names of X-* headers they will be mapped to. + #[prost(map="string, string", tag="11")] + pub attributes_to_headers: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -4711,6 +4890,9 @@ pub struct CreateSipParticipantRequest { /// What number should be dialed via SIP #[prost(string, tag="2")] pub sip_call_to: ::prost::alloc::string::String, + /// Optional SIP From number to use. If empty, trunk number is used. + #[prost(string, tag="15")] + pub sip_number: ::prost::alloc::string::String, /// What LiveKit room should this participant be connected too #[prost(string, tag="3")] pub room_name: ::prost::alloc::string::String, @@ -4730,13 +4912,27 @@ pub struct CreateSipParticipantRequest { /// Character 'w' can be used to add a 0.5 sec delay. #[prost(string, tag="5")] pub dtmf: ::prost::alloc::string::String, - /// Optionally play ringtone in the room as an audible indicator for existing participants + /// Optionally play dialtone in the room as an audible indicator for existing participants. The `play_ringtone` option is deprectated but has the same effect. + #[deprecated] #[prost(bool, tag="6")] pub play_ringtone: bool, + #[prost(bool, tag="13")] + pub play_dialtone: bool, /// By default the From value (Phone number) is used for participant name/identity (if not set) and added to attributes. /// If true, a random value for identity will be used and numbers will be omitted from attributes. #[prost(bool, tag="10")] pub hide_phone_number: bool, + /// Max time for the callee to answer the call. + #[prost(message, optional, tag="11")] + pub ringing_timeout: ::core::option::Option<::pbjson_types::Duration>, + /// Max call duration. + #[prost(message, optional, tag="12")] + pub max_call_duration: ::core::option::Option<::pbjson_types::Duration>, + /// Enable voice isolation for the callee. + /// + /// NEXT ID: 16 + #[prost(bool, tag="14")] + pub enable_krisp: bool, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -4759,6 +4955,56 @@ pub struct TransferSipParticipantRequest { pub room_name: ::prost::alloc::string::String, #[prost(string, tag="3")] pub transfer_to: ::prost::alloc::string::String, + /// Optionally play dialtone to the SIP participant as an audible indicator of being transferred + #[prost(bool, tag="4")] + pub play_dialtone: bool, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SipCallInfo { + #[prost(string, tag="1")] + pub call_id: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub trunk_id: ::prost::alloc::string::String, + #[prost(string, tag="3")] + pub room_name: ::prost::alloc::string::String, + /// ID of the current/previous room published to + #[prost(string, tag="4")] + pub room_id: ::prost::alloc::string::String, + #[prost(string, tag="5")] + pub participant_identity: ::prost::alloc::string::String, + #[prost(message, optional, tag="6")] + pub from_uri: ::core::option::Option, + #[prost(message, optional, tag="7")] + pub to_uri: ::core::option::Option, + #[prost(enumeration="SipFeature", repeated, tag="14")] + pub enabled_features: ::prost::alloc::vec::Vec, + #[prost(enumeration="SipCallStatus", tag="8")] + pub call_status: i32, + #[prost(int64, tag="9")] + pub created_at: i64, + #[prost(int64, tag="10")] + pub started_at: i64, + #[prost(int64, tag="11")] + pub ended_at: i64, + #[prost(enumeration="DisconnectReason", tag="12")] + pub disconnect_reason: i32, + #[prost(string, tag="13")] + pub error: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SipUri { + #[prost(string, tag="1")] + pub user: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub host: ::prost::alloc::string::String, + #[prost(string, tag="3")] + pub ip: ::prost::alloc::string::String, + #[prost(uint32, tag="4")] + pub port: u32, + #[prost(enumeration="SipTransport", tag="5")] + pub transport: i32, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] @@ -4792,5 +5038,71 @@ impl SipTransport { } } } +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum SipCallStatus { + /// Incoming call is being handled by the SIP service. The SIP participant hasn't joined a LiveKit room yet + ScsCallIncoming = 0, + /// SIP participant for outgoing call has been created. The SIP outgoing call is being established + ScsParticipantJoined = 1, + /// Call is ongoing. SIP participant is active in the LiveKit room + ScsActive = 2, + /// Call has ended + ScsDisconnected = 3, + /// Call has ended or never succeeded because of an error + ScsError = 4, +} +impl SipCallStatus { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + SipCallStatus::ScsCallIncoming => "SCS_CALL_INCOMING", + SipCallStatus::ScsParticipantJoined => "SCS_PARTICIPANT_JOINED", + SipCallStatus::ScsActive => "SCS_ACTIVE", + SipCallStatus::ScsDisconnected => "SCS_DISCONNECTED", + SipCallStatus::ScsError => "SCS_ERROR", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "SCS_CALL_INCOMING" => Some(Self::ScsCallIncoming), + "SCS_PARTICIPANT_JOINED" => Some(Self::ScsParticipantJoined), + "SCS_ACTIVE" => Some(Self::ScsActive), + "SCS_DISCONNECTED" => Some(Self::ScsDisconnected), + "SCS_ERROR" => Some(Self::ScsError), + _ => None, + } + } +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum SipFeature { + None = 0, + KrispEnabled = 1, +} +impl SipFeature { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + SipFeature::None => "NONE", + SipFeature::KrispEnabled => "KRISP_ENABLED", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "NONE" => Some(Self::None), + "KRISP_ENABLED" => Some(Self::KrispEnabled), + _ => None, + } + } +} include!("livekit.serde.rs"); // @@protoc_insertion_point(module) diff --git a/livekit-protocol/src/livekit.serde.rs b/livekit-protocol/src/livekit.serde.rs index 4e6a0fb03..5da36416c 100644 --- a/livekit-protocol/src/livekit.serde.rs +++ b/livekit-protocol/src/livekit.serde.rs @@ -3555,7 +3555,7 @@ impl serde::Serialize for CreateRoomRequest { if !self.name.is_empty() { len += 1; } - if !self.config_name.is_empty() { + if !self.room_preset.is_empty() { len += 1; } if self.empty_timeout != 0 { @@ -3576,9 +3576,6 @@ impl serde::Serialize for CreateRoomRequest { if self.egress.is_some() { len += 1; } - if self.agent.is_some() { - len += 1; - } if self.min_playout_delay != 0 { len += 1; } @@ -3591,12 +3588,15 @@ impl serde::Serialize for CreateRoomRequest { if self.replay_enabled { len += 1; } + if !self.agents.is_empty() { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.CreateRoomRequest", len)?; if !self.name.is_empty() { struct_ser.serialize_field("name", &self.name)?; } - if !self.config_name.is_empty() { - struct_ser.serialize_field("configName", &self.config_name)?; + if !self.room_preset.is_empty() { + struct_ser.serialize_field("roomPreset", &self.room_preset)?; } if self.empty_timeout != 0 { struct_ser.serialize_field("emptyTimeout", &self.empty_timeout)?; @@ -3616,9 +3616,6 @@ impl serde::Serialize for CreateRoomRequest { if let Some(v) = self.egress.as_ref() { struct_ser.serialize_field("egress", v)?; } - if let Some(v) = self.agent.as_ref() { - struct_ser.serialize_field("agent", v)?; - } if self.min_playout_delay != 0 { struct_ser.serialize_field("minPlayoutDelay", &self.min_playout_delay)?; } @@ -3631,6 +3628,9 @@ impl serde::Serialize for CreateRoomRequest { if self.replay_enabled { struct_ser.serialize_field("replayEnabled", &self.replay_enabled)?; } + if !self.agents.is_empty() { + struct_ser.serialize_field("agents", &self.agents)?; + } struct_ser.end() } } @@ -3642,8 +3642,8 @@ impl<'de> serde::Deserialize<'de> for CreateRoomRequest { { const FIELDS: &[&str] = &[ "name", - "config_name", - "configName", + "room_preset", + "roomPreset", "empty_timeout", "emptyTimeout", "departure_timeout", @@ -3654,7 +3654,6 @@ impl<'de> serde::Deserialize<'de> for CreateRoomRequest { "nodeId", "metadata", "egress", - "agent", "min_playout_delay", "minPlayoutDelay", "max_playout_delay", @@ -3663,23 +3662,24 @@ impl<'de> serde::Deserialize<'de> for CreateRoomRequest { "syncStreams", "replay_enabled", "replayEnabled", + "agents", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { Name, - ConfigName, + RoomPreset, EmptyTimeout, DepartureTimeout, MaxParticipants, NodeId, Metadata, Egress, - Agent, MinPlayoutDelay, MaxPlayoutDelay, SyncStreams, ReplayEnabled, + Agents, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -3703,18 +3703,18 @@ impl<'de> serde::Deserialize<'de> for CreateRoomRequest { { match value { "name" => Ok(GeneratedField::Name), - "configName" | "config_name" => Ok(GeneratedField::ConfigName), + "roomPreset" | "room_preset" => Ok(GeneratedField::RoomPreset), "emptyTimeout" | "empty_timeout" => Ok(GeneratedField::EmptyTimeout), "departureTimeout" | "departure_timeout" => Ok(GeneratedField::DepartureTimeout), "maxParticipants" | "max_participants" => Ok(GeneratedField::MaxParticipants), "nodeId" | "node_id" => Ok(GeneratedField::NodeId), "metadata" => Ok(GeneratedField::Metadata), "egress" => Ok(GeneratedField::Egress), - "agent" => Ok(GeneratedField::Agent), "minPlayoutDelay" | "min_playout_delay" => Ok(GeneratedField::MinPlayoutDelay), "maxPlayoutDelay" | "max_playout_delay" => Ok(GeneratedField::MaxPlayoutDelay), "syncStreams" | "sync_streams" => Ok(GeneratedField::SyncStreams), "replayEnabled" | "replay_enabled" => Ok(GeneratedField::ReplayEnabled), + "agents" => Ok(GeneratedField::Agents), _ => Ok(GeneratedField::__SkipField__), } } @@ -3735,18 +3735,18 @@ impl<'de> serde::Deserialize<'de> for CreateRoomRequest { V: serde::de::MapAccess<'de>, { let mut name__ = None; - let mut config_name__ = None; + let mut room_preset__ = None; let mut empty_timeout__ = None; let mut departure_timeout__ = None; let mut max_participants__ = None; let mut node_id__ = None; let mut metadata__ = None; let mut egress__ = None; - let mut agent__ = None; let mut min_playout_delay__ = None; let mut max_playout_delay__ = None; let mut sync_streams__ = None; let mut replay_enabled__ = None; + let mut agents__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::Name => { @@ -3755,11 +3755,11 @@ impl<'de> serde::Deserialize<'de> for CreateRoomRequest { } name__ = Some(map_.next_value()?); } - GeneratedField::ConfigName => { - if config_name__.is_some() { - return Err(serde::de::Error::duplicate_field("configName")); + GeneratedField::RoomPreset => { + if room_preset__.is_some() { + return Err(serde::de::Error::duplicate_field("roomPreset")); } - config_name__ = Some(map_.next_value()?); + room_preset__ = Some(map_.next_value()?); } GeneratedField::EmptyTimeout => { if empty_timeout__.is_some() { @@ -3803,12 +3803,6 @@ impl<'de> serde::Deserialize<'de> for CreateRoomRequest { } egress__ = map_.next_value()?; } - GeneratedField::Agent => { - if agent__.is_some() { - return Err(serde::de::Error::duplicate_field("agent")); - } - agent__ = map_.next_value()?; - } GeneratedField::MinPlayoutDelay => { if min_playout_delay__.is_some() { return Err(serde::de::Error::duplicate_field("minPlayoutDelay")); @@ -3837,6 +3831,12 @@ impl<'de> serde::Deserialize<'de> for CreateRoomRequest { } replay_enabled__ = Some(map_.next_value()?); } + GeneratedField::Agents => { + if agents__.is_some() { + return Err(serde::de::Error::duplicate_field("agents")); + } + agents__ = Some(map_.next_value()?); + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -3844,18 +3844,18 @@ impl<'de> serde::Deserialize<'de> for CreateRoomRequest { } Ok(CreateRoomRequest { name: name__.unwrap_or_default(), - config_name: config_name__.unwrap_or_default(), + room_preset: room_preset__.unwrap_or_default(), empty_timeout: empty_timeout__.unwrap_or_default(), departure_timeout: departure_timeout__.unwrap_or_default(), max_participants: max_participants__.unwrap_or_default(), node_id: node_id__.unwrap_or_default(), metadata: metadata__.unwrap_or_default(), egress: egress__, - agent: agent__, min_playout_delay: min_playout_delay__.unwrap_or_default(), max_playout_delay: max_playout_delay__.unwrap_or_default(), sync_streams: sync_streams__.unwrap_or_default(), replay_enabled: replay_enabled__.unwrap_or_default(), + agents: agents__.unwrap_or_default(), }) } } @@ -4268,6 +4268,9 @@ impl serde::Serialize for CreateSipParticipantRequest { if !self.sip_call_to.is_empty() { len += 1; } + if !self.sip_number.is_empty() { + len += 1; + } if !self.room_name.is_empty() { len += 1; } @@ -4289,9 +4292,21 @@ impl serde::Serialize for CreateSipParticipantRequest { if self.play_ringtone { len += 1; } + if self.play_dialtone { + len += 1; + } if self.hide_phone_number { len += 1; } + if self.ringing_timeout.is_some() { + len += 1; + } + if self.max_call_duration.is_some() { + len += 1; + } + if self.enable_krisp { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.CreateSIPParticipantRequest", len)?; if !self.sip_trunk_id.is_empty() { struct_ser.serialize_field("sipTrunkId", &self.sip_trunk_id)?; @@ -4299,6 +4314,9 @@ impl serde::Serialize for CreateSipParticipantRequest { if !self.sip_call_to.is_empty() { struct_ser.serialize_field("sipCallTo", &self.sip_call_to)?; } + if !self.sip_number.is_empty() { + struct_ser.serialize_field("sipNumber", &self.sip_number)?; + } if !self.room_name.is_empty() { struct_ser.serialize_field("roomName", &self.room_name)?; } @@ -4320,9 +4338,21 @@ impl serde::Serialize for CreateSipParticipantRequest { if self.play_ringtone { struct_ser.serialize_field("playRingtone", &self.play_ringtone)?; } + if self.play_dialtone { + struct_ser.serialize_field("playDialtone", &self.play_dialtone)?; + } if self.hide_phone_number { struct_ser.serialize_field("hidePhoneNumber", &self.hide_phone_number)?; } + if let Some(v) = self.ringing_timeout.as_ref() { + struct_ser.serialize_field("ringingTimeout", v)?; + } + if let Some(v) = self.max_call_duration.as_ref() { + struct_ser.serialize_field("maxCallDuration", v)?; + } + if self.enable_krisp { + struct_ser.serialize_field("enableKrisp", &self.enable_krisp)?; + } struct_ser.end() } } @@ -4337,6 +4367,8 @@ impl<'de> serde::Deserialize<'de> for CreateSipParticipantRequest { "sipTrunkId", "sip_call_to", "sipCallTo", + "sip_number", + "sipNumber", "room_name", "roomName", "participant_identity", @@ -4350,14 +4382,23 @@ impl<'de> serde::Deserialize<'de> for CreateSipParticipantRequest { "dtmf", "play_ringtone", "playRingtone", + "play_dialtone", + "playDialtone", "hide_phone_number", "hidePhoneNumber", + "ringing_timeout", + "ringingTimeout", + "max_call_duration", + "maxCallDuration", + "enable_krisp", + "enableKrisp", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { SipTrunkId, SipCallTo, + SipNumber, RoomName, ParticipantIdentity, ParticipantName, @@ -4365,7 +4406,11 @@ impl<'de> serde::Deserialize<'de> for CreateSipParticipantRequest { ParticipantAttributes, Dtmf, PlayRingtone, + PlayDialtone, HidePhoneNumber, + RingingTimeout, + MaxCallDuration, + EnableKrisp, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -4390,6 +4435,7 @@ impl<'de> serde::Deserialize<'de> for CreateSipParticipantRequest { match value { "sipTrunkId" | "sip_trunk_id" => Ok(GeneratedField::SipTrunkId), "sipCallTo" | "sip_call_to" => Ok(GeneratedField::SipCallTo), + "sipNumber" | "sip_number" => Ok(GeneratedField::SipNumber), "roomName" | "room_name" => Ok(GeneratedField::RoomName), "participantIdentity" | "participant_identity" => Ok(GeneratedField::ParticipantIdentity), "participantName" | "participant_name" => Ok(GeneratedField::ParticipantName), @@ -4397,7 +4443,11 @@ impl<'de> serde::Deserialize<'de> for CreateSipParticipantRequest { "participantAttributes" | "participant_attributes" => Ok(GeneratedField::ParticipantAttributes), "dtmf" => Ok(GeneratedField::Dtmf), "playRingtone" | "play_ringtone" => Ok(GeneratedField::PlayRingtone), + "playDialtone" | "play_dialtone" => Ok(GeneratedField::PlayDialtone), "hidePhoneNumber" | "hide_phone_number" => Ok(GeneratedField::HidePhoneNumber), + "ringingTimeout" | "ringing_timeout" => Ok(GeneratedField::RingingTimeout), + "maxCallDuration" | "max_call_duration" => Ok(GeneratedField::MaxCallDuration), + "enableKrisp" | "enable_krisp" => Ok(GeneratedField::EnableKrisp), _ => Ok(GeneratedField::__SkipField__), } } @@ -4419,6 +4469,7 @@ impl<'de> serde::Deserialize<'de> for CreateSipParticipantRequest { { let mut sip_trunk_id__ = None; let mut sip_call_to__ = None; + let mut sip_number__ = None; let mut room_name__ = None; let mut participant_identity__ = None; let mut participant_name__ = None; @@ -4426,7 +4477,11 @@ impl<'de> serde::Deserialize<'de> for CreateSipParticipantRequest { let mut participant_attributes__ = None; let mut dtmf__ = None; let mut play_ringtone__ = None; + let mut play_dialtone__ = None; let mut hide_phone_number__ = None; + let mut ringing_timeout__ = None; + let mut max_call_duration__ = None; + let mut enable_krisp__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::SipTrunkId => { @@ -4441,6 +4496,12 @@ impl<'de> serde::Deserialize<'de> for CreateSipParticipantRequest { } sip_call_to__ = Some(map_.next_value()?); } + GeneratedField::SipNumber => { + if sip_number__.is_some() { + return Err(serde::de::Error::duplicate_field("sipNumber")); + } + sip_number__ = Some(map_.next_value()?); + } GeneratedField::RoomName => { if room_name__.is_some() { return Err(serde::de::Error::duplicate_field("roomName")); @@ -4485,12 +4546,36 @@ impl<'de> serde::Deserialize<'de> for CreateSipParticipantRequest { } play_ringtone__ = Some(map_.next_value()?); } + GeneratedField::PlayDialtone => { + if play_dialtone__.is_some() { + return Err(serde::de::Error::duplicate_field("playDialtone")); + } + play_dialtone__ = Some(map_.next_value()?); + } GeneratedField::HidePhoneNumber => { if hide_phone_number__.is_some() { return Err(serde::de::Error::duplicate_field("hidePhoneNumber")); } hide_phone_number__ = Some(map_.next_value()?); } + GeneratedField::RingingTimeout => { + if ringing_timeout__.is_some() { + return Err(serde::de::Error::duplicate_field("ringingTimeout")); + } + ringing_timeout__ = map_.next_value()?; + } + GeneratedField::MaxCallDuration => { + if max_call_duration__.is_some() { + return Err(serde::de::Error::duplicate_field("maxCallDuration")); + } + max_call_duration__ = map_.next_value()?; + } + GeneratedField::EnableKrisp => { + if enable_krisp__.is_some() { + return Err(serde::de::Error::duplicate_field("enableKrisp")); + } + enable_krisp__ = Some(map_.next_value()?); + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -4499,6 +4584,7 @@ impl<'de> serde::Deserialize<'de> for CreateSipParticipantRequest { Ok(CreateSipParticipantRequest { sip_trunk_id: sip_trunk_id__.unwrap_or_default(), sip_call_to: sip_call_to__.unwrap_or_default(), + sip_number: sip_number__.unwrap_or_default(), room_name: room_name__.unwrap_or_default(), participant_identity: participant_identity__.unwrap_or_default(), participant_name: participant_name__.unwrap_or_default(), @@ -4506,7 +4592,11 @@ impl<'de> serde::Deserialize<'de> for CreateSipParticipantRequest { participant_attributes: participant_attributes__.unwrap_or_default(), dtmf: dtmf__.unwrap_or_default(), play_ringtone: play_ringtone__.unwrap_or_default(), + play_dialtone: play_dialtone__.unwrap_or_default(), hide_phone_number: hide_phone_number__.unwrap_or_default(), + ringing_timeout: ringing_timeout__, + max_call_duration: max_call_duration__, + enable_krisp: enable_krisp__.unwrap_or_default(), }) } } @@ -4981,6 +5071,12 @@ impl serde::Serialize for DataPacket { data_packet::Value::RpcResponse(v) => { struct_ser.serialize_field("rpcResponse", v)?; } + data_packet::Value::StreamHeader(v) => { + struct_ser.serialize_field("streamHeader", v)?; + } + data_packet::Value::StreamChunk(v) => { + struct_ser.serialize_field("streamChunk", v)?; + } } } struct_ser.end() @@ -5012,6 +5108,10 @@ impl<'de> serde::Deserialize<'de> for DataPacket { "rpcAck", "rpc_response", "rpcResponse", + "stream_header", + "streamHeader", + "stream_chunk", + "streamChunk", ]; #[allow(clippy::enum_variant_names)] @@ -5028,6 +5128,8 @@ impl<'de> serde::Deserialize<'de> for DataPacket { RpcRequest, RpcAck, RpcResponse, + StreamHeader, + StreamChunk, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -5062,6 +5164,8 @@ impl<'de> serde::Deserialize<'de> for DataPacket { "rpcRequest" | "rpc_request" => Ok(GeneratedField::RpcRequest), "rpcAck" | "rpc_ack" => Ok(GeneratedField::RpcAck), "rpcResponse" | "rpc_response" => Ok(GeneratedField::RpcResponse), + "streamHeader" | "stream_header" => Ok(GeneratedField::StreamHeader), + "streamChunk" | "stream_chunk" => Ok(GeneratedField::StreamChunk), _ => Ok(GeneratedField::__SkipField__), } } @@ -5166,6 +5270,20 @@ impl<'de> serde::Deserialize<'de> for DataPacket { return Err(serde::de::Error::duplicate_field("rpcResponse")); } value__ = map_.next_value::<::std::option::Option<_>>()?.map(data_packet::Value::RpcResponse) +; + } + GeneratedField::StreamHeader => { + if value__.is_some() { + return Err(serde::de::Error::duplicate_field("streamHeader")); + } + value__ = map_.next_value::<::std::option::Option<_>>()?.map(data_packet::Value::StreamHeader) +; + } + GeneratedField::StreamChunk => { + if value__.is_some() { + return Err(serde::de::Error::duplicate_field("streamChunk")); + } + value__ = map_.next_value::<::std::option::Option<_>>()?.map(data_packet::Value::StreamChunk) ; } GeneratedField::__SkipField__ => { @@ -5255,46 +5373,29 @@ impl<'de> serde::Deserialize<'de> for data_packet::Kind { deserializer.deserialize_any(GeneratedVisitor) } } -impl serde::Serialize for DeleteAgentDispatchRequest { +impl serde::Serialize for DataStream { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where S: serde::Serializer, { use serde::ser::SerializeStruct; - let mut len = 0; - if !self.dispatch_id.is_empty() { - len += 1; - } - if !self.room.is_empty() { - len += 1; - } - let mut struct_ser = serializer.serialize_struct("livekit.DeleteAgentDispatchRequest", len)?; - if !self.dispatch_id.is_empty() { - struct_ser.serialize_field("dispatchId", &self.dispatch_id)?; - } - if !self.room.is_empty() { - struct_ser.serialize_field("room", &self.room)?; - } + let len = 0; + let struct_ser = serializer.serialize_struct("livekit.DataStream", len)?; struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for DeleteAgentDispatchRequest { +impl<'de> serde::Deserialize<'de> for DataStream { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "dispatch_id", - "dispatchId", - "room", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { - DispatchId, - Room, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -5316,11 +5417,7 @@ impl<'de> serde::Deserialize<'de> for DeleteAgentDispatchRequest { where E: serde::de::Error, { - match value { - "dispatchId" | "dispatch_id" => Ok(GeneratedField::DispatchId), - "room" => Ok(GeneratedField::Room), - _ => Ok(GeneratedField::__SkipField__), - } + Ok(GeneratedField::__SkipField__) } } deserializer.deserialize_identifier(GeneratedVisitor) @@ -5328,47 +5425,27 @@ impl<'de> serde::Deserialize<'de> for DeleteAgentDispatchRequest { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = DeleteAgentDispatchRequest; + type Value = DataStream; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.DeleteAgentDispatchRequest") + formatter.write_str("struct livekit.DataStream") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut dispatch_id__ = None; - let mut room__ = None; - while let Some(k) = map_.next_key()? { - match k { - GeneratedField::DispatchId => { - if dispatch_id__.is_some() { - return Err(serde::de::Error::duplicate_field("dispatchId")); - } - dispatch_id__ = Some(map_.next_value()?); - } - GeneratedField::Room => { - if room__.is_some() { - return Err(serde::de::Error::duplicate_field("room")); - } - room__ = Some(map_.next_value()?); - } - GeneratedField::__SkipField__ => { - let _ = map_.next_value::()?; - } - } + while map_.next_key::()?.is_some() { + let _ = map_.next_value::()?; } - Ok(DeleteAgentDispatchRequest { - dispatch_id: dispatch_id__.unwrap_or_default(), - room: room__.unwrap_or_default(), + Ok(DataStream { }) } } - deserializer.deserialize_struct("livekit.DeleteAgentDispatchRequest", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.DataStream", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for DeleteIngressRequest { +impl serde::Serialize for data_stream::Chunk { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -5376,30 +5453,77 @@ impl serde::Serialize for DeleteIngressRequest { { use serde::ser::SerializeStruct; let mut len = 0; - if !self.ingress_id.is_empty() { + if !self.stream_id.is_empty() { len += 1; } - let mut struct_ser = serializer.serialize_struct("livekit.DeleteIngressRequest", len)?; - if !self.ingress_id.is_empty() { - struct_ser.serialize_field("ingressId", &self.ingress_id)?; + if self.chunk_index != 0 { + len += 1; + } + if !self.content.is_empty() { + len += 1; + } + if self.complete { + len += 1; + } + if self.version != 0 { + len += 1; + } + if self.iv.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.DataStream.Chunk", len)?; + if !self.stream_id.is_empty() { + struct_ser.serialize_field("streamId", &self.stream_id)?; + } + if self.chunk_index != 0 { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("chunkIndex", ToString::to_string(&self.chunk_index).as_str())?; + } + if !self.content.is_empty() { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("content", pbjson::private::base64::encode(&self.content).as_str())?; + } + if self.complete { + struct_ser.serialize_field("complete", &self.complete)?; + } + if self.version != 0 { + struct_ser.serialize_field("version", &self.version)?; + } + if let Some(v) = self.iv.as_ref() { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("iv", pbjson::private::base64::encode(&v).as_str())?; } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for DeleteIngressRequest { +impl<'de> serde::Deserialize<'de> for data_stream::Chunk { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "ingress_id", - "ingressId", + "stream_id", + "streamId", + "chunk_index", + "chunkIndex", + "content", + "complete", + "version", + "iv", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { - IngressId, + StreamId, + ChunkIndex, + Content, + Complete, + Version, + Iv, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -5422,7 +5546,12 @@ impl<'de> serde::Deserialize<'de> for DeleteIngressRequest { E: serde::de::Error, { match value { - "ingressId" | "ingress_id" => Ok(GeneratedField::IngressId), + "streamId" | "stream_id" => Ok(GeneratedField::StreamId), + "chunkIndex" | "chunk_index" => Ok(GeneratedField::ChunkIndex), + "content" => Ok(GeneratedField::Content), + "complete" => Ok(GeneratedField::Complete), + "version" => Ok(GeneratedField::Version), + "iv" => Ok(GeneratedField::Iv), _ => Ok(GeneratedField::__SkipField__), } } @@ -5432,39 +5561,87 @@ impl<'de> serde::Deserialize<'de> for DeleteIngressRequest { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = DeleteIngressRequest; + type Value = data_stream::Chunk; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.DeleteIngressRequest") + formatter.write_str("struct livekit.DataStream.Chunk") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut ingress_id__ = None; + let mut stream_id__ = None; + let mut chunk_index__ = None; + let mut content__ = None; + let mut complete__ = None; + let mut version__ = None; + let mut iv__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::IngressId => { - if ingress_id__.is_some() { - return Err(serde::de::Error::duplicate_field("ingressId")); + GeneratedField::StreamId => { + if stream_id__.is_some() { + return Err(serde::de::Error::duplicate_field("streamId")); } - ingress_id__ = Some(map_.next_value()?); + stream_id__ = Some(map_.next_value()?); + } + GeneratedField::ChunkIndex => { + if chunk_index__.is_some() { + return Err(serde::de::Error::duplicate_field("chunkIndex")); + } + chunk_index__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::Content => { + if content__.is_some() { + return Err(serde::de::Error::duplicate_field("content")); + } + content__ = + Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) + ; + } + GeneratedField::Complete => { + if complete__.is_some() { + return Err(serde::de::Error::duplicate_field("complete")); + } + complete__ = Some(map_.next_value()?); + } + GeneratedField::Version => { + if version__.is_some() { + return Err(serde::de::Error::duplicate_field("version")); + } + version__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::Iv => { + if iv__.is_some() { + return Err(serde::de::Error::duplicate_field("iv")); + } + iv__ = + map_.next_value::<::std::option::Option<::pbjson::private::BytesDeserialize<_>>>()?.map(|x| x.0) + ; } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } } } - Ok(DeleteIngressRequest { - ingress_id: ingress_id__.unwrap_or_default(), + Ok(data_stream::Chunk { + stream_id: stream_id__.unwrap_or_default(), + chunk_index: chunk_index__.unwrap_or_default(), + content: content__.unwrap_or_default(), + complete: complete__.unwrap_or_default(), + version: version__.unwrap_or_default(), + iv: iv__, }) } } - deserializer.deserialize_struct("livekit.DeleteIngressRequest", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.DataStream.Chunk", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for DeleteRoomRequest { +impl serde::Serialize for data_stream::FileHeader { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -5472,29 +5649,30 @@ impl serde::Serialize for DeleteRoomRequest { { use serde::ser::SerializeStruct; let mut len = 0; - if !self.room.is_empty() { + if !self.file_name.is_empty() { len += 1; } - let mut struct_ser = serializer.serialize_struct("livekit.DeleteRoomRequest", len)?; - if !self.room.is_empty() { - struct_ser.serialize_field("room", &self.room)?; + let mut struct_ser = serializer.serialize_struct("livekit.DataStream.FileHeader", len)?; + if !self.file_name.is_empty() { + struct_ser.serialize_field("fileName", &self.file_name)?; } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for DeleteRoomRequest { +impl<'de> serde::Deserialize<'de> for data_stream::FileHeader { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "room", + "file_name", + "fileName", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { - Room, + FileName, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -5517,7 +5695,7 @@ impl<'de> serde::Deserialize<'de> for DeleteRoomRequest { E: serde::de::Error, { match value { - "room" => Ok(GeneratedField::Room), + "fileName" | "file_name" => Ok(GeneratedField::FileName), _ => Ok(GeneratedField::__SkipField__), } } @@ -5527,61 +5705,157 @@ impl<'de> serde::Deserialize<'de> for DeleteRoomRequest { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = DeleteRoomRequest; + type Value = data_stream::FileHeader; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.DeleteRoomRequest") + formatter.write_str("struct livekit.DataStream.FileHeader") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut room__ = None; + let mut file_name__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::Room => { - if room__.is_some() { - return Err(serde::de::Error::duplicate_field("room")); + GeneratedField::FileName => { + if file_name__.is_some() { + return Err(serde::de::Error::duplicate_field("fileName")); } - room__ = Some(map_.next_value()?); + file_name__ = Some(map_.next_value()?); } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } } } - Ok(DeleteRoomRequest { - room: room__.unwrap_or_default(), + Ok(data_stream::FileHeader { + file_name: file_name__.unwrap_or_default(), }) } } - deserializer.deserialize_struct("livekit.DeleteRoomRequest", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.DataStream.FileHeader", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for DeleteRoomResponse { +impl serde::Serialize for data_stream::Header { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where S: serde::Serializer, { use serde::ser::SerializeStruct; - let len = 0; - let struct_ser = serializer.serialize_struct("livekit.DeleteRoomResponse", len)?; + let mut len = 0; + if !self.stream_id.is_empty() { + len += 1; + } + if self.timestamp != 0 { + len += 1; + } + if !self.topic.is_empty() { + len += 1; + } + if !self.mime_type.is_empty() { + len += 1; + } + if self.total_length.is_some() { + len += 1; + } + if self.total_chunks.is_some() { + len += 1; + } + if self.encryption_type != 0 { + len += 1; + } + if !self.extensions.is_empty() { + len += 1; + } + if self.content_header.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.DataStream.Header", len)?; + if !self.stream_id.is_empty() { + struct_ser.serialize_field("streamId", &self.stream_id)?; + } + if self.timestamp != 0 { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("timestamp", ToString::to_string(&self.timestamp).as_str())?; + } + if !self.topic.is_empty() { + struct_ser.serialize_field("topic", &self.topic)?; + } + if !self.mime_type.is_empty() { + struct_ser.serialize_field("mimeType", &self.mime_type)?; + } + if let Some(v) = self.total_length.as_ref() { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("totalLength", ToString::to_string(&v).as_str())?; + } + if let Some(v) = self.total_chunks.as_ref() { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("totalChunks", ToString::to_string(&v).as_str())?; + } + if self.encryption_type != 0 { + let v = encryption::Type::try_from(self.encryption_type) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.encryption_type)))?; + struct_ser.serialize_field("encryptionType", &v)?; + } + if !self.extensions.is_empty() { + struct_ser.serialize_field("extensions", &self.extensions)?; + } + if let Some(v) = self.content_header.as_ref() { + match v { + data_stream::header::ContentHeader::TextHeader(v) => { + struct_ser.serialize_field("textHeader", v)?; + } + data_stream::header::ContentHeader::FileHeader(v) => { + struct_ser.serialize_field("fileHeader", v)?; + } + } + } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for DeleteRoomResponse { +impl<'de> serde::Deserialize<'de> for data_stream::Header { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ + "stream_id", + "streamId", + "timestamp", + "topic", + "mime_type", + "mimeType", + "total_length", + "totalLength", + "total_chunks", + "totalChunks", + "encryption_type", + "encryptionType", + "extensions", + "text_header", + "textHeader", + "file_header", + "fileHeader", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { + StreamId, + Timestamp, + Topic, + MimeType, + TotalLength, + TotalChunks, + EncryptionType, + Extensions, + TextHeader, + FileHeader, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -5603,7 +5877,19 @@ impl<'de> serde::Deserialize<'de> for DeleteRoomResponse { where E: serde::de::Error, { - Ok(GeneratedField::__SkipField__) + match value { + "streamId" | "stream_id" => Ok(GeneratedField::StreamId), + "timestamp" => Ok(GeneratedField::Timestamp), + "topic" => Ok(GeneratedField::Topic), + "mimeType" | "mime_type" => Ok(GeneratedField::MimeType), + "totalLength" | "total_length" => Ok(GeneratedField::TotalLength), + "totalChunks" | "total_chunks" => Ok(GeneratedField::TotalChunks), + "encryptionType" | "encryption_type" => Ok(GeneratedField::EncryptionType), + "extensions" => Ok(GeneratedField::Extensions), + "textHeader" | "text_header" => Ok(GeneratedField::TextHeader), + "fileHeader" | "file_header" => Ok(GeneratedField::FileHeader), + _ => Ok(GeneratedField::__SkipField__), + } } } deserializer.deserialize_identifier(GeneratedVisitor) @@ -5611,27 +5897,196 @@ impl<'de> serde::Deserialize<'de> for DeleteRoomResponse { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = DeleteRoomResponse; + type Value = data_stream::Header; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.DeleteRoomResponse") + formatter.write_str("struct livekit.DataStream.Header") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - while map_.next_key::()?.is_some() { - let _ = map_.next_value::()?; + let mut stream_id__ = None; + let mut timestamp__ = None; + let mut topic__ = None; + let mut mime_type__ = None; + let mut total_length__ = None; + let mut total_chunks__ = None; + let mut encryption_type__ = None; + let mut extensions__ = None; + let mut content_header__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::StreamId => { + if stream_id__.is_some() { + return Err(serde::de::Error::duplicate_field("streamId")); + } + stream_id__ = Some(map_.next_value()?); + } + GeneratedField::Timestamp => { + if timestamp__.is_some() { + return Err(serde::de::Error::duplicate_field("timestamp")); + } + timestamp__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::Topic => { + if topic__.is_some() { + return Err(serde::de::Error::duplicate_field("topic")); + } + topic__ = Some(map_.next_value()?); + } + GeneratedField::MimeType => { + if mime_type__.is_some() { + return Err(serde::de::Error::duplicate_field("mimeType")); + } + mime_type__ = Some(map_.next_value()?); + } + GeneratedField::TotalLength => { + if total_length__.is_some() { + return Err(serde::de::Error::duplicate_field("totalLength")); + } + total_length__ = + map_.next_value::<::std::option::Option<::pbjson::private::NumberDeserialize<_>>>()?.map(|x| x.0) + ; + } + GeneratedField::TotalChunks => { + if total_chunks__.is_some() { + return Err(serde::de::Error::duplicate_field("totalChunks")); + } + total_chunks__ = + map_.next_value::<::std::option::Option<::pbjson::private::NumberDeserialize<_>>>()?.map(|x| x.0) + ; + } + GeneratedField::EncryptionType => { + if encryption_type__.is_some() { + return Err(serde::de::Error::duplicate_field("encryptionType")); + } + encryption_type__ = Some(map_.next_value::()? as i32); + } + GeneratedField::Extensions => { + if extensions__.is_some() { + return Err(serde::de::Error::duplicate_field("extensions")); + } + extensions__ = Some( + map_.next_value::>()? + ); + } + GeneratedField::TextHeader => { + if content_header__.is_some() { + return Err(serde::de::Error::duplicate_field("textHeader")); + } + content_header__ = map_.next_value::<::std::option::Option<_>>()?.map(data_stream::header::ContentHeader::TextHeader) +; + } + GeneratedField::FileHeader => { + if content_header__.is_some() { + return Err(serde::de::Error::duplicate_field("fileHeader")); + } + content_header__ = map_.next_value::<::std::option::Option<_>>()?.map(data_stream::header::ContentHeader::FileHeader) +; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } } - Ok(DeleteRoomResponse { + Ok(data_stream::Header { + stream_id: stream_id__.unwrap_or_default(), + timestamp: timestamp__.unwrap_or_default(), + topic: topic__.unwrap_or_default(), + mime_type: mime_type__.unwrap_or_default(), + total_length: total_length__, + total_chunks: total_chunks__, + encryption_type: encryption_type__.unwrap_or_default(), + extensions: extensions__.unwrap_or_default(), + content_header: content_header__, }) } } - deserializer.deserialize_struct("livekit.DeleteRoomResponse", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.DataStream.Header", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for DeleteSipDispatchRuleRequest { +impl serde::Serialize for data_stream::OperationType { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + let variant = match self { + Self::Create => "CREATE", + Self::Update => "UPDATE", + Self::Delete => "DELETE", + Self::Reaction => "REACTION", + }; + serializer.serialize_str(variant) + } +} +impl<'de> serde::Deserialize<'de> for data_stream::OperationType { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "CREATE", + "UPDATE", + "DELETE", + "REACTION", + ]; + + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = data_stream::OperationType; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + fn visit_i64(self, v: i64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self) + }) + } + + fn visit_u64(self, v: u64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(v), &self) + }) + } + + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "CREATE" => Ok(data_stream::OperationType::Create), + "UPDATE" => Ok(data_stream::OperationType::Update), + "DELETE" => Ok(data_stream::OperationType::Delete), + "REACTION" => Ok(data_stream::OperationType::Reaction), + _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), + } + } + } + deserializer.deserialize_any(GeneratedVisitor) + } +} +impl serde::Serialize for data_stream::TextHeader { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -5639,30 +6094,66 @@ impl serde::Serialize for DeleteSipDispatchRuleRequest { { use serde::ser::SerializeStruct; let mut len = 0; - if !self.sip_dispatch_rule_id.is_empty() { + if self.operation_type != 0 { len += 1; } - let mut struct_ser = serializer.serialize_struct("livekit.DeleteSIPDispatchRuleRequest", len)?; - if !self.sip_dispatch_rule_id.is_empty() { - struct_ser.serialize_field("sipDispatchRuleId", &self.sip_dispatch_rule_id)?; + if self.version != 0 { + len += 1; + } + if !self.reply_to_stream_id.is_empty() { + len += 1; + } + if !self.attached_stream_ids.is_empty() { + len += 1; + } + if self.generated { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.DataStream.TextHeader", len)?; + if self.operation_type != 0 { + let v = data_stream::OperationType::try_from(self.operation_type) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.operation_type)))?; + struct_ser.serialize_field("operationType", &v)?; + } + if self.version != 0 { + struct_ser.serialize_field("version", &self.version)?; + } + if !self.reply_to_stream_id.is_empty() { + struct_ser.serialize_field("replyToStreamId", &self.reply_to_stream_id)?; + } + if !self.attached_stream_ids.is_empty() { + struct_ser.serialize_field("attachedStreamIds", &self.attached_stream_ids)?; + } + if self.generated { + struct_ser.serialize_field("generated", &self.generated)?; } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for DeleteSipDispatchRuleRequest { +impl<'de> serde::Deserialize<'de> for data_stream::TextHeader { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "sip_dispatch_rule_id", - "sipDispatchRuleId", + "operation_type", + "operationType", + "version", + "reply_to_stream_id", + "replyToStreamId", + "attached_stream_ids", + "attachedStreamIds", + "generated", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { - SipDispatchRuleId, + OperationType, + Version, + ReplyToStreamId, + AttachedStreamIds, + Generated, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -5685,7 +6176,11 @@ impl<'de> serde::Deserialize<'de> for DeleteSipDispatchRuleRequest { E: serde::de::Error, { match value { - "sipDispatchRuleId" | "sip_dispatch_rule_id" => Ok(GeneratedField::SipDispatchRuleId), + "operationType" | "operation_type" => Ok(GeneratedField::OperationType), + "version" => Ok(GeneratedField::Version), + "replyToStreamId" | "reply_to_stream_id" => Ok(GeneratedField::ReplyToStreamId), + "attachedStreamIds" | "attached_stream_ids" => Ok(GeneratedField::AttachedStreamIds), + "generated" => Ok(GeneratedField::Generated), _ => Ok(GeneratedField::__SkipField__), } } @@ -5695,39 +6190,73 @@ impl<'de> serde::Deserialize<'de> for DeleteSipDispatchRuleRequest { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = DeleteSipDispatchRuleRequest; + type Value = data_stream::TextHeader; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.DeleteSIPDispatchRuleRequest") + formatter.write_str("struct livekit.DataStream.TextHeader") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut sip_dispatch_rule_id__ = None; + let mut operation_type__ = None; + let mut version__ = None; + let mut reply_to_stream_id__ = None; + let mut attached_stream_ids__ = None; + let mut generated__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::SipDispatchRuleId => { - if sip_dispatch_rule_id__.is_some() { - return Err(serde::de::Error::duplicate_field("sipDispatchRuleId")); + GeneratedField::OperationType => { + if operation_type__.is_some() { + return Err(serde::de::Error::duplicate_field("operationType")); } - sip_dispatch_rule_id__ = Some(map_.next_value()?); + operation_type__ = Some(map_.next_value::()? as i32); + } + GeneratedField::Version => { + if version__.is_some() { + return Err(serde::de::Error::duplicate_field("version")); + } + version__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::ReplyToStreamId => { + if reply_to_stream_id__.is_some() { + return Err(serde::de::Error::duplicate_field("replyToStreamId")); + } + reply_to_stream_id__ = Some(map_.next_value()?); + } + GeneratedField::AttachedStreamIds => { + if attached_stream_ids__.is_some() { + return Err(serde::de::Error::duplicate_field("attachedStreamIds")); + } + attached_stream_ids__ = Some(map_.next_value()?); + } + GeneratedField::Generated => { + if generated__.is_some() { + return Err(serde::de::Error::duplicate_field("generated")); + } + generated__ = Some(map_.next_value()?); } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } } } - Ok(DeleteSipDispatchRuleRequest { - sip_dispatch_rule_id: sip_dispatch_rule_id__.unwrap_or_default(), + Ok(data_stream::TextHeader { + operation_type: operation_type__.unwrap_or_default(), + version: version__.unwrap_or_default(), + reply_to_stream_id: reply_to_stream_id__.unwrap_or_default(), + attached_stream_ids: attached_stream_ids__.unwrap_or_default(), + generated: generated__.unwrap_or_default(), }) } } - deserializer.deserialize_struct("livekit.DeleteSIPDispatchRuleRequest", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.DataStream.TextHeader", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for DeleteSipTrunkRequest { +impl serde::Serialize for DeleteAgentDispatchRequest { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -5735,30 +6264,38 @@ impl serde::Serialize for DeleteSipTrunkRequest { { use serde::ser::SerializeStruct; let mut len = 0; - if !self.sip_trunk_id.is_empty() { + if !self.dispatch_id.is_empty() { len += 1; } - let mut struct_ser = serializer.serialize_struct("livekit.DeleteSIPTrunkRequest", len)?; - if !self.sip_trunk_id.is_empty() { - struct_ser.serialize_field("sipTrunkId", &self.sip_trunk_id)?; + if !self.room.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.DeleteAgentDispatchRequest", len)?; + if !self.dispatch_id.is_empty() { + struct_ser.serialize_field("dispatchId", &self.dispatch_id)?; + } + if !self.room.is_empty() { + struct_ser.serialize_field("room", &self.room)?; } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for DeleteSipTrunkRequest { +impl<'de> serde::Deserialize<'de> for DeleteAgentDispatchRequest { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "sip_trunk_id", - "sipTrunkId", + "dispatch_id", + "dispatchId", + "room", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { - SipTrunkId, + DispatchId, + Room, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -5781,7 +6318,8 @@ impl<'de> serde::Deserialize<'de> for DeleteSipTrunkRequest { E: serde::de::Error, { match value { - "sipTrunkId" | "sip_trunk_id" => Ok(GeneratedField::SipTrunkId), + "dispatchId" | "dispatch_id" => Ok(GeneratedField::DispatchId), + "room" => Ok(GeneratedField::Room), _ => Ok(GeneratedField::__SkipField__), } } @@ -5791,39 +6329,47 @@ impl<'de> serde::Deserialize<'de> for DeleteSipTrunkRequest { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = DeleteSipTrunkRequest; + type Value = DeleteAgentDispatchRequest; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.DeleteSIPTrunkRequest") + formatter.write_str("struct livekit.DeleteAgentDispatchRequest") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut sip_trunk_id__ = None; + let mut dispatch_id__ = None; + let mut room__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::SipTrunkId => { - if sip_trunk_id__.is_some() { - return Err(serde::de::Error::duplicate_field("sipTrunkId")); + GeneratedField::DispatchId => { + if dispatch_id__.is_some() { + return Err(serde::de::Error::duplicate_field("dispatchId")); } - sip_trunk_id__ = Some(map_.next_value()?); + dispatch_id__ = Some(map_.next_value()?); + } + GeneratedField::Room => { + if room__.is_some() { + return Err(serde::de::Error::duplicate_field("room")); + } + room__ = Some(map_.next_value()?); } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } } } - Ok(DeleteSipTrunkRequest { - sip_trunk_id: sip_trunk_id__.unwrap_or_default(), + Ok(DeleteAgentDispatchRequest { + dispatch_id: dispatch_id__.unwrap_or_default(), + room: room__.unwrap_or_default(), }) } } - deserializer.deserialize_struct("livekit.DeleteSIPTrunkRequest", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.DeleteAgentDispatchRequest", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for DirectFileOutput { +impl serde::Serialize for DeleteIngressRequest { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -5831,65 +6377,30 @@ impl serde::Serialize for DirectFileOutput { { use serde::ser::SerializeStruct; let mut len = 0; - if !self.filepath.is_empty() { - len += 1; - } - if self.disable_manifest { - len += 1; - } - if self.output.is_some() { + if !self.ingress_id.is_empty() { len += 1; } - let mut struct_ser = serializer.serialize_struct("livekit.DirectFileOutput", len)?; - if !self.filepath.is_empty() { - struct_ser.serialize_field("filepath", &self.filepath)?; - } - if self.disable_manifest { - struct_ser.serialize_field("disableManifest", &self.disable_manifest)?; - } - if let Some(v) = self.output.as_ref() { - match v { - direct_file_output::Output::S3(v) => { - struct_ser.serialize_field("s3", v)?; - } - direct_file_output::Output::Gcp(v) => { - struct_ser.serialize_field("gcp", v)?; - } - direct_file_output::Output::Azure(v) => { - struct_ser.serialize_field("azure", v)?; - } - direct_file_output::Output::AliOss(v) => { - struct_ser.serialize_field("aliOSS", v)?; - } - } + let mut struct_ser = serializer.serialize_struct("livekit.DeleteIngressRequest", len)?; + if !self.ingress_id.is_empty() { + struct_ser.serialize_field("ingressId", &self.ingress_id)?; } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for DirectFileOutput { +impl<'de> serde::Deserialize<'de> for DeleteIngressRequest { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "filepath", - "disable_manifest", - "disableManifest", - "s3", - "gcp", - "azure", - "aliOSS", + "ingress_id", + "ingressId", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { - Filepath, - DisableManifest, - S3, - Gcp, - Azure, - AliOss, + IngressId, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -5912,12 +6423,7 @@ impl<'de> serde::Deserialize<'de> for DirectFileOutput { E: serde::de::Error, { match value { - "filepath" => Ok(GeneratedField::Filepath), - "disableManifest" | "disable_manifest" => Ok(GeneratedField::DisableManifest), - "s3" => Ok(GeneratedField::S3), - "gcp" => Ok(GeneratedField::Gcp), - "azure" => Ok(GeneratedField::Azure), - "aliOSS" => Ok(GeneratedField::AliOss), + "ingressId" | "ingress_id" => Ok(GeneratedField::IngressId), _ => Ok(GeneratedField::__SkipField__), } } @@ -5927,77 +6433,39 @@ impl<'de> serde::Deserialize<'de> for DirectFileOutput { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = DirectFileOutput; + type Value = DeleteIngressRequest; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.DirectFileOutput") + formatter.write_str("struct livekit.DeleteIngressRequest") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut filepath__ = None; - let mut disable_manifest__ = None; - let mut output__ = None; + let mut ingress_id__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::Filepath => { - if filepath__.is_some() { - return Err(serde::de::Error::duplicate_field("filepath")); - } - filepath__ = Some(map_.next_value()?); - } - GeneratedField::DisableManifest => { - if disable_manifest__.is_some() { - return Err(serde::de::Error::duplicate_field("disableManifest")); - } - disable_manifest__ = Some(map_.next_value()?); - } - GeneratedField::S3 => { - if output__.is_some() { - return Err(serde::de::Error::duplicate_field("s3")); - } - output__ = map_.next_value::<::std::option::Option<_>>()?.map(direct_file_output::Output::S3) -; - } - GeneratedField::Gcp => { - if output__.is_some() { - return Err(serde::de::Error::duplicate_field("gcp")); - } - output__ = map_.next_value::<::std::option::Option<_>>()?.map(direct_file_output::Output::Gcp) -; - } - GeneratedField::Azure => { - if output__.is_some() { - return Err(serde::de::Error::duplicate_field("azure")); - } - output__ = map_.next_value::<::std::option::Option<_>>()?.map(direct_file_output::Output::Azure) -; - } - GeneratedField::AliOss => { - if output__.is_some() { - return Err(serde::de::Error::duplicate_field("aliOSS")); + GeneratedField::IngressId => { + if ingress_id__.is_some() { + return Err(serde::de::Error::duplicate_field("ingressId")); } - output__ = map_.next_value::<::std::option::Option<_>>()?.map(direct_file_output::Output::AliOss) -; + ingress_id__ = Some(map_.next_value()?); } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } } } - Ok(DirectFileOutput { - filepath: filepath__.unwrap_or_default(), - disable_manifest: disable_manifest__.unwrap_or_default(), - output: output__, + Ok(DeleteIngressRequest { + ingress_id: ingress_id__.unwrap_or_default(), }) } } - deserializer.deserialize_struct("livekit.DirectFileOutput", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.DeleteIngressRequest", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for DisabledCodecs { +impl serde::Serialize for DeleteRoomRequest { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -6005,37 +6473,29 @@ impl serde::Serialize for DisabledCodecs { { use serde::ser::SerializeStruct; let mut len = 0; - if !self.codecs.is_empty() { - len += 1; - } - if !self.publish.is_empty() { + if !self.room.is_empty() { len += 1; } - let mut struct_ser = serializer.serialize_struct("livekit.DisabledCodecs", len)?; - if !self.codecs.is_empty() { - struct_ser.serialize_field("codecs", &self.codecs)?; - } - if !self.publish.is_empty() { - struct_ser.serialize_field("publish", &self.publish)?; + let mut struct_ser = serializer.serialize_struct("livekit.DeleteRoomRequest", len)?; + if !self.room.is_empty() { + struct_ser.serialize_field("room", &self.room)?; } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for DisabledCodecs { +impl<'de> serde::Deserialize<'de> for DeleteRoomRequest { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "codecs", - "publish", + "room", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { - Codecs, - Publish, + Room, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -6058,8 +6518,7 @@ impl<'de> serde::Deserialize<'de> for DisabledCodecs { E: serde::de::Error, { match value { - "codecs" => Ok(GeneratedField::Codecs), - "publish" => Ok(GeneratedField::Publish), + "room" => Ok(GeneratedField::Room), _ => Ok(GeneratedField::__SkipField__), } } @@ -6069,110 +6528,658 @@ impl<'de> serde::Deserialize<'de> for DisabledCodecs { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = DisabledCodecs; + type Value = DeleteRoomRequest; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.DisabledCodecs") + formatter.write_str("struct livekit.DeleteRoomRequest") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut codecs__ = None; - let mut publish__ = None; + let mut room__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::Codecs => { - if codecs__.is_some() { - return Err(serde::de::Error::duplicate_field("codecs")); - } - codecs__ = Some(map_.next_value()?); - } - GeneratedField::Publish => { - if publish__.is_some() { - return Err(serde::de::Error::duplicate_field("publish")); + GeneratedField::Room => { + if room__.is_some() { + return Err(serde::de::Error::duplicate_field("room")); } - publish__ = Some(map_.next_value()?); + room__ = Some(map_.next_value()?); } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } } } - Ok(DisabledCodecs { - codecs: codecs__.unwrap_or_default(), - publish: publish__.unwrap_or_default(), + Ok(DeleteRoomRequest { + room: room__.unwrap_or_default(), }) } } - deserializer.deserialize_struct("livekit.DisabledCodecs", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.DeleteRoomRequest", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for DisconnectReason { +impl serde::Serialize for DeleteRoomResponse { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where S: serde::Serializer, { - let variant = match self { - Self::UnknownReason => "UNKNOWN_REASON", - Self::ClientInitiated => "CLIENT_INITIATED", - Self::DuplicateIdentity => "DUPLICATE_IDENTITY", - Self::ServerShutdown => "SERVER_SHUTDOWN", - Self::ParticipantRemoved => "PARTICIPANT_REMOVED", - Self::RoomDeleted => "ROOM_DELETED", - Self::StateMismatch => "STATE_MISMATCH", - Self::JoinFailure => "JOIN_FAILURE", - Self::Migration => "MIGRATION", - Self::SignalClose => "SIGNAL_CLOSE", - Self::RoomClosed => "ROOM_CLOSED", - }; - serializer.serialize_str(variant) + use serde::ser::SerializeStruct; + let len = 0; + let struct_ser = serializer.serialize_struct("livekit.DeleteRoomResponse", len)?; + struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for DisconnectReason { +impl<'de> serde::Deserialize<'de> for DeleteRoomResponse { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "UNKNOWN_REASON", - "CLIENT_INITIATED", - "DUPLICATE_IDENTITY", - "SERVER_SHUTDOWN", - "PARTICIPANT_REMOVED", - "ROOM_DELETED", - "STATE_MISMATCH", - "JOIN_FAILURE", - "MIGRATION", - "SIGNAL_CLOSE", - "ROOM_CLOSED", ]; - struct GeneratedVisitor; + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + Ok(GeneratedField::__SkipField__) + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = DisconnectReason; + type Value = DeleteRoomResponse; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(formatter, "expected one of: {:?}", &FIELDS) + formatter.write_str("struct livekit.DeleteRoomResponse") } - fn visit_i64(self, v: i64) -> std::result::Result - where - E: serde::de::Error, + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, { - i32::try_from(v) - .ok() - .and_then(|x| x.try_into().ok()) - .ok_or_else(|| { - serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self) - }) - } - - fn visit_u64(self, v: u64) -> std::result::Result + while map_.next_key::()?.is_some() { + let _ = map_.next_value::()?; + } + Ok(DeleteRoomResponse { + }) + } + } + deserializer.deserialize_struct("livekit.DeleteRoomResponse", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for DeleteSipDispatchRuleRequest { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.sip_dispatch_rule_id.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.DeleteSIPDispatchRuleRequest", len)?; + if !self.sip_dispatch_rule_id.is_empty() { + struct_ser.serialize_field("sipDispatchRuleId", &self.sip_dispatch_rule_id)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for DeleteSipDispatchRuleRequest { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "sip_dispatch_rule_id", + "sipDispatchRuleId", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + SipDispatchRuleId, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "sipDispatchRuleId" | "sip_dispatch_rule_id" => Ok(GeneratedField::SipDispatchRuleId), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = DeleteSipDispatchRuleRequest; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.DeleteSIPDispatchRuleRequest") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut sip_dispatch_rule_id__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::SipDispatchRuleId => { + if sip_dispatch_rule_id__.is_some() { + return Err(serde::de::Error::duplicate_field("sipDispatchRuleId")); + } + sip_dispatch_rule_id__ = Some(map_.next_value()?); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(DeleteSipDispatchRuleRequest { + sip_dispatch_rule_id: sip_dispatch_rule_id__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.DeleteSIPDispatchRuleRequest", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for DeleteSipTrunkRequest { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.sip_trunk_id.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.DeleteSIPTrunkRequest", len)?; + if !self.sip_trunk_id.is_empty() { + struct_ser.serialize_field("sipTrunkId", &self.sip_trunk_id)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for DeleteSipTrunkRequest { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "sip_trunk_id", + "sipTrunkId", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + SipTrunkId, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "sipTrunkId" | "sip_trunk_id" => Ok(GeneratedField::SipTrunkId), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = DeleteSipTrunkRequest; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.DeleteSIPTrunkRequest") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut sip_trunk_id__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::SipTrunkId => { + if sip_trunk_id__.is_some() { + return Err(serde::de::Error::duplicate_field("sipTrunkId")); + } + sip_trunk_id__ = Some(map_.next_value()?); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(DeleteSipTrunkRequest { + sip_trunk_id: sip_trunk_id__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.DeleteSIPTrunkRequest", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for DirectFileOutput { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.filepath.is_empty() { + len += 1; + } + if self.disable_manifest { + len += 1; + } + if self.output.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.DirectFileOutput", len)?; + if !self.filepath.is_empty() { + struct_ser.serialize_field("filepath", &self.filepath)?; + } + if self.disable_manifest { + struct_ser.serialize_field("disableManifest", &self.disable_manifest)?; + } + if let Some(v) = self.output.as_ref() { + match v { + direct_file_output::Output::S3(v) => { + struct_ser.serialize_field("s3", v)?; + } + direct_file_output::Output::Gcp(v) => { + struct_ser.serialize_field("gcp", v)?; + } + direct_file_output::Output::Azure(v) => { + struct_ser.serialize_field("azure", v)?; + } + direct_file_output::Output::AliOss(v) => { + struct_ser.serialize_field("aliOSS", v)?; + } + } + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for DirectFileOutput { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "filepath", + "disable_manifest", + "disableManifest", + "s3", + "gcp", + "azure", + "aliOSS", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Filepath, + DisableManifest, + S3, + Gcp, + Azure, + AliOss, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "filepath" => Ok(GeneratedField::Filepath), + "disableManifest" | "disable_manifest" => Ok(GeneratedField::DisableManifest), + "s3" => Ok(GeneratedField::S3), + "gcp" => Ok(GeneratedField::Gcp), + "azure" => Ok(GeneratedField::Azure), + "aliOSS" => Ok(GeneratedField::AliOss), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = DirectFileOutput; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.DirectFileOutput") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut filepath__ = None; + let mut disable_manifest__ = None; + let mut output__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Filepath => { + if filepath__.is_some() { + return Err(serde::de::Error::duplicate_field("filepath")); + } + filepath__ = Some(map_.next_value()?); + } + GeneratedField::DisableManifest => { + if disable_manifest__.is_some() { + return Err(serde::de::Error::duplicate_field("disableManifest")); + } + disable_manifest__ = Some(map_.next_value()?); + } + GeneratedField::S3 => { + if output__.is_some() { + return Err(serde::de::Error::duplicate_field("s3")); + } + output__ = map_.next_value::<::std::option::Option<_>>()?.map(direct_file_output::Output::S3) +; + } + GeneratedField::Gcp => { + if output__.is_some() { + return Err(serde::de::Error::duplicate_field("gcp")); + } + output__ = map_.next_value::<::std::option::Option<_>>()?.map(direct_file_output::Output::Gcp) +; + } + GeneratedField::Azure => { + if output__.is_some() { + return Err(serde::de::Error::duplicate_field("azure")); + } + output__ = map_.next_value::<::std::option::Option<_>>()?.map(direct_file_output::Output::Azure) +; + } + GeneratedField::AliOss => { + if output__.is_some() { + return Err(serde::de::Error::duplicate_field("aliOSS")); + } + output__ = map_.next_value::<::std::option::Option<_>>()?.map(direct_file_output::Output::AliOss) +; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(DirectFileOutput { + filepath: filepath__.unwrap_or_default(), + disable_manifest: disable_manifest__.unwrap_or_default(), + output: output__, + }) + } + } + deserializer.deserialize_struct("livekit.DirectFileOutput", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for DisabledCodecs { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.codecs.is_empty() { + len += 1; + } + if !self.publish.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.DisabledCodecs", len)?; + if !self.codecs.is_empty() { + struct_ser.serialize_field("codecs", &self.codecs)?; + } + if !self.publish.is_empty() { + struct_ser.serialize_field("publish", &self.publish)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for DisabledCodecs { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "codecs", + "publish", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Codecs, + Publish, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "codecs" => Ok(GeneratedField::Codecs), + "publish" => Ok(GeneratedField::Publish), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = DisabledCodecs; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.DisabledCodecs") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut codecs__ = None; + let mut publish__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Codecs => { + if codecs__.is_some() { + return Err(serde::de::Error::duplicate_field("codecs")); + } + codecs__ = Some(map_.next_value()?); + } + GeneratedField::Publish => { + if publish__.is_some() { + return Err(serde::de::Error::duplicate_field("publish")); + } + publish__ = Some(map_.next_value()?); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(DisabledCodecs { + codecs: codecs__.unwrap_or_default(), + publish: publish__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.DisabledCodecs", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for DisconnectReason { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + let variant = match self { + Self::UnknownReason => "UNKNOWN_REASON", + Self::ClientInitiated => "CLIENT_INITIATED", + Self::DuplicateIdentity => "DUPLICATE_IDENTITY", + Self::ServerShutdown => "SERVER_SHUTDOWN", + Self::ParticipantRemoved => "PARTICIPANT_REMOVED", + Self::RoomDeleted => "ROOM_DELETED", + Self::StateMismatch => "STATE_MISMATCH", + Self::JoinFailure => "JOIN_FAILURE", + Self::Migration => "MIGRATION", + Self::SignalClose => "SIGNAL_CLOSE", + Self::RoomClosed => "ROOM_CLOSED", + Self::UserUnavailable => "USER_UNAVAILABLE", + Self::UserRejected => "USER_REJECTED", + Self::SipTrunkFailure => "SIP_TRUNK_FAILURE", + }; + serializer.serialize_str(variant) + } +} +impl<'de> serde::Deserialize<'de> for DisconnectReason { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "UNKNOWN_REASON", + "CLIENT_INITIATED", + "DUPLICATE_IDENTITY", + "SERVER_SHUTDOWN", + "PARTICIPANT_REMOVED", + "ROOM_DELETED", + "STATE_MISMATCH", + "JOIN_FAILURE", + "MIGRATION", + "SIGNAL_CLOSE", + "ROOM_CLOSED", + "USER_UNAVAILABLE", + "USER_REJECTED", + "SIP_TRUNK_FAILURE", + ]; + + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = DisconnectReason; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + fn visit_i64(self, v: i64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self) + }) + } + + fn visit_u64(self, v: u64) -> std::result::Result where E: serde::de::Error, { @@ -6200,6 +7207,9 @@ impl<'de> serde::Deserialize<'de> for DisconnectReason { "MIGRATION" => Ok(DisconnectReason::Migration), "SIGNAL_CLOSE" => Ok(DisconnectReason::SignalClose), "ROOM_CLOSED" => Ok(DisconnectReason::RoomClosed), + "USER_UNAVAILABLE" => Ok(DisconnectReason::UserUnavailable), + "USER_REJECTED" => Ok(DisconnectReason::UserRejected), + "SIP_TRUNK_FAILURE" => Ok(DisconnectReason::SipTrunkFailure), _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), } } @@ -6257,6 +7267,12 @@ impl serde::Serialize for EgressInfo { if !self.image_results.is_empty() { len += 1; } + if !self.manifest_location.is_empty() { + len += 1; + } + if self.backup_storage_used { + len += 1; + } if self.request.is_some() { len += 1; } @@ -6314,6 +7330,12 @@ impl serde::Serialize for EgressInfo { if !self.image_results.is_empty() { struct_ser.serialize_field("imageResults", &self.image_results)?; } + if !self.manifest_location.is_empty() { + struct_ser.serialize_field("manifestLocation", &self.manifest_location)?; + } + if self.backup_storage_used { + struct_ser.serialize_field("backupStorageUsed", &self.backup_storage_used)?; + } if let Some(v) = self.request.as_ref() { match v { egress_info::Request::RoomComposite(v) => { @@ -6381,6 +7403,10 @@ impl<'de> serde::Deserialize<'de> for EgressInfo { "segmentResults", "image_results", "imageResults", + "manifest_location", + "manifestLocation", + "backup_storage_used", + "backupStorageUsed", "room_composite", "roomComposite", "web", @@ -6409,6 +7435,8 @@ impl<'de> serde::Deserialize<'de> for EgressInfo { FileResults, SegmentResults, ImageResults, + ManifestLocation, + BackupStorageUsed, RoomComposite, Web, Participant, @@ -6453,6 +7481,8 @@ impl<'de> serde::Deserialize<'de> for EgressInfo { "fileResults" | "file_results" => Ok(GeneratedField::FileResults), "segmentResults" | "segment_results" => Ok(GeneratedField::SegmentResults), "imageResults" | "image_results" => Ok(GeneratedField::ImageResults), + "manifestLocation" | "manifest_location" => Ok(GeneratedField::ManifestLocation), + "backupStorageUsed" | "backup_storage_used" => Ok(GeneratedField::BackupStorageUsed), "roomComposite" | "room_composite" => Ok(GeneratedField::RoomComposite), "web" => Ok(GeneratedField::Web), "participant" => Ok(GeneratedField::Participant), @@ -6494,6 +7524,8 @@ impl<'de> serde::Deserialize<'de> for EgressInfo { let mut file_results__ = None; let mut segment_results__ = None; let mut image_results__ = None; + let mut manifest_location__ = None; + let mut backup_storage_used__ = None; let mut request__ = None; let mut result__ = None; while let Some(k) = map_.next_key()? { @@ -6590,6 +7622,18 @@ impl<'de> serde::Deserialize<'de> for EgressInfo { } image_results__ = Some(map_.next_value()?); } + GeneratedField::ManifestLocation => { + if manifest_location__.is_some() { + return Err(serde::de::Error::duplicate_field("manifestLocation")); + } + manifest_location__ = Some(map_.next_value()?); + } + GeneratedField::BackupStorageUsed => { + if backup_storage_used__.is_some() { + return Err(serde::de::Error::duplicate_field("backupStorageUsed")); + } + backup_storage_used__ = Some(map_.next_value()?); + } GeneratedField::RoomComposite => { if request__.is_some() { return Err(serde::de::Error::duplicate_field("roomComposite")); @@ -6666,6 +7710,8 @@ impl<'de> serde::Deserialize<'de> for EgressInfo { file_results: file_results__.unwrap_or_default(), segment_results: segment_results__.unwrap_or_default(), image_results: image_results__.unwrap_or_default(), + manifest_location: manifest_location__.unwrap_or_default(), + backup_storage_used: backup_storage_used__.unwrap_or_default(), request: request__, result: result__, }) @@ -14194,6 +15240,9 @@ impl serde::Serialize for MetricLabel { Self::ClientVideoPublisherQualityLimitationDurationBandwidth => "CLIENT_VIDEO_PUBLISHER_QUALITY_LIMITATION_DURATION_BANDWIDTH", Self::ClientVideoPublisherQualityLimitationDurationCpu => "CLIENT_VIDEO_PUBLISHER_QUALITY_LIMITATION_DURATION_CPU", Self::ClientVideoPublisherQualityLimitationDurationOther => "CLIENT_VIDEO_PUBLISHER_QUALITY_LIMITATION_DURATION_OTHER", + Self::PublisherRtt => "PUBLISHER_RTT", + Self::ServerMeshRtt => "SERVER_MESH_RTT", + Self::SubscriberRtt => "SUBSCRIBER_RTT", Self::PredefinedMaxValue => "METRIC_LABEL_PREDEFINED_MAX_VALUE", }; serializer.serialize_str(variant) @@ -14223,6 +15272,9 @@ impl<'de> serde::Deserialize<'de> for MetricLabel { "CLIENT_VIDEO_PUBLISHER_QUALITY_LIMITATION_DURATION_BANDWIDTH", "CLIENT_VIDEO_PUBLISHER_QUALITY_LIMITATION_DURATION_CPU", "CLIENT_VIDEO_PUBLISHER_QUALITY_LIMITATION_DURATION_OTHER", + "PUBLISHER_RTT", + "SERVER_MESH_RTT", + "SUBSCRIBER_RTT", "METRIC_LABEL_PREDEFINED_MAX_VALUE", ]; @@ -14281,6 +15333,9 @@ impl<'de> serde::Deserialize<'de> for MetricLabel { "CLIENT_VIDEO_PUBLISHER_QUALITY_LIMITATION_DURATION_BANDWIDTH" => Ok(MetricLabel::ClientVideoPublisherQualityLimitationDurationBandwidth), "CLIENT_VIDEO_PUBLISHER_QUALITY_LIMITATION_DURATION_CPU" => Ok(MetricLabel::ClientVideoPublisherQualityLimitationDurationCpu), "CLIENT_VIDEO_PUBLISHER_QUALITY_LIMITATION_DURATION_OTHER" => Ok(MetricLabel::ClientVideoPublisherQualityLimitationDurationOther), + "PUBLISHER_RTT" => Ok(MetricLabel::PublisherRtt), + "SERVER_MESH_RTT" => Ok(MetricLabel::ServerMeshRtt), + "SUBSCRIBER_RTT" => Ok(MetricLabel::SubscriberRtt), "METRIC_LABEL_PREDEFINED_MAX_VALUE" => Ok(MetricLabel::PredefinedMaxValue), _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), } @@ -20502,9 +21557,6 @@ impl serde::Serialize for RoomConfiguration { if self.egress.is_some() { len += 1; } - if self.agent.is_some() { - len += 1; - } if self.min_playout_delay != 0 { len += 1; } @@ -20514,6 +21566,9 @@ impl serde::Serialize for RoomConfiguration { if self.sync_streams { len += 1; } + if !self.agents.is_empty() { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.RoomConfiguration", len)?; if !self.name.is_empty() { struct_ser.serialize_field("name", &self.name)?; @@ -20530,9 +21585,6 @@ impl serde::Serialize for RoomConfiguration { if let Some(v) = self.egress.as_ref() { struct_ser.serialize_field("egress", v)?; } - if let Some(v) = self.agent.as_ref() { - struct_ser.serialize_field("agent", v)?; - } if self.min_playout_delay != 0 { struct_ser.serialize_field("minPlayoutDelay", &self.min_playout_delay)?; } @@ -20542,6 +21594,9 @@ impl serde::Serialize for RoomConfiguration { if self.sync_streams { struct_ser.serialize_field("syncStreams", &self.sync_streams)?; } + if !self.agents.is_empty() { + struct_ser.serialize_field("agents", &self.agents)?; + } struct_ser.end() } } @@ -20560,13 +21615,13 @@ impl<'de> serde::Deserialize<'de> for RoomConfiguration { "max_participants", "maxParticipants", "egress", - "agent", "min_playout_delay", "minPlayoutDelay", "max_playout_delay", "maxPlayoutDelay", "sync_streams", "syncStreams", + "agents", ]; #[allow(clippy::enum_variant_names)] @@ -20576,10 +21631,10 @@ impl<'de> serde::Deserialize<'de> for RoomConfiguration { DepartureTimeout, MaxParticipants, Egress, - Agent, MinPlayoutDelay, MaxPlayoutDelay, SyncStreams, + Agents, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -20607,10 +21662,10 @@ impl<'de> serde::Deserialize<'de> for RoomConfiguration { "departureTimeout" | "departure_timeout" => Ok(GeneratedField::DepartureTimeout), "maxParticipants" | "max_participants" => Ok(GeneratedField::MaxParticipants), "egress" => Ok(GeneratedField::Egress), - "agent" => Ok(GeneratedField::Agent), "minPlayoutDelay" | "min_playout_delay" => Ok(GeneratedField::MinPlayoutDelay), "maxPlayoutDelay" | "max_playout_delay" => Ok(GeneratedField::MaxPlayoutDelay), "syncStreams" | "sync_streams" => Ok(GeneratedField::SyncStreams), + "agents" => Ok(GeneratedField::Agents), _ => Ok(GeneratedField::__SkipField__), } } @@ -20635,10 +21690,10 @@ impl<'de> serde::Deserialize<'de> for RoomConfiguration { let mut departure_timeout__ = None; let mut max_participants__ = None; let mut egress__ = None; - let mut agent__ = None; let mut min_playout_delay__ = None; let mut max_playout_delay__ = None; let mut sync_streams__ = None; + let mut agents__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::Name => { @@ -20677,12 +21732,6 @@ impl<'de> serde::Deserialize<'de> for RoomConfiguration { } egress__ = map_.next_value()?; } - GeneratedField::Agent => { - if agent__.is_some() { - return Err(serde::de::Error::duplicate_field("agent")); - } - agent__ = map_.next_value()?; - } GeneratedField::MinPlayoutDelay => { if min_playout_delay__.is_some() { return Err(serde::de::Error::duplicate_field("minPlayoutDelay")); @@ -20705,6 +21754,12 @@ impl<'de> serde::Deserialize<'de> for RoomConfiguration { } sync_streams__ = Some(map_.next_value()?); } + GeneratedField::Agents => { + if agents__.is_some() { + return Err(serde::de::Error::duplicate_field("agents")); + } + agents__ = Some(map_.next_value()?); + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -20716,10 +21771,10 @@ impl<'de> serde::Deserialize<'de> for RoomConfiguration { departure_timeout: departure_timeout__.unwrap_or_default(), max_participants: max_participants__.unwrap_or_default(), egress: egress__, - agent: agent__, min_playout_delay: min_playout_delay__.unwrap_or_default(), max_playout_delay: max_playout_delay__.unwrap_or_default(), sync_streams: sync_streams__.unwrap_or_default(), + agents: agents__.unwrap_or_default(), }) } } @@ -21468,44 +22523,240 @@ impl serde::Serialize for RpcResponse { if !self.request_id.is_empty() { len += 1; } - if self.value.is_some() { + if self.value.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.RpcResponse", len)?; + if !self.request_id.is_empty() { + struct_ser.serialize_field("requestId", &self.request_id)?; + } + if let Some(v) = self.value.as_ref() { + match v { + rpc_response::Value::Payload(v) => { + struct_ser.serialize_field("payload", v)?; + } + rpc_response::Value::Error(v) => { + struct_ser.serialize_field("error", v)?; + } + } + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for RpcResponse { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "request_id", + "requestId", + "payload", + "error", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + RequestId, + Payload, + Error, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "requestId" | "request_id" => Ok(GeneratedField::RequestId), + "payload" => Ok(GeneratedField::Payload), + "error" => Ok(GeneratedField::Error), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = RpcResponse; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.RpcResponse") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut request_id__ = None; + let mut value__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::RequestId => { + if request_id__.is_some() { + return Err(serde::de::Error::duplicate_field("requestId")); + } + request_id__ = Some(map_.next_value()?); + } + GeneratedField::Payload => { + if value__.is_some() { + return Err(serde::de::Error::duplicate_field("payload")); + } + value__ = map_.next_value::<::std::option::Option<_>>()?.map(rpc_response::Value::Payload); + } + GeneratedField::Error => { + if value__.is_some() { + return Err(serde::de::Error::duplicate_field("error")); + } + value__ = map_.next_value::<::std::option::Option<_>>()?.map(rpc_response::Value::Error) +; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(RpcResponse { + request_id: request_id__.unwrap_or_default(), + value: value__, + }) + } + } + deserializer.deserialize_struct("livekit.RpcResponse", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for S3Upload { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.access_key.is_empty() { + len += 1; + } + if !self.secret.is_empty() { + len += 1; + } + if !self.session_token.is_empty() { + len += 1; + } + if !self.region.is_empty() { + len += 1; + } + if !self.endpoint.is_empty() { + len += 1; + } + if !self.bucket.is_empty() { + len += 1; + } + if self.force_path_style { + len += 1; + } + if !self.metadata.is_empty() { + len += 1; + } + if !self.tagging.is_empty() { + len += 1; + } + if !self.content_disposition.is_empty() { len += 1; } - let mut struct_ser = serializer.serialize_struct("livekit.RpcResponse", len)?; - if !self.request_id.is_empty() { - struct_ser.serialize_field("requestId", &self.request_id)?; + if self.proxy.is_some() { + len += 1; } - if let Some(v) = self.value.as_ref() { - match v { - rpc_response::Value::Payload(v) => { - struct_ser.serialize_field("payload", v)?; - } - rpc_response::Value::Error(v) => { - struct_ser.serialize_field("error", v)?; - } - } + let mut struct_ser = serializer.serialize_struct("livekit.S3Upload", len)?; + if !self.access_key.is_empty() { + struct_ser.serialize_field("accessKey", &self.access_key)?; + } + if !self.secret.is_empty() { + struct_ser.serialize_field("secret", &self.secret)?; + } + if !self.session_token.is_empty() { + struct_ser.serialize_field("sessionToken", &self.session_token)?; + } + if !self.region.is_empty() { + struct_ser.serialize_field("region", &self.region)?; + } + if !self.endpoint.is_empty() { + struct_ser.serialize_field("endpoint", &self.endpoint)?; + } + if !self.bucket.is_empty() { + struct_ser.serialize_field("bucket", &self.bucket)?; + } + if self.force_path_style { + struct_ser.serialize_field("forcePathStyle", &self.force_path_style)?; + } + if !self.metadata.is_empty() { + struct_ser.serialize_field("metadata", &self.metadata)?; + } + if !self.tagging.is_empty() { + struct_ser.serialize_field("tagging", &self.tagging)?; + } + if !self.content_disposition.is_empty() { + struct_ser.serialize_field("contentDisposition", &self.content_disposition)?; + } + if let Some(v) = self.proxy.as_ref() { + struct_ser.serialize_field("proxy", v)?; } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for RpcResponse { +impl<'de> serde::Deserialize<'de> for S3Upload { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "request_id", - "requestId", - "payload", - "error", + "access_key", + "accessKey", + "secret", + "session_token", + "sessionToken", + "region", + "endpoint", + "bucket", + "force_path_style", + "forcePathStyle", + "metadata", + "tagging", + "content_disposition", + "contentDisposition", + "proxy", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { - RequestId, - Payload, - Error, + AccessKey, + Secret, + SessionToken, + Region, + Endpoint, + Bucket, + ForcePathStyle, + Metadata, + Tagging, + ContentDisposition, + Proxy, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -21528,9 +22779,17 @@ impl<'de> serde::Deserialize<'de> for RpcResponse { E: serde::de::Error, { match value { - "requestId" | "request_id" => Ok(GeneratedField::RequestId), - "payload" => Ok(GeneratedField::Payload), - "error" => Ok(GeneratedField::Error), + "accessKey" | "access_key" => Ok(GeneratedField::AccessKey), + "secret" => Ok(GeneratedField::Secret), + "sessionToken" | "session_token" => Ok(GeneratedField::SessionToken), + "region" => Ok(GeneratedField::Region), + "endpoint" => Ok(GeneratedField::Endpoint), + "bucket" => Ok(GeneratedField::Bucket), + "forcePathStyle" | "force_path_style" => Ok(GeneratedField::ForcePathStyle), + "metadata" => Ok(GeneratedField::Metadata), + "tagging" => Ok(GeneratedField::Tagging), + "contentDisposition" | "content_disposition" => Ok(GeneratedField::ContentDisposition), + "proxy" => Ok(GeneratedField::Proxy), _ => Ok(GeneratedField::__SkipField__), } } @@ -21540,54 +22799,121 @@ impl<'de> serde::Deserialize<'de> for RpcResponse { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = RpcResponse; + type Value = S3Upload; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.RpcResponse") + formatter.write_str("struct livekit.S3Upload") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut request_id__ = None; - let mut value__ = None; + let mut access_key__ = None; + let mut secret__ = None; + let mut session_token__ = None; + let mut region__ = None; + let mut endpoint__ = None; + let mut bucket__ = None; + let mut force_path_style__ = None; + let mut metadata__ = None; + let mut tagging__ = None; + let mut content_disposition__ = None; + let mut proxy__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::RequestId => { - if request_id__.is_some() { - return Err(serde::de::Error::duplicate_field("requestId")); + GeneratedField::AccessKey => { + if access_key__.is_some() { + return Err(serde::de::Error::duplicate_field("accessKey")); } - request_id__ = Some(map_.next_value()?); + access_key__ = Some(map_.next_value()?); } - GeneratedField::Payload => { - if value__.is_some() { - return Err(serde::de::Error::duplicate_field("payload")); + GeneratedField::Secret => { + if secret__.is_some() { + return Err(serde::de::Error::duplicate_field("secret")); } - value__ = map_.next_value::<::std::option::Option<_>>()?.map(rpc_response::Value::Payload); + secret__ = Some(map_.next_value()?); } - GeneratedField::Error => { - if value__.is_some() { - return Err(serde::de::Error::duplicate_field("error")); + GeneratedField::SessionToken => { + if session_token__.is_some() { + return Err(serde::de::Error::duplicate_field("sessionToken")); } - value__ = map_.next_value::<::std::option::Option<_>>()?.map(rpc_response::Value::Error) -; + session_token__ = Some(map_.next_value()?); + } + GeneratedField::Region => { + if region__.is_some() { + return Err(serde::de::Error::duplicate_field("region")); + } + region__ = Some(map_.next_value()?); + } + GeneratedField::Endpoint => { + if endpoint__.is_some() { + return Err(serde::de::Error::duplicate_field("endpoint")); + } + endpoint__ = Some(map_.next_value()?); + } + GeneratedField::Bucket => { + if bucket__.is_some() { + return Err(serde::de::Error::duplicate_field("bucket")); + } + bucket__ = Some(map_.next_value()?); + } + GeneratedField::ForcePathStyle => { + if force_path_style__.is_some() { + return Err(serde::de::Error::duplicate_field("forcePathStyle")); + } + force_path_style__ = Some(map_.next_value()?); + } + GeneratedField::Metadata => { + if metadata__.is_some() { + return Err(serde::de::Error::duplicate_field("metadata")); + } + metadata__ = Some( + map_.next_value::>()? + ); + } + GeneratedField::Tagging => { + if tagging__.is_some() { + return Err(serde::de::Error::duplicate_field("tagging")); + } + tagging__ = Some(map_.next_value()?); + } + GeneratedField::ContentDisposition => { + if content_disposition__.is_some() { + return Err(serde::de::Error::duplicate_field("contentDisposition")); + } + content_disposition__ = Some(map_.next_value()?); + } + GeneratedField::Proxy => { + if proxy__.is_some() { + return Err(serde::de::Error::duplicate_field("proxy")); + } + proxy__ = map_.next_value()?; } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } } } - Ok(RpcResponse { - request_id: request_id__.unwrap_or_default(), - value: value__, + Ok(S3Upload { + access_key: access_key__.unwrap_or_default(), + secret: secret__.unwrap_or_default(), + session_token: session_token__.unwrap_or_default(), + region: region__.unwrap_or_default(), + endpoint: endpoint__.unwrap_or_default(), + bucket: bucket__.unwrap_or_default(), + force_path_style: force_path_style__.unwrap_or_default(), + metadata: metadata__.unwrap_or_default(), + tagging: tagging__.unwrap_or_default(), + content_disposition: content_disposition__.unwrap_or_default(), + proxy: proxy__, }) } } - deserializer.deserialize_struct("livekit.RpcResponse", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.S3Upload", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for S3Upload { +impl serde::Serialize for SipCallInfo { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -21595,113 +22921,160 @@ impl serde::Serialize for S3Upload { { use serde::ser::SerializeStruct; let mut len = 0; - if !self.access_key.is_empty() { + if !self.call_id.is_empty() { len += 1; } - if !self.secret.is_empty() { + if !self.trunk_id.is_empty() { len += 1; } - if !self.session_token.is_empty() { + if !self.room_name.is_empty() { len += 1; } - if !self.region.is_empty() { + if !self.room_id.is_empty() { len += 1; } - if !self.endpoint.is_empty() { + if !self.participant_identity.is_empty() { len += 1; } - if !self.bucket.is_empty() { + if self.from_uri.is_some() { len += 1; } - if self.force_path_style { + if self.to_uri.is_some() { len += 1; } - if !self.metadata.is_empty() { + if !self.enabled_features.is_empty() { len += 1; } - if !self.tagging.is_empty() { + if self.call_status != 0 { len += 1; } - if !self.content_disposition.is_empty() { + if self.created_at != 0 { len += 1; } - if self.proxy.is_some() { + if self.started_at != 0 { len += 1; } - let mut struct_ser = serializer.serialize_struct("livekit.S3Upload", len)?; - if !self.access_key.is_empty() { - struct_ser.serialize_field("accessKey", &self.access_key)?; + if self.ended_at != 0 { + len += 1; } - if !self.secret.is_empty() { - struct_ser.serialize_field("secret", &self.secret)?; + if self.disconnect_reason != 0 { + len += 1; } - if !self.session_token.is_empty() { - struct_ser.serialize_field("sessionToken", &self.session_token)?; + if !self.error.is_empty() { + len += 1; } - if !self.region.is_empty() { - struct_ser.serialize_field("region", &self.region)?; + let mut struct_ser = serializer.serialize_struct("livekit.SIPCallInfo", len)?; + if !self.call_id.is_empty() { + struct_ser.serialize_field("callId", &self.call_id)?; } - if !self.endpoint.is_empty() { - struct_ser.serialize_field("endpoint", &self.endpoint)?; + if !self.trunk_id.is_empty() { + struct_ser.serialize_field("trunkId", &self.trunk_id)?; + } + if !self.room_name.is_empty() { + struct_ser.serialize_field("roomName", &self.room_name)?; + } + if !self.room_id.is_empty() { + struct_ser.serialize_field("roomId", &self.room_id)?; + } + if !self.participant_identity.is_empty() { + struct_ser.serialize_field("participantIdentity", &self.participant_identity)?; + } + if let Some(v) = self.from_uri.as_ref() { + struct_ser.serialize_field("fromUri", v)?; + } + if let Some(v) = self.to_uri.as_ref() { + struct_ser.serialize_field("toUri", v)?; + } + if !self.enabled_features.is_empty() { + let v = self.enabled_features.iter().cloned().map(|v| { + SipFeature::try_from(v) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", v))) + }).collect::, _>>()?; + struct_ser.serialize_field("enabledFeatures", &v)?; } - if !self.bucket.is_empty() { - struct_ser.serialize_field("bucket", &self.bucket)?; + if self.call_status != 0 { + let v = SipCallStatus::try_from(self.call_status) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.call_status)))?; + struct_ser.serialize_field("callStatus", &v)?; } - if self.force_path_style { - struct_ser.serialize_field("forcePathStyle", &self.force_path_style)?; + if self.created_at != 0 { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("createdAt", ToString::to_string(&self.created_at).as_str())?; } - if !self.metadata.is_empty() { - struct_ser.serialize_field("metadata", &self.metadata)?; + if self.started_at != 0 { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("startedAt", ToString::to_string(&self.started_at).as_str())?; } - if !self.tagging.is_empty() { - struct_ser.serialize_field("tagging", &self.tagging)?; + if self.ended_at != 0 { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("endedAt", ToString::to_string(&self.ended_at).as_str())?; } - if !self.content_disposition.is_empty() { - struct_ser.serialize_field("contentDisposition", &self.content_disposition)?; + if self.disconnect_reason != 0 { + let v = DisconnectReason::try_from(self.disconnect_reason) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.disconnect_reason)))?; + struct_ser.serialize_field("disconnectReason", &v)?; } - if let Some(v) = self.proxy.as_ref() { - struct_ser.serialize_field("proxy", v)?; + if !self.error.is_empty() { + struct_ser.serialize_field("error", &self.error)?; } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for S3Upload { +impl<'de> serde::Deserialize<'de> for SipCallInfo { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "access_key", - "accessKey", - "secret", - "session_token", - "sessionToken", - "region", - "endpoint", - "bucket", - "force_path_style", - "forcePathStyle", - "metadata", - "tagging", - "content_disposition", - "contentDisposition", - "proxy", + "call_id", + "callId", + "trunk_id", + "trunkId", + "room_name", + "roomName", + "room_id", + "roomId", + "participant_identity", + "participantIdentity", + "from_uri", + "fromUri", + "to_uri", + "toUri", + "enabled_features", + "enabledFeatures", + "call_status", + "callStatus", + "created_at", + "createdAt", + "started_at", + "startedAt", + "ended_at", + "endedAt", + "disconnect_reason", + "disconnectReason", + "error", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { - AccessKey, - Secret, - SessionToken, - Region, - Endpoint, - Bucket, - ForcePathStyle, - Metadata, - Tagging, - ContentDisposition, - Proxy, + CallId, + TrunkId, + RoomName, + RoomId, + ParticipantIdentity, + FromUri, + ToUri, + EnabledFeatures, + CallStatus, + CreatedAt, + StartedAt, + EndedAt, + DisconnectReason, + Error, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -21724,17 +23097,20 @@ impl<'de> serde::Deserialize<'de> for S3Upload { E: serde::de::Error, { match value { - "accessKey" | "access_key" => Ok(GeneratedField::AccessKey), - "secret" => Ok(GeneratedField::Secret), - "sessionToken" | "session_token" => Ok(GeneratedField::SessionToken), - "region" => Ok(GeneratedField::Region), - "endpoint" => Ok(GeneratedField::Endpoint), - "bucket" => Ok(GeneratedField::Bucket), - "forcePathStyle" | "force_path_style" => Ok(GeneratedField::ForcePathStyle), - "metadata" => Ok(GeneratedField::Metadata), - "tagging" => Ok(GeneratedField::Tagging), - "contentDisposition" | "content_disposition" => Ok(GeneratedField::ContentDisposition), - "proxy" => Ok(GeneratedField::Proxy), + "callId" | "call_id" => Ok(GeneratedField::CallId), + "trunkId" | "trunk_id" => Ok(GeneratedField::TrunkId), + "roomName" | "room_name" => Ok(GeneratedField::RoomName), + "roomId" | "room_id" => Ok(GeneratedField::RoomId), + "participantIdentity" | "participant_identity" => Ok(GeneratedField::ParticipantIdentity), + "fromUri" | "from_uri" => Ok(GeneratedField::FromUri), + "toUri" | "to_uri" => Ok(GeneratedField::ToUri), + "enabledFeatures" | "enabled_features" => Ok(GeneratedField::EnabledFeatures), + "callStatus" | "call_status" => Ok(GeneratedField::CallStatus), + "createdAt" | "created_at" => Ok(GeneratedField::CreatedAt), + "startedAt" | "started_at" => Ok(GeneratedField::StartedAt), + "endedAt" | "ended_at" => Ok(GeneratedField::EndedAt), + "disconnectReason" | "disconnect_reason" => Ok(GeneratedField::DisconnectReason), + "error" => Ok(GeneratedField::Error), _ => Ok(GeneratedField::__SkipField__), } } @@ -21744,118 +23120,226 @@ impl<'de> serde::Deserialize<'de> for S3Upload { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = S3Upload; + type Value = SipCallInfo; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.S3Upload") + formatter.write_str("struct livekit.SIPCallInfo") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut access_key__ = None; - let mut secret__ = None; - let mut session_token__ = None; - let mut region__ = None; - let mut endpoint__ = None; - let mut bucket__ = None; - let mut force_path_style__ = None; - let mut metadata__ = None; - let mut tagging__ = None; - let mut content_disposition__ = None; - let mut proxy__ = None; + let mut call_id__ = None; + let mut trunk_id__ = None; + let mut room_name__ = None; + let mut room_id__ = None; + let mut participant_identity__ = None; + let mut from_uri__ = None; + let mut to_uri__ = None; + let mut enabled_features__ = None; + let mut call_status__ = None; + let mut created_at__ = None; + let mut started_at__ = None; + let mut ended_at__ = None; + let mut disconnect_reason__ = None; + let mut error__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::AccessKey => { - if access_key__.is_some() { - return Err(serde::de::Error::duplicate_field("accessKey")); + GeneratedField::CallId => { + if call_id__.is_some() { + return Err(serde::de::Error::duplicate_field("callId")); } - access_key__ = Some(map_.next_value()?); + call_id__ = Some(map_.next_value()?); } - GeneratedField::Secret => { - if secret__.is_some() { - return Err(serde::de::Error::duplicate_field("secret")); + GeneratedField::TrunkId => { + if trunk_id__.is_some() { + return Err(serde::de::Error::duplicate_field("trunkId")); } - secret__ = Some(map_.next_value()?); + trunk_id__ = Some(map_.next_value()?); } - GeneratedField::SessionToken => { - if session_token__.is_some() { - return Err(serde::de::Error::duplicate_field("sessionToken")); + GeneratedField::RoomName => { + if room_name__.is_some() { + return Err(serde::de::Error::duplicate_field("roomName")); } - session_token__ = Some(map_.next_value()?); + room_name__ = Some(map_.next_value()?); } - GeneratedField::Region => { - if region__.is_some() { - return Err(serde::de::Error::duplicate_field("region")); + GeneratedField::RoomId => { + if room_id__.is_some() { + return Err(serde::de::Error::duplicate_field("roomId")); } - region__ = Some(map_.next_value()?); + room_id__ = Some(map_.next_value()?); } - GeneratedField::Endpoint => { - if endpoint__.is_some() { - return Err(serde::de::Error::duplicate_field("endpoint")); + GeneratedField::ParticipantIdentity => { + if participant_identity__.is_some() { + return Err(serde::de::Error::duplicate_field("participantIdentity")); } - endpoint__ = Some(map_.next_value()?); + participant_identity__ = Some(map_.next_value()?); } - GeneratedField::Bucket => { - if bucket__.is_some() { - return Err(serde::de::Error::duplicate_field("bucket")); + GeneratedField::FromUri => { + if from_uri__.is_some() { + return Err(serde::de::Error::duplicate_field("fromUri")); } - bucket__ = Some(map_.next_value()?); + from_uri__ = map_.next_value()?; } - GeneratedField::ForcePathStyle => { - if force_path_style__.is_some() { - return Err(serde::de::Error::duplicate_field("forcePathStyle")); + GeneratedField::ToUri => { + if to_uri__.is_some() { + return Err(serde::de::Error::duplicate_field("toUri")); } - force_path_style__ = Some(map_.next_value()?); + to_uri__ = map_.next_value()?; } - GeneratedField::Metadata => { - if metadata__.is_some() { - return Err(serde::de::Error::duplicate_field("metadata")); + GeneratedField::EnabledFeatures => { + if enabled_features__.is_some() { + return Err(serde::de::Error::duplicate_field("enabledFeatures")); } - metadata__ = Some( - map_.next_value::>()? - ); + enabled_features__ = Some(map_.next_value::>()?.into_iter().map(|x| x as i32).collect()); } - GeneratedField::Tagging => { - if tagging__.is_some() { - return Err(serde::de::Error::duplicate_field("tagging")); + GeneratedField::CallStatus => { + if call_status__.is_some() { + return Err(serde::de::Error::duplicate_field("callStatus")); } - tagging__ = Some(map_.next_value()?); + call_status__ = Some(map_.next_value::()? as i32); } - GeneratedField::ContentDisposition => { - if content_disposition__.is_some() { - return Err(serde::de::Error::duplicate_field("contentDisposition")); + GeneratedField::CreatedAt => { + if created_at__.is_some() { + return Err(serde::de::Error::duplicate_field("createdAt")); } - content_disposition__ = Some(map_.next_value()?); + created_at__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; } - GeneratedField::Proxy => { - if proxy__.is_some() { - return Err(serde::de::Error::duplicate_field("proxy")); + GeneratedField::StartedAt => { + if started_at__.is_some() { + return Err(serde::de::Error::duplicate_field("startedAt")); } - proxy__ = map_.next_value()?; + started_at__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::EndedAt => { + if ended_at__.is_some() { + return Err(serde::de::Error::duplicate_field("endedAt")); + } + ended_at__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::DisconnectReason => { + if disconnect_reason__.is_some() { + return Err(serde::de::Error::duplicate_field("disconnectReason")); + } + disconnect_reason__ = Some(map_.next_value::()? as i32); + } + GeneratedField::Error => { + if error__.is_some() { + return Err(serde::de::Error::duplicate_field("error")); + } + error__ = Some(map_.next_value()?); } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } } } - Ok(S3Upload { - access_key: access_key__.unwrap_or_default(), - secret: secret__.unwrap_or_default(), - session_token: session_token__.unwrap_or_default(), - region: region__.unwrap_or_default(), - endpoint: endpoint__.unwrap_or_default(), - bucket: bucket__.unwrap_or_default(), - force_path_style: force_path_style__.unwrap_or_default(), - metadata: metadata__.unwrap_or_default(), - tagging: tagging__.unwrap_or_default(), - content_disposition: content_disposition__.unwrap_or_default(), - proxy: proxy__, - }) + Ok(SipCallInfo { + call_id: call_id__.unwrap_or_default(), + trunk_id: trunk_id__.unwrap_or_default(), + room_name: room_name__.unwrap_or_default(), + room_id: room_id__.unwrap_or_default(), + participant_identity: participant_identity__.unwrap_or_default(), + from_uri: from_uri__, + to_uri: to_uri__, + enabled_features: enabled_features__.unwrap_or_default(), + call_status: call_status__.unwrap_or_default(), + created_at: created_at__.unwrap_or_default(), + started_at: started_at__.unwrap_or_default(), + ended_at: ended_at__.unwrap_or_default(), + disconnect_reason: disconnect_reason__.unwrap_or_default(), + error: error__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.SIPCallInfo", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for SipCallStatus { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + let variant = match self { + Self::ScsCallIncoming => "SCS_CALL_INCOMING", + Self::ScsParticipantJoined => "SCS_PARTICIPANT_JOINED", + Self::ScsActive => "SCS_ACTIVE", + Self::ScsDisconnected => "SCS_DISCONNECTED", + Self::ScsError => "SCS_ERROR", + }; + serializer.serialize_str(variant) + } +} +impl<'de> serde::Deserialize<'de> for SipCallStatus { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "SCS_CALL_INCOMING", + "SCS_PARTICIPANT_JOINED", + "SCS_ACTIVE", + "SCS_DISCONNECTED", + "SCS_ERROR", + ]; + + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = SipCallStatus; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + fn visit_i64(self, v: i64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self) + }) + } + + fn visit_u64(self, v: u64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(v), &self) + }) + } + + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "SCS_CALL_INCOMING" => Ok(SipCallStatus::ScsCallIncoming), + "SCS_PARTICIPANT_JOINED" => Ok(SipCallStatus::ScsParticipantJoined), + "SCS_ACTIVE" => Ok(SipCallStatus::ScsActive), + "SCS_DISCONNECTED" => Ok(SipCallStatus::ScsDisconnected), + "SCS_ERROR" => Ok(SipCallStatus::ScsError), + _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), + } } } - deserializer.deserialize_struct("livekit.S3Upload", FIELDS, GeneratedVisitor) + deserializer.deserialize_any(GeneratedVisitor) } } impl serde::Serialize for SipDispatchRule { @@ -22563,6 +24047,77 @@ impl<'de> serde::Deserialize<'de> for SipDispatchRuleInfo { deserializer.deserialize_struct("livekit.SIPDispatchRuleInfo", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for SipFeature { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + let variant = match self { + Self::None => "NONE", + Self::KrispEnabled => "KRISP_ENABLED", + }; + serializer.serialize_str(variant) + } +} +impl<'de> serde::Deserialize<'de> for SipFeature { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "NONE", + "KRISP_ENABLED", + ]; + + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = SipFeature; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + fn visit_i64(self, v: i64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self) + }) + } + + fn visit_u64(self, v: u64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(v), &self) + }) + } + + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "NONE" => Ok(SipFeature::None), + "KRISP_ENABLED" => Ok(SipFeature::KrispEnabled), + _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), + } + } + } + deserializer.deserialize_any(GeneratedVisitor) + } +} impl serde::Serialize for SipInboundTrunkInfo { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -22601,6 +24156,18 @@ impl serde::Serialize for SipInboundTrunkInfo { if !self.headers_to_attributes.is_empty() { len += 1; } + if !self.attributes_to_headers.is_empty() { + len += 1; + } + if self.ringing_timeout.is_some() { + len += 1; + } + if self.max_call_duration.is_some() { + len += 1; + } + if self.krisp_enabled { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.SIPInboundTrunkInfo", len)?; if !self.sip_trunk_id.is_empty() { struct_ser.serialize_field("sipTrunkId", &self.sip_trunk_id)?; @@ -22632,6 +24199,18 @@ impl serde::Serialize for SipInboundTrunkInfo { if !self.headers_to_attributes.is_empty() { struct_ser.serialize_field("headersToAttributes", &self.headers_to_attributes)?; } + if !self.attributes_to_headers.is_empty() { + struct_ser.serialize_field("attributesToHeaders", &self.attributes_to_headers)?; + } + if let Some(v) = self.ringing_timeout.as_ref() { + struct_ser.serialize_field("ringingTimeout", v)?; + } + if let Some(v) = self.max_call_duration.as_ref() { + struct_ser.serialize_field("maxCallDuration", v)?; + } + if self.krisp_enabled { + struct_ser.serialize_field("krispEnabled", &self.krisp_enabled)?; + } struct_ser.end() } } @@ -22658,6 +24237,14 @@ impl<'de> serde::Deserialize<'de> for SipInboundTrunkInfo { "headers", "headers_to_attributes", "headersToAttributes", + "attributes_to_headers", + "attributesToHeaders", + "ringing_timeout", + "ringingTimeout", + "max_call_duration", + "maxCallDuration", + "krisp_enabled", + "krispEnabled", ]; #[allow(clippy::enum_variant_names)] @@ -22672,6 +24259,10 @@ impl<'de> serde::Deserialize<'de> for SipInboundTrunkInfo { AuthPassword, Headers, HeadersToAttributes, + AttributesToHeaders, + RingingTimeout, + MaxCallDuration, + KrispEnabled, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -22704,6 +24295,10 @@ impl<'de> serde::Deserialize<'de> for SipInboundTrunkInfo { "authPassword" | "auth_password" => Ok(GeneratedField::AuthPassword), "headers" => Ok(GeneratedField::Headers), "headersToAttributes" | "headers_to_attributes" => Ok(GeneratedField::HeadersToAttributes), + "attributesToHeaders" | "attributes_to_headers" => Ok(GeneratedField::AttributesToHeaders), + "ringingTimeout" | "ringing_timeout" => Ok(GeneratedField::RingingTimeout), + "maxCallDuration" | "max_call_duration" => Ok(GeneratedField::MaxCallDuration), + "krispEnabled" | "krisp_enabled" => Ok(GeneratedField::KrispEnabled), _ => Ok(GeneratedField::__SkipField__), } } @@ -22733,6 +24328,10 @@ impl<'de> serde::Deserialize<'de> for SipInboundTrunkInfo { let mut auth_password__ = None; let mut headers__ = None; let mut headers_to_attributes__ = None; + let mut attributes_to_headers__ = None; + let mut ringing_timeout__ = None; + let mut max_call_duration__ = None; + let mut krisp_enabled__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::SipTrunkId => { @@ -22799,6 +24398,32 @@ impl<'de> serde::Deserialize<'de> for SipInboundTrunkInfo { map_.next_value::>()? ); } + GeneratedField::AttributesToHeaders => { + if attributes_to_headers__.is_some() { + return Err(serde::de::Error::duplicate_field("attributesToHeaders")); + } + attributes_to_headers__ = Some( + map_.next_value::>()? + ); + } + GeneratedField::RingingTimeout => { + if ringing_timeout__.is_some() { + return Err(serde::de::Error::duplicate_field("ringingTimeout")); + } + ringing_timeout__ = map_.next_value()?; + } + GeneratedField::MaxCallDuration => { + if max_call_duration__.is_some() { + return Err(serde::de::Error::duplicate_field("maxCallDuration")); + } + max_call_duration__ = map_.next_value()?; + } + GeneratedField::KrispEnabled => { + if krisp_enabled__.is_some() { + return Err(serde::de::Error::duplicate_field("krispEnabled")); + } + krisp_enabled__ = Some(map_.next_value()?); + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -22815,6 +24440,10 @@ impl<'de> serde::Deserialize<'de> for SipInboundTrunkInfo { auth_password: auth_password__.unwrap_or_default(), headers: headers__.unwrap_or_default(), headers_to_attributes: headers_to_attributes__.unwrap_or_default(), + attributes_to_headers: attributes_to_headers__.unwrap_or_default(), + ringing_timeout: ringing_timeout__, + max_call_duration: max_call_duration__, + krisp_enabled: krisp_enabled__.unwrap_or_default(), }) } } @@ -22859,6 +24488,9 @@ impl serde::Serialize for SipOutboundTrunkInfo { if !self.headers_to_attributes.is_empty() { len += 1; } + if !self.attributes_to_headers.is_empty() { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.SIPOutboundTrunkInfo", len)?; if !self.sip_trunk_id.is_empty() { struct_ser.serialize_field("sipTrunkId", &self.sip_trunk_id)?; @@ -22892,6 +24524,9 @@ impl serde::Serialize for SipOutboundTrunkInfo { if !self.headers_to_attributes.is_empty() { struct_ser.serialize_field("headersToAttributes", &self.headers_to_attributes)?; } + if !self.attributes_to_headers.is_empty() { + struct_ser.serialize_field("attributesToHeaders", &self.attributes_to_headers)?; + } struct_ser.end() } } @@ -22916,6 +24551,8 @@ impl<'de> serde::Deserialize<'de> for SipOutboundTrunkInfo { "headers", "headers_to_attributes", "headersToAttributes", + "attributes_to_headers", + "attributesToHeaders", ]; #[allow(clippy::enum_variant_names)] @@ -22930,6 +24567,7 @@ impl<'de> serde::Deserialize<'de> for SipOutboundTrunkInfo { AuthPassword, Headers, HeadersToAttributes, + AttributesToHeaders, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -22962,6 +24600,7 @@ impl<'de> serde::Deserialize<'de> for SipOutboundTrunkInfo { "authPassword" | "auth_password" => Ok(GeneratedField::AuthPassword), "headers" => Ok(GeneratedField::Headers), "headersToAttributes" | "headers_to_attributes" => Ok(GeneratedField::HeadersToAttributes), + "attributesToHeaders" | "attributes_to_headers" => Ok(GeneratedField::AttributesToHeaders), _ => Ok(GeneratedField::__SkipField__), } } @@ -22991,6 +24630,7 @@ impl<'de> serde::Deserialize<'de> for SipOutboundTrunkInfo { let mut auth_password__ = None; let mut headers__ = None; let mut headers_to_attributes__ = None; + let mut attributes_to_headers__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::SipTrunkId => { @@ -23057,6 +24697,14 @@ impl<'de> serde::Deserialize<'de> for SipOutboundTrunkInfo { map_.next_value::>()? ); } + GeneratedField::AttributesToHeaders => { + if attributes_to_headers__.is_some() { + return Err(serde::de::Error::duplicate_field("attributesToHeaders")); + } + attributes_to_headers__ = Some( + map_.next_value::>()? + ); + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -23073,6 +24721,7 @@ impl<'de> serde::Deserialize<'de> for SipOutboundTrunkInfo { auth_password: auth_password__.unwrap_or_default(), headers: headers__.unwrap_or_default(), headers_to_attributes: headers_to_attributes__.unwrap_or_default(), + attributes_to_headers: attributes_to_headers__.unwrap_or_default(), }) } } @@ -23710,6 +25359,173 @@ impl<'de> serde::Deserialize<'de> for sip_trunk_info::TrunkKind { deserializer.deserialize_any(GeneratedVisitor) } } +impl serde::Serialize for SipUri { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.user.is_empty() { + len += 1; + } + if !self.host.is_empty() { + len += 1; + } + if !self.ip.is_empty() { + len += 1; + } + if self.port != 0 { + len += 1; + } + if self.transport != 0 { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.SIPUri", len)?; + if !self.user.is_empty() { + struct_ser.serialize_field("user", &self.user)?; + } + if !self.host.is_empty() { + struct_ser.serialize_field("host", &self.host)?; + } + if !self.ip.is_empty() { + struct_ser.serialize_field("ip", &self.ip)?; + } + if self.port != 0 { + struct_ser.serialize_field("port", &self.port)?; + } + if self.transport != 0 { + let v = SipTransport::try_from(self.transport) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.transport)))?; + struct_ser.serialize_field("transport", &v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for SipUri { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "user", + "host", + "ip", + "port", + "transport", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + User, + Host, + Ip, + Port, + Transport, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "user" => Ok(GeneratedField::User), + "host" => Ok(GeneratedField::Host), + "ip" => Ok(GeneratedField::Ip), + "port" => Ok(GeneratedField::Port), + "transport" => Ok(GeneratedField::Transport), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = SipUri; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.SIPUri") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut user__ = None; + let mut host__ = None; + let mut ip__ = None; + let mut port__ = None; + let mut transport__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::User => { + if user__.is_some() { + return Err(serde::de::Error::duplicate_field("user")); + } + user__ = Some(map_.next_value()?); + } + GeneratedField::Host => { + if host__.is_some() { + return Err(serde::de::Error::duplicate_field("host")); + } + host__ = Some(map_.next_value()?); + } + GeneratedField::Ip => { + if ip__.is_some() { + return Err(serde::de::Error::duplicate_field("ip")); + } + ip__ = Some(map_.next_value()?); + } + GeneratedField::Port => { + if port__.is_some() { + return Err(serde::de::Error::duplicate_field("port")); + } + port__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::Transport => { + if transport__.is_some() { + return Err(serde::de::Error::duplicate_field("transport")); + } + transport__ = Some(map_.next_value::()? as i32); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(SipUri { + user: user__.unwrap_or_default(), + host: host__.unwrap_or_default(), + ip: ip__.unwrap_or_default(), + port: port__.unwrap_or_default(), + transport: transport__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.SIPUri", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for SegmentedFileOutput { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -30911,6 +32727,9 @@ impl serde::Serialize for TransferSipParticipantRequest { if !self.transfer_to.is_empty() { len += 1; } + if self.play_dialtone { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.TransferSIPParticipantRequest", len)?; if !self.participant_identity.is_empty() { struct_ser.serialize_field("participantIdentity", &self.participant_identity)?; @@ -30921,6 +32740,9 @@ impl serde::Serialize for TransferSipParticipantRequest { if !self.transfer_to.is_empty() { struct_ser.serialize_field("transferTo", &self.transfer_to)?; } + if self.play_dialtone { + struct_ser.serialize_field("playDialtone", &self.play_dialtone)?; + } struct_ser.end() } } @@ -30937,6 +32759,8 @@ impl<'de> serde::Deserialize<'de> for TransferSipParticipantRequest { "roomName", "transfer_to", "transferTo", + "play_dialtone", + "playDialtone", ]; #[allow(clippy::enum_variant_names)] @@ -30944,6 +32768,7 @@ impl<'de> serde::Deserialize<'de> for TransferSipParticipantRequest { ParticipantIdentity, RoomName, TransferTo, + PlayDialtone, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -30969,6 +32794,7 @@ impl<'de> serde::Deserialize<'de> for TransferSipParticipantRequest { "participantIdentity" | "participant_identity" => Ok(GeneratedField::ParticipantIdentity), "roomName" | "room_name" => Ok(GeneratedField::RoomName), "transferTo" | "transfer_to" => Ok(GeneratedField::TransferTo), + "playDialtone" | "play_dialtone" => Ok(GeneratedField::PlayDialtone), _ => Ok(GeneratedField::__SkipField__), } } @@ -30991,6 +32817,7 @@ impl<'de> serde::Deserialize<'de> for TransferSipParticipantRequest { let mut participant_identity__ = None; let mut room_name__ = None; let mut transfer_to__ = None; + let mut play_dialtone__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::ParticipantIdentity => { @@ -31011,6 +32838,12 @@ impl<'de> serde::Deserialize<'de> for TransferSipParticipantRequest { } transfer_to__ = Some(map_.next_value()?); } + GeneratedField::PlayDialtone => { + if play_dialtone__.is_some() { + return Err(serde::de::Error::duplicate_field("playDialtone")); + } + play_dialtone__ = Some(map_.next_value()?); + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -31020,6 +32853,7 @@ impl<'de> serde::Deserialize<'de> for TransferSipParticipantRequest { participant_identity: participant_identity__.unwrap_or_default(), room_name: room_name__.unwrap_or_default(), transfer_to: transfer_to__.unwrap_or_default(), + play_dialtone: play_dialtone__.unwrap_or_default(), }) } } diff --git a/livekit/src/prelude.rs b/livekit/src/prelude.rs index d86e328c3..1c2eab0ff 100644 --- a/livekit/src/prelude.rs +++ b/livekit/src/prelude.rs @@ -15,14 +15,14 @@ pub use crate::{ id::*, participant::{ - ConnectionQuality, LocalParticipant, Participant, PerformRpcData, RemoteParticipant, - RpcError, RpcErrorCode, RpcInvocationData, + ConnectionQuality, DisconnectReason, LocalParticipant, Participant, PerformRpcData, + RemoteParticipant, RpcError, RpcErrorCode, RpcInvocationData, }, publication::{LocalTrackPublication, RemoteTrackPublication, TrackPublication}, track::{ AudioTrack, LocalAudioTrack, LocalTrack, LocalVideoTrack, RemoteAudioTrack, RemoteTrack, RemoteVideoTrack, StreamState, Track, TrackDimension, TrackKind, TrackSource, VideoTrack, }, - ConnectionState, DataPacket, DataPacketKind, DisconnectReason, Room, RoomError, RoomEvent, - RoomOptions, RoomResult, RoomSdkOptions, SipDTMF, Transcription, TranscriptionSegment, + ConnectionState, DataPacket, DataPacketKind, Room, RoomError, RoomEvent, RoomOptions, + RoomResult, RoomSdkOptions, SipDTMF, Transcription, TranscriptionSegment, }; diff --git a/livekit/src/proto.rs b/livekit/src/proto.rs index b96ae2c57..b630feb1a 100644 --- a/livekit/src/proto.rs +++ b/livekit/src/proto.rs @@ -30,6 +30,27 @@ impl From for participant::ConnectionQuality { } } +impl From for participant::DisconnectReason { + fn from(value: DisconnectReason) -> Self { + match value { + DisconnectReason::UnknownReason => Self::UnknownReason, + DisconnectReason::ClientInitiated => Self::ClientInitiated, + DisconnectReason::DuplicateIdentity => Self::DuplicateIdentity, + DisconnectReason::ServerShutdown => Self::ServerShutdown, + DisconnectReason::ParticipantRemoved => Self::ParticipantRemoved, + DisconnectReason::RoomDeleted => Self::RoomDeleted, + DisconnectReason::StateMismatch => Self::StateMismatch, + DisconnectReason::JoinFailure => Self::JoinFailure, + DisconnectReason::Migration => Self::Migration, + DisconnectReason::SignalClose => Self::SignalClose, + DisconnectReason::RoomClosed => Self::RoomClosed, + DisconnectReason::UserUnavailable => Self::UserUnavailable, + DisconnectReason::UserRejected => Self::UserRejected, + DisconnectReason::SipTrunkFailure => Self::SipTrunkFailure, + } + } +} + impl TryFrom for track::TrackKind { type Error = &'static str; diff --git a/livekit/src/room/mod.rs b/livekit/src/room/mod.rs index 7e38cbfff..21e288773 100644 --- a/livekit/src/room/mod.rs +++ b/livekit/src/room/mod.rs @@ -783,6 +783,7 @@ impl RoomSession { let remote_participant = self.get_participant_by_sid(&participant_sid); if pi.state == proto::participant_info::State::Disconnected as i32 { if let Some(remote_participant) = remote_participant { + remote_participant.update_info(pi.clone()); self.clone().handle_participant_disconnect(remote_participant) } else { // Ignore, just received the ParticipantInfo but the participant is already diff --git a/livekit/src/room/participant/local_participant.rs b/livekit/src/room/participant/local_participant.rs index 037c31709..71ff4d775 100644 --- a/livekit/src/room/participant/local_participant.rs +++ b/livekit/src/room/participant/local_participant.rs @@ -637,6 +637,10 @@ impl LocalParticipant { self.inner.info.read().kind } + pub fn disconnect_reason(&self) -> DisconnectReason { + self.inner.info.read().disconnect_reason + } + pub async fn perform_rpc(&self, data: PerformRpcData) -> Result { let max_round_trip_latency = Duration::from_millis(2000); diff --git a/livekit/src/room/participant/mod.rs b/livekit/src/room/participant/mod.rs index f8f7a7460..164c3eddf 100644 --- a/livekit/src/room/participant/mod.rs +++ b/livekit/src/room/participant/mod.rs @@ -46,6 +46,24 @@ pub enum ParticipantKind { Agent, } +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum DisconnectReason { + UnknownReason, + ClientInitiated, + DuplicateIdentity, + ServerShutdown, + ParticipantRemoved, + RoomDeleted, + StateMismatch, + JoinFailure, + Migration, + SignalClose, + RoomClosed, + UserUnavailable, + UserRejected, + SipTrunkFailure, +} + #[derive(Debug, Clone)] pub enum Participant { Local(LocalParticipant), @@ -64,6 +82,7 @@ impl Participant { pub fn audio_level(self: &Self) -> f32; pub fn connection_quality(self: &Self) -> ConnectionQuality; pub fn kind(self: &Self) -> ParticipantKind; + pub fn disconnect_reason(self: &Self) -> DisconnectReason; pub(crate) fn update_info(self: &Self, info: proto::ParticipantInfo) -> (); @@ -93,6 +112,7 @@ struct ParticipantInfo { pub audio_level: f32, pub connection_quality: ConnectionQuality, pub kind: ParticipantKind, + pub disconnect_reason: DisconnectReason, } type TrackMutedHandler = Box; @@ -138,6 +158,7 @@ pub(super) fn new_inner( speaking: false, audio_level: 0.0, connection_quality: ConnectionQuality::Excellent, + disconnect_reason: DisconnectReason::UnknownReason, }), track_publications: Default::default(), events: Default::default(), @@ -150,6 +171,7 @@ pub(super) fn update_info( new_info: proto::ParticipantInfo, ) { let mut info = inner.info.write(); + info.disconnect_reason = new_info.disconnect_reason().into(); info.kind = new_info.kind().into(); info.sid = new_info.sid.try_into().unwrap(); info.identity = new_info.identity.into(); diff --git a/livekit/src/room/participant/remote_participant.rs b/livekit/src/room/participant/remote_participant.rs index e9e61ea07..58a1659e7 100644 --- a/livekit/src/room/participant/remote_participant.rs +++ b/livekit/src/room/participant/remote_participant.rs @@ -488,4 +488,8 @@ impl RemoteParticipant { pub fn kind(&self) -> ParticipantKind { self.inner.info.read().kind } + + pub fn disconnect_reason(&self) -> DisconnectReason { + self.inner.info.read().disconnect_reason + } } From a1b926ece849e45f6a38eb66a2015deaee35f475 Mon Sep 17 00:00:00 2001 From: aoife cassidy Date: Wed, 11 Dec 2024 19:57:22 +0200 Subject: [PATCH 095/274] chore(webrtc-sys): bump to m125_release (#505) --- .nanpa/bump-webrtc.kdl | 2 ++ webrtc-sys/include/livekit/audio_device.h | 4 +-- webrtc-sys/include/livekit/audio_track.h | 4 +-- .../include/livekit/video_decoder_factory.h | 5 ++-- .../include/livekit/video_encoder_factory.h | 8 +++--- webrtc-sys/libwebrtc/.gclient | 2 +- webrtc-sys/libwebrtc/build_android.sh | 5 ++++ webrtc-sys/libwebrtc/build_ios.sh | 5 ++++ webrtc-sys/libwebrtc/build_linux.sh | 5 ++++ webrtc-sys/libwebrtc/build_macos.sh | 5 ++++ webrtc-sys/libwebrtc/build_windows.cmd | 5 ++++ .../patches/abseil_use_optional.patch | 13 +++++++++ webrtc-sys/src/audio_device.cpp | 6 ++--- webrtc-sys/src/audio_track.cpp | 6 ++--- webrtc-sys/src/peer_connection_factory.cpp | 27 +++++++------------ webrtc-sys/src/video_decoder_factory.cpp | 11 ++++---- webrtc-sys/src/video_encoder_factory.cpp | 15 ++++++----- 17 files changed, 82 insertions(+), 46 deletions(-) create mode 100644 .nanpa/bump-webrtc.kdl create mode 100644 webrtc-sys/libwebrtc/patches/abseil_use_optional.patch diff --git a/.nanpa/bump-webrtc.kdl b/.nanpa/bump-webrtc.kdl new file mode 100644 index 000000000..996bd1e19 --- /dev/null +++ b/.nanpa/bump-webrtc.kdl @@ -0,0 +1,2 @@ +patch package="webrtc-sys/build" type="added" "bump libwebrtc to m125" +patch package="webrtc-sys" type="added" "bump libwebrtc to m125" diff --git a/webrtc-sys/include/livekit/audio_device.h b/webrtc-sys/include/livekit/audio_device.h index 903e22d6e..74d76823a 100644 --- a/webrtc-sys/include/livekit/audio_device.h +++ b/webrtc-sys/include/livekit/audio_device.h @@ -19,9 +19,9 @@ #include #include "api/task_queue/task_queue_factory.h" +#include "api/task_queue/task_queue_base.h" #include "modules/audio_device/include/audio_device.h" #include "rtc_base/synchronization/mutex.h" -#include "rtc_base/task_queue.h" #include "rtc_base/task_utils/repeating_task.h" namespace livekit { @@ -119,7 +119,7 @@ class AudioDevice : public webrtc::AudioDeviceModule { private: mutable webrtc::Mutex mutex_; std::vector data_; - std::unique_ptr audio_queue_; + std::unique_ptr audio_queue_; webrtc::RepeatingTaskHandle audio_task_; webrtc::AudioTransport* audio_transport_; webrtc::TaskQueueFactory* task_queue_factory_; diff --git a/webrtc-sys/include/livekit/audio_track.h b/webrtc-sys/include/livekit/audio_track.h index e7f3e7520..c780b7797 100644 --- a/webrtc-sys/include/livekit/audio_track.h +++ b/webrtc-sys/include/livekit/audio_track.h @@ -27,7 +27,7 @@ #include "livekit/webrtc.h" #include "pc/local_audio_source.h" #include "rtc_base/synchronization/mutex.h" -#include "rtc_base/task_queue.h" +#include "api/task_queue/task_queue_base.h" #include "rtc_base/task_utils/repeating_task.h" #include "rtc_base/thread_annotations.h" #include "rust/cxx.h" @@ -127,7 +127,7 @@ class AudioTrackSource { private: mutable webrtc::Mutex mutex_; - std::unique_ptr audio_queue_; + std::unique_ptr audio_queue_; webrtc::RepeatingTaskHandle audio_task_; std::vector sinks_ RTC_GUARDED_BY(mutex_); diff --git a/webrtc-sys/include/livekit/video_decoder_factory.h b/webrtc-sys/include/livekit/video_decoder_factory.h index 1943601e6..9b2d7030d 100644 --- a/webrtc-sys/include/livekit/video_decoder_factory.h +++ b/webrtc-sys/include/livekit/video_decoder_factory.h @@ -18,6 +18,7 @@ #include "api/video_codecs/video_decoder.h" #include "api/video_codecs/video_decoder_factory.h" +#include "absl/strings/match.h" namespace livekit { class VideoDecoderFactory : public webrtc::VideoDecoderFactory { @@ -29,8 +30,8 @@ class VideoDecoderFactory : public webrtc::VideoDecoderFactory { CodecSupport QueryCodecSupport(const webrtc::SdpVideoFormat& format, bool reference_scaling) const override; - std::unique_ptr CreateVideoDecoder( - const webrtc::SdpVideoFormat& format) override; + std::unique_ptr Create( + const webrtc::Environment& env, const webrtc::SdpVideoFormat& format) override; private: std::vector> factories_; diff --git a/webrtc-sys/include/livekit/video_encoder_factory.h b/webrtc-sys/include/livekit/video_encoder_factory.h index 1d7852f81..f1e8bc84a 100644 --- a/webrtc-sys/include/livekit/video_encoder_factory.h +++ b/webrtc-sys/include/livekit/video_encoder_factory.h @@ -31,8 +31,8 @@ class VideoEncoderFactory : public webrtc::VideoEncoderFactory { const webrtc::SdpVideoFormat& format, absl::optional scalability_mode) const override; - std::unique_ptr CreateVideoEncoder( - const webrtc::SdpVideoFormat& format) override; + std::unique_ptr Create( + const webrtc::Environment& env, const webrtc::SdpVideoFormat& format) override; private: std::vector> factories_; @@ -47,8 +47,8 @@ class VideoEncoderFactory : public webrtc::VideoEncoderFactory { const webrtc::SdpVideoFormat& format, absl::optional scalability_mode) const override; - std::unique_ptr CreateVideoEncoder( - const webrtc::SdpVideoFormat& format) override; + std::unique_ptr Create( + const webrtc::Environment& env, const webrtc::SdpVideoFormat& format) override; private: std::unique_ptr internal_factory_; diff --git a/webrtc-sys/libwebrtc/.gclient b/webrtc-sys/libwebrtc/.gclient index 5f2dc30af..b4fbb1f00 100644 --- a/webrtc-sys/libwebrtc/.gclient +++ b/webrtc-sys/libwebrtc/.gclient @@ -1,7 +1,7 @@ solutions = [ { "name": 'src', - "url": 'https://github.com/webrtc-sdk/webrtc.git@m114_release', + "url": 'https://github.com/webrtc-sdk/webrtc.git@m125_release', "custom_deps": {}, "deps_file": "DEPS", "managed": False, diff --git a/webrtc-sys/libwebrtc/build_android.sh b/webrtc-sys/libwebrtc/build_android.sh index 01778b206..0b7006fcc 100755 --- a/webrtc-sys/libwebrtc/build_android.sh +++ b/webrtc-sys/libwebrtc/build_android.sh @@ -71,6 +71,11 @@ cd src git apply "$COMMAND_DIR/patches/ssl_verify_callback_with_native_handle.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn git apply "$COMMAND_DIR/patches/add_deps.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn git apply "$COMMAND_DIR/patches/android_use_libunwind.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn + +cd third_party +git apply "$COMMAND_DIR/patches/abseil_use_optional.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn +cd .. + cd .. mkdir -p "$ARTIFACTS_DIR/lib" diff --git a/webrtc-sys/libwebrtc/build_ios.sh b/webrtc-sys/libwebrtc/build_ios.sh index 3bed8f7f8..94505e8b0 100755 --- a/webrtc-sys/libwebrtc/build_ios.sh +++ b/webrtc-sys/libwebrtc/build_ios.sh @@ -81,6 +81,11 @@ cd src # git apply "$COMMAND_DIR/patches/add_licenses.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn git apply "$COMMAND_DIR/patches/ssl_verify_callback_with_native_handle.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn git apply "$COMMAND_DIR/patches/add_deps.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn + +cd third_party +git apply "$COMMAND_DIR/patches/abseil_use_optional.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn +cd .. + cd .. mkdir -p "$ARTIFACTS_DIR/lib" diff --git a/webrtc-sys/libwebrtc/build_linux.sh b/webrtc-sys/libwebrtc/build_linux.sh index 5f1af2212..ef63dc744 100755 --- a/webrtc-sys/libwebrtc/build_linux.sh +++ b/webrtc-sys/libwebrtc/build_linux.sh @@ -70,6 +70,11 @@ cd src git apply "$COMMAND_DIR/patches/add_licenses.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn git apply "$COMMAND_DIR/patches/ssl_verify_callback_with_native_handle.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn git apply "$COMMAND_DIR/patches/add_deps.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn + +cd third_party +git apply "$COMMAND_DIR/patches/abseil_use_optional.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn +cd .. + cd .. mkdir -p "$ARTIFACTS_DIR/lib" diff --git a/webrtc-sys/libwebrtc/build_macos.sh b/webrtc-sys/libwebrtc/build_macos.sh index 4498b2eb0..5b07cb1e3 100755 --- a/webrtc-sys/libwebrtc/build_macos.sh +++ b/webrtc-sys/libwebrtc/build_macos.sh @@ -70,6 +70,11 @@ cd src git apply "$COMMAND_DIR/patches/add_licenses.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn git apply "$COMMAND_DIR/patches/ssl_verify_callback_with_native_handle.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn git apply "$COMMAND_DIR/patches/add_deps.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn + +cd third_party +git apply "$COMMAND_DIR/patches/abseil_use_optional.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn +cd .. + cd .. mkdir -p "$ARTIFACTS_DIR/lib" diff --git a/webrtc-sys/libwebrtc/build_windows.cmd b/webrtc-sys/libwebrtc/build_windows.cmd index 47ca9845b..249320581 100644 --- a/webrtc-sys/libwebrtc/build_windows.cmd +++ b/webrtc-sys/libwebrtc/build_windows.cmd @@ -54,6 +54,11 @@ call git apply "%COMMAND_DIR%/patches/add_licenses.patch" -v --ignore-space-chan call git apply "%COMMAND_DIR%/patches/add_deps.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn call git apply "%COMMAND_DIR%/patches/windows_silence_warnings.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn call git apply "%COMMAND_DIR%/patches/ssl_verify_callback_with_native_handle.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn + +cd third_party +call git apply "%COMMAND_DIR%/patches/abseil_use_optional.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn +cd .. + cd .. mkdir "%ARTIFACTS_DIR%\lib" diff --git a/webrtc-sys/libwebrtc/patches/abseil_use_optional.patch b/webrtc-sys/libwebrtc/patches/abseil_use_optional.patch new file mode 100644 index 000000000..476ad2619 --- /dev/null +++ b/webrtc-sys/libwebrtc/patches/abseil_use_optional.patch @@ -0,0 +1,13 @@ +diff --git a/abseil-cpp/absl/base/options.h b/abseil-cpp/absl/base/options.h +index bd43b6ef0..ab5917e75 100644 +--- a/abseil-cpp/absl/base/options.h ++++ b/abseil-cpp/absl/base/options.h +@@ -121,7 +121,7 @@ + // absl::optional is a typedef of std::optional, use the feature macro + // ABSL_USES_STD_OPTIONAL. + +-#define ABSL_OPTION_USE_STD_OPTIONAL 2 ++#define ABSL_OPTION_USE_STD_OPTIONAL 0 + + + // ABSL_OPTION_USE_STD_STRING_VIEW diff --git a/webrtc-sys/src/audio_device.cpp b/webrtc-sys/src/audio_device.cpp index 75911917a..d146a0c8a 100644 --- a/webrtc-sys/src/audio_device.cpp +++ b/webrtc-sys/src/audio_device.cpp @@ -48,11 +48,11 @@ int32_t AudioDevice::Init() { return 0; audio_queue_ = - std::make_unique(task_queue_factory_->CreateTaskQueue( - "AudioDevice", webrtc::TaskQueueFactory::Priority::NORMAL)); + task_queue_factory_->CreateTaskQueue( + "AudioDevice", webrtc::TaskQueueFactory::Priority::NORMAL); audio_task_ = - webrtc::RepeatingTaskHandle::Start(audio_queue_->Get(), [this]() { + webrtc::RepeatingTaskHandle::Start(audio_queue_.get(), [this]() { webrtc::MutexLock lock(&mutex_); if (playing_) { diff --git a/webrtc-sys/src/audio_track.cpp b/webrtc-sys/src/audio_track.cpp index 8a49c3203..8ca76e801 100644 --- a/webrtc-sys/src/audio_track.cpp +++ b/webrtc-sys/src/audio_track.cpp @@ -153,11 +153,11 @@ AudioTrackSource::InternalSource::InternalSource( buffer_.reserve(queue_size_samples_ + notify_threshold_samples_); audio_queue_ = - std::make_unique(task_queue_factory->CreateTaskQueue( - "AudioSourceCapture", webrtc::TaskQueueFactory::Priority::NORMAL)); + task_queue_factory->CreateTaskQueue( + "AudioSourceCapture", webrtc::TaskQueueFactory::Priority::NORMAL); audio_task_ = webrtc::RepeatingTaskHandle::Start( - audio_queue_->Get(), + audio_queue_.get(), [this, samples10ms]() { webrtc::MutexLock lock(&mutex_); diff --git a/webrtc-sys/src/peer_connection_factory.cpp b/webrtc-sys/src/peer_connection_factory.cpp index bbc73bfdc..31b32729e 100644 --- a/webrtc-sys/src/peer_connection_factory.cpp +++ b/webrtc-sys/src/peer_connection_factory.cpp @@ -23,6 +23,7 @@ #include "api/audio_codecs/builtin_audio_encoder_factory.h" #include "api/peer_connection_interface.h" #include "api/rtc_error.h" +#include "api/enable_media.h" #include "api/rtc_event_log/rtc_event_log_factory.h" #include "api/task_queue/default_task_queue_factory.h" #include "api/video_codecs/builtin_video_decoder_factory.h" @@ -35,7 +36,6 @@ #include "livekit/video_decoder_factory.h" #include "livekit/video_encoder_factory.h" #include "livekit/webrtc.h" -#include "media/engine/webrtc_media_engine.h" #include "rtc_base/thread.h" #include "webrtc-sys/src/peer_connection.rs.h" #include "webrtc-sys/src/peer_connection_factory.rs.h" @@ -55,32 +55,25 @@ PeerConnectionFactory::PeerConnectionFactory( dependencies.signaling_thread = rtc_runtime_->signaling_thread(); dependencies.socket_factory = rtc_runtime_->network_thread()->socketserver(); dependencies.task_queue_factory = webrtc::CreateDefaultTaskQueueFactory(); - dependencies.event_log_factory = std::make_unique( - dependencies.task_queue_factory.get()); - dependencies.call_factory = webrtc::CreateCallFactory(); + dependencies.event_log_factory = std::make_unique(); dependencies.trials = std::make_unique(); - cricket::MediaEngineDependencies media_deps; - media_deps.task_queue_factory = dependencies.task_queue_factory.get(); - audio_device_ = rtc_runtime_->worker_thread()->BlockingCall([&] { return rtc::make_ref_counted( - media_deps.task_queue_factory); + dependencies.task_queue_factory.get()); }); - media_deps.adm = audio_device_; + dependencies.adm = audio_device_; - media_deps.video_encoder_factory = + dependencies.video_encoder_factory = std::move(std::make_unique()); - media_deps.video_decoder_factory = + dependencies.video_decoder_factory = std::move(std::make_unique()); - media_deps.audio_encoder_factory = webrtc::CreateBuiltinAudioEncoderFactory(); - media_deps.audio_decoder_factory = webrtc::CreateBuiltinAudioDecoderFactory(); - media_deps.audio_processing = webrtc::AudioProcessingBuilder().Create(); - media_deps.trials = dependencies.trials.get(); - - dependencies.media_engine = cricket::CreateMediaEngine(std::move(media_deps)); + dependencies.audio_encoder_factory = webrtc::CreateBuiltinAudioEncoderFactory(); + dependencies.audio_decoder_factory = webrtc::CreateBuiltinAudioDecoderFactory(); + dependencies.audio_processing = webrtc::AudioProcessingBuilder().Create(); + webrtc::EnableMedia(dependencies); peer_factory_ = webrtc::CreateModularPeerConnectionFactory(std::move(dependencies)); diff --git a/webrtc-sys/src/video_decoder_factory.cpp b/webrtc-sys/src/video_decoder_factory.cpp index 40e742897..f7e7bb082 100644 --- a/webrtc-sys/src/video_decoder_factory.cpp +++ b/webrtc-sys/src/video_decoder_factory.cpp @@ -16,6 +16,7 @@ #include "livekit/video_decoder_factory.h" +#include "api/environment/environment.h" #include "api/video_codecs/av1_profile.h" #include "api/video_codecs/sdp_video_format.h" #include "livekit/objc_video_factory.h" @@ -69,7 +70,7 @@ std::vector VideoDecoderFactory::GetSupportedFormats() formats.push_back(webrtc::SdpVideoFormat(cricket::kAv1CodecName)); formats.push_back(webrtc::SdpVideoFormat( cricket::kAv1CodecName, - {{webrtc::kAV1FmtpProfile, + {{cricket::kAv1FmtpProfile, AV1ProfileToString(webrtc::AV1Profile::kProfile1).data()}})); #endif @@ -92,17 +93,17 @@ VideoDecoderFactory::CodecSupport VideoDecoderFactory::QueryCodecSupport( return codec_support; } -std::unique_ptr VideoDecoderFactory::CreateVideoDecoder( - const webrtc::SdpVideoFormat& format) { +std::unique_ptr VideoDecoderFactory::Create( + const webrtc::Environment& env, const webrtc::SdpVideoFormat& format) { for (const auto& factory : factories_) { for (const auto& supported_format : factory->GetSupportedFormats()) { if (supported_format.IsSameCodec(format)) - return factory->CreateVideoDecoder(format); + return factory->Create(env, format); } } if (absl::EqualsIgnoreCase(format.name, cricket::kVp8CodecName)) - return webrtc::VP8Decoder::Create(); + return webrtc::CreateVp8Decoder(env); if (absl::EqualsIgnoreCase(format.name, cricket::kVp9CodecName)) return webrtc::VP9Decoder::Create(); if (absl::EqualsIgnoreCase(format.name, cricket::kH264CodecName)) diff --git a/webrtc-sys/src/video_encoder_factory.cpp b/webrtc-sys/src/video_encoder_factory.cpp index b2005867c..cd362675e 100644 --- a/webrtc-sys/src/video_encoder_factory.cpp +++ b/webrtc-sys/src/video_encoder_factory.cpp @@ -16,6 +16,7 @@ #include "livekit/video_encoder_factory.h" +#include "api/environment/environment_factory.h" #include "api/video_codecs/sdp_video_format.h" #include "api/video_codecs/video_encoder.h" #include "api/video_codecs/video_encoder_factory_template.h" @@ -84,12 +85,12 @@ VideoEncoderFactory::InternalFactory::QueryCodecSupport( } std::unique_ptr -VideoEncoderFactory::InternalFactory::CreateVideoEncoder( - const webrtc::SdpVideoFormat& format) { +VideoEncoderFactory::InternalFactory::Create( + const webrtc::Environment& env, const webrtc::SdpVideoFormat& format) { for (const auto& factory : factories_) { for (const auto& supported_format : factory->GetSupportedFormats()) { if (supported_format.IsSameCodec(format)) - return factory->CreateVideoEncoder(format); + return factory->Create(env, format); } } @@ -97,7 +98,7 @@ VideoEncoderFactory::InternalFactory::CreateVideoEncoder( webrtc::FuzzyMatchSdpVideoFormat(Factory().GetSupportedFormats(), format); if (original_format) { - return Factory().CreateVideoEncoder(*original_format); + return Factory().Create(env, *original_format); } RTC_LOG(LS_ERROR) << "No VideoEncoder found for " << format.name; @@ -119,12 +120,12 @@ VideoEncoderFactory::CodecSupport VideoEncoderFactory::QueryCodecSupport( return internal_factory_->QueryCodecSupport(format, scalability_mode); } -std::unique_ptr VideoEncoderFactory::CreateVideoEncoder( - const webrtc::SdpVideoFormat& format) { +std::unique_ptr VideoEncoderFactory::Create( + const webrtc::Environment& env, const webrtc::SdpVideoFormat& format) { std::unique_ptr encoder; if (format.IsCodecInList(internal_factory_->GetSupportedFormats())) { encoder = std::make_unique( - internal_factory_.get(), format); + env, internal_factory_.get(), nullptr, format); } return encoder; From a0a961b9ab9ff075ee85e8dd296caf7dd319c9ef Mon Sep 17 00:00:00 2001 From: aoife cassidy Date: Wed, 11 Dec 2024 20:27:42 +0200 Subject: [PATCH 096/274] chore: update webrtc and all dependents (#512) --- .github/workflows/ffi-builds.yml | 4 ++-- .nanpa/bump-webrtc.kdl | 3 +++ download_ffi.py | 2 +- webrtc-sys/build/src/lib.rs | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ffi-builds.yml b/.github/workflows/ffi-builds.yml index ccba91f91..e011092c8 100644 --- a/.github/workflows/ffi-builds.yml +++ b/.github/workflows/ffi-builds.yml @@ -17,7 +17,7 @@ on: push: branches: ["main"] tags: - - "ffi-v*" + - "rust-sdks/livekit-ffi@*" workflow_dispatch: env: @@ -208,7 +208,7 @@ jobs: needs: build permissions: contents: write - if: startsWith(github.ref, 'refs/tags/ffi-v') + if: startsWith(github.ref, 'refs/tags/rust-sdks/livekit-ffi') env: GH_TOKEN: ${{ github.token }} steps: diff --git a/.nanpa/bump-webrtc.kdl b/.nanpa/bump-webrtc.kdl index 996bd1e19..de1db4674 100644 --- a/.nanpa/bump-webrtc.kdl +++ b/.nanpa/bump-webrtc.kdl @@ -1,2 +1,5 @@ patch package="webrtc-sys/build" type="added" "bump libwebrtc to m125" patch package="webrtc-sys" type="added" "bump libwebrtc to m125" +patch package="libwebrtc" type="added" "bump libwebrtc to m125" +patch package="livekit" type="added" "bump libwebrtc to m125" +patch package="livekit-ffi" type="added" "bump libwebrtc to m125" diff --git a/download_ffi.py b/download_ffi.py index 4d100d674..0bb980067 100755 --- a/download_ffi.py +++ b/download_ffi.py @@ -74,7 +74,7 @@ def ffi_version(): def download_ffi(platform, arch, version, output): filename = "ffi-%s-%s.zip" % (platform, arch) - url = "https://github.com/livekit/client-sdk-rust/releases/download/ffi-v%s/%s" + url = "https://github.com/livekit/client-sdk-rust/releases/download/rust-sdks/livekit-ffi@%s/%s" url = url % (version, filename) tmp = os.path.join(tempfile.gettempdir(), filename) diff --git a/webrtc-sys/build/src/lib.rs b/webrtc-sys/build/src/lib.rs index cb137c799..959bdd0c1 100644 --- a/webrtc-sys/build/src/lib.rs +++ b/webrtc-sys/build/src/lib.rs @@ -26,7 +26,7 @@ use regex::Regex; use reqwest::StatusCode; pub const SCRATH_PATH: &str = "livekit_webrtc"; -pub const WEBRTC_TAG: &str = "webrtc-dac8015-6"; +pub const WEBRTC_TAG: &str = "webrtc-b99fd2c"; pub const IGNORE_DEFINES: [&str; 2] = ["CR_CLANG_REVISION", "CR_XCODE_VERSION"]; pub fn target_os() -> String { From 3f2bbb3ecf674c3a585417fbd011d47252c53d37 Mon Sep 17 00:00:00 2001 From: aoife cassidy Date: Thu, 12 Dec 2024 13:34:14 +0200 Subject: [PATCH 097/274] fix: webrtc builds on macOS, iOS (#514) --- webrtc-sys/libwebrtc/build_ios.sh | 2 +- webrtc-sys/libwebrtc/build_macos.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/webrtc-sys/libwebrtc/build_ios.sh b/webrtc-sys/libwebrtc/build_ios.sh index 94505e8b0..6db1ce21f 100755 --- a/webrtc-sys/libwebrtc/build_ios.sh +++ b/webrtc-sys/libwebrtc/build_ios.sh @@ -125,7 +125,7 @@ ninja -C "$OUTPUT_DIR" :default \ api/task_queue:default_task_queue_factory \ sdk:native_api \ sdk:default_codec_factory_objc \ - pc:peerconnection \ + pc:peer_connection \ sdk:videocapture_objc \ sdk:framework_objc diff --git a/webrtc-sys/libwebrtc/build_macos.sh b/webrtc-sys/libwebrtc/build_macos.sh index 5b07cb1e3..c4ba72bd6 100755 --- a/webrtc-sys/libwebrtc/build_macos.sh +++ b/webrtc-sys/libwebrtc/build_macos.sh @@ -115,7 +115,7 @@ ninja -C "$OUTPUT_DIR" :default \ api/task_queue:default_task_queue_factory \ sdk:native_api \ sdk:default_codec_factory_objc \ - pc:peerconnection \ + pc:peer_connection \ sdk:videocapture_objc \ sdk:mac_framework_objc From 4a2f352e9ddd833ab404a06569c74668b24d71f4 Mon Sep 17 00:00:00 2001 From: aoife cassidy Date: Thu, 12 Dec 2024 13:42:30 +0200 Subject: [PATCH 098/274] add changeset files and bump imgproc, yuv-sys (#513) --- .nanpa/add-libyuv.kdl | 2 ++ .nanparc | 2 +- imgproc/.nanparc | 2 ++ yuv-sys/.nanparc | 2 ++ 4 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 .nanpa/add-libyuv.kdl create mode 100644 imgproc/.nanparc create mode 100644 yuv-sys/.nanparc diff --git a/.nanpa/add-libyuv.kdl b/.nanpa/add-libyuv.kdl new file mode 100644 index 000000000..0f2f3d75e --- /dev/null +++ b/.nanpa/add-libyuv.kdl @@ -0,0 +1,2 @@ +patch package="yuv-sys" type="added" "move yuv-sys to main rust-sdks monorepo" +patch package="imgproc" type="added" "move imgproc to main rust-sdks monorepo" diff --git a/.nanparc b/.nanparc index 748a8a360..7e9fced1a 100644 --- a/.nanparc +++ b/.nanparc @@ -1 +1 @@ -packages livekit livekit-ffi livekit-protocol livekit-runtime livekit-api libwebrtc webrtc-sys webrtc-sys/build soxr-sys +packages livekit livekit-ffi livekit-protocol livekit-runtime livekit-api libwebrtc webrtc-sys webrtc-sys/build soxr-sys yuv-sys imgproc diff --git a/imgproc/.nanparc b/imgproc/.nanparc new file mode 100644 index 000000000..57ab0be8e --- /dev/null +++ b/imgproc/.nanparc @@ -0,0 +1,2 @@ +version 0.3.11 +language rust diff --git a/yuv-sys/.nanparc b/yuv-sys/.nanparc new file mode 100644 index 000000000..10b95b2c7 --- /dev/null +++ b/yuv-sys/.nanparc @@ -0,0 +1,2 @@ +version 0.3.6 +language rust From d15807d3d8ce04efd0f27ed711201b464fab1e85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Thu, 12 Dec 2024 12:55:10 +0100 Subject: [PATCH 099/274] fix yuv simd errors (#515) --- .nanpa/fix-simd.kdl | 1 + Cargo.lock | 4 ---- yuv-sys/libyuv | 2 +- 3 files changed, 2 insertions(+), 5 deletions(-) create mode 100644 .nanpa/fix-simd.kdl diff --git a/.nanpa/fix-simd.kdl b/.nanpa/fix-simd.kdl new file mode 100644 index 000000000..d90870d8e --- /dev/null +++ b/.nanpa/fix-simd.kdl @@ -0,0 +1 @@ +patch package="yuv-sys" type="fixed" "fix yuv simd errors" diff --git a/Cargo.lock b/Cargo.lock index 6154cfb8b..e915bba8a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1322,8 +1322,6 @@ dependencies = [ [[package]] name = "imgproc" version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cce22149028888b582c91e56e1d53add9cd42f98d22046618478ef2aadbc9091" dependencies = [ "yuv-sys", ] @@ -3581,8 +3579,6 @@ dependencies = [ [[package]] name = "yuv-sys" version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84a3f589776c945135cd8dee6e1e0d6006748f2808e439200cc242f639b07150" dependencies = [ "bindgen", "cc", diff --git a/yuv-sys/libyuv b/yuv-sys/libyuv index 3a0ad00ed..3e435fe6d 160000 --- a/yuv-sys/libyuv +++ b/yuv-sys/libyuv @@ -1 +1 @@ -Subproject commit 3a0ad00ed34afe3a43eb742579d53e9e7c597ae3 +Subproject commit 3e435fe6d4910ae1adbb4e116317072118ff210d From 43933e58c07f66f285a2a02a48ac6f21a0a00b91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Thu, 12 Dec 2024 13:45:26 +0100 Subject: [PATCH 100/274] fix unmatched codec (#516) --- livekit/src/rtc_engine/rtc_session.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/livekit/src/rtc_engine/rtc_session.rs b/livekit/src/rtc_engine/rtc_session.rs index fa01a69fe..3b6febb0e 100644 --- a/livekit/src/rtc_engine/rtc_session.rs +++ b/livekit/src/rtc_engine/rtc_session.rs @@ -821,7 +821,6 @@ impl SessionInner { } matched.append(&mut partial_matched); - matched.append(&mut unmatched); transceiver.set_codec_preferences(matched)?; } From 893fe0f9582bd31c0d73afcf15b7552bf6f69f70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Thu, 12 Dec 2024 14:12:46 +0100 Subject: [PATCH 101/274] fix av1 supported formats (#517) --- webrtc-sys/src/video_decoder_factory.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/webrtc-sys/src/video_decoder_factory.cpp b/webrtc-sys/src/video_decoder_factory.cpp index f7e7bb082..d9ea8bae8 100644 --- a/webrtc-sys/src/video_decoder_factory.cpp +++ b/webrtc-sys/src/video_decoder_factory.cpp @@ -16,6 +16,7 @@ #include "livekit/video_decoder_factory.h" +#include #include "api/environment/environment.h" #include "api/video_codecs/av1_profile.h" #include "api/video_codecs/sdp_video_format.h" @@ -66,14 +67,9 @@ std::vector VideoDecoderFactory::GetSupportedFormats() webrtc::SupportedH264DecoderCodecs()) formats.push_back(h264_format); -#if defined(RTC_DAV1D_IN_INTERNAL_DECODER_FACTORY) - formats.push_back(webrtc::SdpVideoFormat(cricket::kAv1CodecName)); formats.push_back(webrtc::SdpVideoFormat( - cricket::kAv1CodecName, - {{cricket::kAv1FmtpProfile, - AV1ProfileToString(webrtc::AV1Profile::kProfile1).data()}})); -#endif - + webrtc::SdpVideoFormat::AV1Profile0(), + webrtc::LibaomAv1EncoderSupportedScalabilityModes())); return formats; } From bcbc7d8b749be3912900abcff5e38da0bd4711cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Thu, 12 Dec 2024 14:34:19 +0100 Subject: [PATCH 102/274] fix ffi license builds (#518) --- livekit-ffi/build.rs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/livekit-ffi/build.rs b/livekit-ffi/build.rs index 75432fdb5..33a18d772 100644 --- a/livekit-ffi/build.rs +++ b/livekit-ffi/build.rs @@ -24,23 +24,26 @@ fn main() { webrtc_sys_build::download_webrtc().unwrap(); } + { + // Copy the webrtc license to CARGO_MANIFEST_DIR + // (used by the ffi release action) + let webrtc_dir = webrtc_sys_build::webrtc_dir(); + let license = webrtc_dir.join("LICENSE.md"); + let target_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + + let out_file = Path::new(&target_dir).join("WEBRTC_LICENSE.md"); + + std::fs::copy(license, out_file).unwrap(); + } + let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap(); match target_os.as_str() { "windows" => {} "linux" => { - // Link webrtc library println!("cargo:rustc-link-lib=static=webrtc"); } "android" => { webrtc_sys_build::configure_jni_symbols().unwrap(); - // Copy the webrtc license to CARGO_MANIFEST_DIR - // (used by the ffi release action) - let license = webrtc_dir.join("LICENSE.md"); - let target_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); - - let out_file = Path::new(&target_dir).join("WEBRTC_LICENSE.md"); - - std::fs::copy(license, out_file).unwrap(); } "macos" | "ios" => { println!("cargo:rustc-link-arg=-ObjC"); From 7b6141c0007c76bbf480817208b4d35c24bf5d41 Mon Sep 17 00:00:00 2001 From: aoife cassidy Date: Thu, 12 Dec 2024 16:05:29 +0200 Subject: [PATCH 103/274] chore: bump webrtc-sys for new webrtc tag (#519) --- webrtc-sys/build/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webrtc-sys/build/src/lib.rs b/webrtc-sys/build/src/lib.rs index 959bdd0c1..3ede29dc4 100644 --- a/webrtc-sys/build/src/lib.rs +++ b/webrtc-sys/build/src/lib.rs @@ -26,7 +26,7 @@ use regex::Regex; use reqwest::StatusCode; pub const SCRATH_PATH: &str = "livekit_webrtc"; -pub const WEBRTC_TAG: &str = "webrtc-b99fd2c"; +pub const WEBRTC_TAG: &str = "webrtc-b99fd2c-2"; pub const IGNORE_DEFINES: [&str; 2] = ["CR_CLANG_REVISION", "CR_XCODE_VERSION"]; pub fn target_os() -> String { From b511e559510eb0a0cf762e171c1c5d53f38c88d9 Mon Sep 17 00:00:00 2001 From: lukasIO Date: Sat, 14 Dec 2024 11:01:54 +0100 Subject: [PATCH 104/274] Update gh action versions (#520) --- .github/workflows/ffi-builds.yml | 5 ++--- .github/workflows/webrtc-builds.yml | 21 ++++++++++----------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ffi-builds.yml b/.github/workflows/ffi-builds.yml index e011092c8..d6b90909c 100644 --- a/.github/workflows/ffi-builds.yml +++ b/.github/workflows/ffi-builds.yml @@ -196,12 +196,11 @@ jobs: Get-ChildItem -Path ${{ matrix.dylib }}, livekit_ffi.h, LICENSE.md | Compress-Archive -DestinationPath ${{ github.workspace }}\${{ matrix.name }}.zip - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ffi-builds path: ${{ matrix.name }}.zip - release: name: Release to GH (Draft) runs-on: ubuntu-latest @@ -215,7 +214,7 @@ jobs: - uses: actions/checkout@v3 - name: Download artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: ffi-builds path: ${{ github.workspace }}/ffi-builds diff --git a/.github/workflows/webrtc-builds.yml b/.github/workflows/webrtc-builds.yml index cd59f3f04..782daaad6 100644 --- a/.github/workflows/webrtc-builds.yml +++ b/.github/workflows/webrtc-builds.yml @@ -45,7 +45,7 @@ jobs: cmd: ./build_macos.sh arch: arm64 - - name: linux + - name: linux os: buildjet-8vcpu-ubuntu-2004 cmd: ./build_linux.sh arch: x64 @@ -53,8 +53,8 @@ jobs: - name: linux os: buildjet-8vcpu-ubuntu-2004 cmd: ./build_linux.sh - arch: arm64 - + arch: arm64 + - name: android os: buildjet-8vcpu-ubuntu-2004 cmd: ./build_android.sh @@ -81,10 +81,10 @@ jobs: os: macos-13 cmd: ./build_ios.sh arch: arm64 - buildargs: --environment simulator + buildargs: --environment simulator profile: - release -# - debug + # - debug name: Build webrtc (${{ matrix.target.name }}-${{ matrix.target.arch }}-${{ matrix.profile }}) ${{ matrix.target.buildargs }} runs-on: ${{ matrix.target.os }} @@ -104,7 +104,7 @@ jobs: echo "OutName: ${{ steps.setup.outputs.OUT }}" echo "OutZip: ${{ steps.setup.outputs.ZIP }}" - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: true @@ -135,7 +135,7 @@ jobs: run: echo -e "\ntarget_os = [\"${{ matrix.target.name }}\"]" >> .gclient shell: bash working-directory: webrtc-sys/libwebrtc - + - name: Build WebRTC run: ${{ matrix.target.cmd }} --arch ${{ matrix.target.arch }} --profile ${{ matrix.profile }} ${{ matrix.target.buildargs }} working-directory: webrtc-sys/libwebrtc @@ -151,12 +151,11 @@ jobs: run: Compress-Archive -Path .\webrtc-sys\libwebrtc\${{ steps.setup.outputs.OUT }} -DestinationPath ${{ steps.setup.outputs.ZIP }} - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: webrtc-builds path: ${{ steps.setup.outputs.ZIP }} - release: name: Release to GH (Draft) runs-on: ubuntu-latest @@ -167,10 +166,10 @@ jobs: env: GH_TOKEN: ${{ github.token }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Download artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: webrtc-builds path: ${{ github.workspace }}/webrtc-builds From 20343452a8a638761724b3b91615ebbb8587ac2b Mon Sep 17 00:00:00 2001 From: aoife cassidy Date: Sat, 14 Dec 2024 17:28:49 +0200 Subject: [PATCH 105/274] ci: attempt to fix windows webrtc builds (#521) --- webrtc-sys/libwebrtc/build_windows.cmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webrtc-sys/libwebrtc/build_windows.cmd b/webrtc-sys/libwebrtc/build_windows.cmd index 249320581..add24f4e9 100644 --- a/webrtc-sys/libwebrtc/build_windows.cmd +++ b/webrtc-sys/libwebrtc/build_windows.cmd @@ -46,7 +46,7 @@ set ARTIFACTS_DIR=%cd%\win-!arch!-!profile! set vs2019_install=C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional if not exist src ( - call gclient.bat sync -D --no-history + call gclient.bat sync -D --with_branch_heads --with_tags ) cd src From 6dbfb96084d58b38ca49d75918214cbd9947dbcc Mon Sep 17 00:00:00 2001 From: aoife cassidy Date: Sat, 14 Dec 2024 20:00:12 +0200 Subject: [PATCH 106/274] ci: fix FFI and WebRTC builds with artifacts v4 (#522) --- .github/workflows/ffi-builds.yml | 5 +++-- .github/workflows/webrtc-builds.yml | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ffi-builds.yml b/.github/workflows/ffi-builds.yml index d6b90909c..45ef24988 100644 --- a/.github/workflows/ffi-builds.yml +++ b/.github/workflows/ffi-builds.yml @@ -198,7 +198,7 @@ jobs: - name: Upload artifacts uses: actions/upload-artifact@v4 with: - name: ffi-builds + name: ffi-builds-${{ matrix.target }} path: ${{ matrix.name }}.zip release: @@ -216,7 +216,8 @@ jobs: - name: Download artifacts uses: actions/download-artifact@v4 with: - name: ffi-builds + name: ffi-builds-* + merge-multiple: true path: ${{ github.workspace }}/ffi-builds - name: Create draft release diff --git a/.github/workflows/webrtc-builds.yml b/.github/workflows/webrtc-builds.yml index 782daaad6..f244eca16 100644 --- a/.github/workflows/webrtc-builds.yml +++ b/.github/workflows/webrtc-builds.yml @@ -153,7 +153,7 @@ jobs: - name: Upload artifacts uses: actions/upload-artifact@v4 with: - name: webrtc-builds + name: webrtc-builds-${{ matrix.name }}-${{ matrix.arch }} path: ${{ steps.setup.outputs.ZIP }} release: @@ -171,7 +171,8 @@ jobs: - name: Download artifacts uses: actions/download-artifact@v4 with: - name: webrtc-builds + name: webrtc-builds-* + merge-multiple: true path: ${{ github.workspace }}/webrtc-builds - name: Create draft release From 627322f845d59e832cae915a9a1edbcf72a85a95 Mon Sep 17 00:00:00 2001 From: aoife cassidy Date: Sat, 14 Dec 2024 20:06:54 +0200 Subject: [PATCH 107/274] ci: fix missed line (#523) --- .github/workflows/ffi-builds.yml | 2 +- .github/workflows/webrtc-builds.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ffi-builds.yml b/.github/workflows/ffi-builds.yml index 45ef24988..3b31ec8c4 100644 --- a/.github/workflows/ffi-builds.yml +++ b/.github/workflows/ffi-builds.yml @@ -216,7 +216,7 @@ jobs: - name: Download artifacts uses: actions/download-artifact@v4 with: - name: ffi-builds-* + pattern: ffi-builds-* merge-multiple: true path: ${{ github.workspace }}/ffi-builds diff --git a/.github/workflows/webrtc-builds.yml b/.github/workflows/webrtc-builds.yml index f244eca16..b5a1b6b1d 100644 --- a/.github/workflows/webrtc-builds.yml +++ b/.github/workflows/webrtc-builds.yml @@ -171,7 +171,7 @@ jobs: - name: Download artifacts uses: actions/download-artifact@v4 with: - name: webrtc-builds-* + pattern: webrtc-builds-* merge-multiple: true path: ${{ github.workspace }}/webrtc-builds From 0d777eb924ed91aed6a5d4cbfb2303dda397f854 Mon Sep 17 00:00:00 2001 From: aoife cassidy Date: Sat, 14 Dec 2024 20:19:25 +0200 Subject: [PATCH 108/274] ci: fix builds again (#524) --- .github/workflows/webrtc-builds.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/webrtc-builds.yml b/.github/workflows/webrtc-builds.yml index b5a1b6b1d..9ffc453e4 100644 --- a/.github/workflows/webrtc-builds.yml +++ b/.github/workflows/webrtc-builds.yml @@ -153,7 +153,7 @@ jobs: - name: Upload artifacts uses: actions/upload-artifact@v4 with: - name: webrtc-builds-${{ matrix.name }}-${{ matrix.arch }} + name: webrtc-builds-${{ matrix.target.name }}-${{ matrix.target.arch }} path: ${{ steps.setup.outputs.ZIP }} release: From 0bdbe698ec2bb12a821213fc38b873d1d9cc66e6 Mon Sep 17 00:00:00 2001 From: aoife cassidy Date: Sat, 14 Dec 2024 21:06:11 +0200 Subject: [PATCH 109/274] ci: use existing OUT variable for iOS simulator (#525) --- .github/workflows/webrtc-builds.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/webrtc-builds.yml b/.github/workflows/webrtc-builds.yml index 9ffc453e4..60ebcd68a 100644 --- a/.github/workflows/webrtc-builds.yml +++ b/.github/workflows/webrtc-builds.yml @@ -153,7 +153,7 @@ jobs: - name: Upload artifacts uses: actions/upload-artifact@v4 with: - name: webrtc-builds-${{ matrix.target.name }}-${{ matrix.target.arch }} + name: webrtc-builds-${{ steps.setup.outputs.OUT }} path: ${{ steps.setup.outputs.ZIP }} release: From d1404e7d25f94269a04a220cb7463f9ad57c4710 Mon Sep 17 00:00:00 2001 From: aoife cassidy Date: Sat, 14 Dec 2024 22:58:28 +0200 Subject: [PATCH 110/274] update webrtc-sys-build/lib.rs (#526) --- webrtc-sys/build/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webrtc-sys/build/src/lib.rs b/webrtc-sys/build/src/lib.rs index 3ede29dc4..a38d740e0 100644 --- a/webrtc-sys/build/src/lib.rs +++ b/webrtc-sys/build/src/lib.rs @@ -26,7 +26,7 @@ use regex::Regex; use reqwest::StatusCode; pub const SCRATH_PATH: &str = "livekit_webrtc"; -pub const WEBRTC_TAG: &str = "webrtc-b99fd2c-2"; +pub const WEBRTC_TAG: &str = "webrtc-b99fd2c-6"; pub const IGNORE_DEFINES: [&str; 2] = ["CR_CLANG_REVISION", "CR_XCODE_VERSION"]; pub fn target_os() -> String { From 32d86183c474206370843e447cc7188becb8a2bf Mon Sep 17 00:00:00 2001 From: aoife cassidy Date: Sun, 15 Dec 2024 00:09:26 +0200 Subject: [PATCH 111/274] ci: add deploy key to nanpa CI (#527) --- .github/workflows/publish.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index de50fd655..f90b522d4 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -33,6 +33,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + with: + ssh-key: ${{ secrets.NANPA_KEY }} - uses: nbsp/ilo@v1 with: packages: ${{ github.event.inputs.packages }} From a5ef23e718628d52e6f745e3c28c8e7dc28d519d Mon Sep 17 00:00:00 2001 From: "ilo-nanpa[bot]" <174912985+ilo-nanpa[bot]@users.noreply.github.com> Date: Sat, 14 Dec 2024 22:10:08 +0000 Subject: [PATCH 112/274] nanpa: bump --- .nanpa/add-libyuv.kdl | 2 -- .nanpa/bump-webrtc.kdl | 5 ----- .nanpa/fix-simd.kdl | 1 - Cargo.toml | 12 ++++++------ imgproc/.nanparc | 2 +- imgproc/CHANGELOG.md | 7 +++++++ imgproc/Cargo.toml | 2 +- libwebrtc/.nanparc | 2 +- libwebrtc/CHANGELOG.md | 7 +++++++ libwebrtc/Cargo.toml | 2 +- livekit-ffi/.nanparc | 2 +- livekit-ffi/CHANGELOG.md | 7 +++++++ livekit/.nanparc | 2 +- livekit/CHANGELOG.md | 7 +++++++ livekit/Cargo.toml | 2 +- webrtc-sys/.nanparc | 2 +- webrtc-sys/CHANGELOG.md | 7 +++++++ webrtc-sys/Cargo.toml | 2 +- webrtc-sys/build/.nanparc | 2 +- webrtc-sys/build/CHANGELOG.md | 7 +++++++ webrtc-sys/build/Cargo.toml | 2 +- yuv-sys/.nanparc | 2 +- yuv-sys/CHANGELOG.md | 11 +++++++++++ yuv-sys/Cargo.toml | 2 +- 24 files changed, 72 insertions(+), 27 deletions(-) delete mode 100644 .nanpa/add-libyuv.kdl delete mode 100644 .nanpa/bump-webrtc.kdl delete mode 100644 .nanpa/fix-simd.kdl create mode 100644 imgproc/CHANGELOG.md create mode 100644 libwebrtc/CHANGELOG.md create mode 100644 livekit-ffi/CHANGELOG.md create mode 100644 livekit/CHANGELOG.md create mode 100644 webrtc-sys/CHANGELOG.md create mode 100644 webrtc-sys/build/CHANGELOG.md create mode 100644 yuv-sys/CHANGELOG.md diff --git a/.nanpa/add-libyuv.kdl b/.nanpa/add-libyuv.kdl deleted file mode 100644 index 0f2f3d75e..000000000 --- a/.nanpa/add-libyuv.kdl +++ /dev/null @@ -1,2 +0,0 @@ -patch package="yuv-sys" type="added" "move yuv-sys to main rust-sdks monorepo" -patch package="imgproc" type="added" "move imgproc to main rust-sdks monorepo" diff --git a/.nanpa/bump-webrtc.kdl b/.nanpa/bump-webrtc.kdl deleted file mode 100644 index de1db4674..000000000 --- a/.nanpa/bump-webrtc.kdl +++ /dev/null @@ -1,5 +0,0 @@ -patch package="webrtc-sys/build" type="added" "bump libwebrtc to m125" -patch package="webrtc-sys" type="added" "bump libwebrtc to m125" -patch package="libwebrtc" type="added" "bump libwebrtc to m125" -patch package="livekit" type="added" "bump libwebrtc to m125" -patch package="livekit-ffi" type="added" "bump libwebrtc to m125" diff --git a/.nanpa/fix-simd.kdl b/.nanpa/fix-simd.kdl deleted file mode 100644 index d90870d8e..000000000 --- a/.nanpa/fix-simd.kdl +++ /dev/null @@ -1 +0,0 @@ -patch package="yuv-sys" type="fixed" "fix yuv simd errors" diff --git a/Cargo.toml b/Cargo.toml index c0a232a25..35de291b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,14 +15,14 @@ members = [ ] [workspace.dependencies] -imgproc = { version = "0.3.11", path = "imgproc" } -yuv-sys = { version = "0.3.6", path = "yuv-sys" } -libwebrtc = { version = "0.3.7", path = "libwebrtc" } +imgproc = { version = "0.3.12", path = "imgproc" } +yuv-sys = { version = "0.3.7", path = "yuv-sys" } +libwebrtc = { version = "0.3.8", path = "libwebrtc" } livekit-api = { version = "0.4.1", path = "livekit-api" } livekit-ffi = { version = "0.12.3", path = "livekit-ffi" } livekit-protocol = { version = "0.3.6", path = "livekit-protocol" } livekit-runtime = { version = "0.3.1", path = "livekit-runtime" } -livekit = { version = "0.7.0", path = "livekit" } +livekit = { version = "0.7.1", path = "livekit" } soxr-sys = { version = "0.1.0", path = "soxr-sys" } -webrtc-sys-build = { version = "0.3.5", path = "webrtc-sys/build" } -webrtc-sys = { version = "0.3.5", path = "webrtc-sys" } +webrtc-sys-build = { version = "0.3.6", path = "webrtc-sys/build" } +webrtc-sys = { version = "0.3.6", path = "webrtc-sys" } diff --git a/imgproc/.nanparc b/imgproc/.nanparc index 57ab0be8e..4e9ce1ea8 100644 --- a/imgproc/.nanparc +++ b/imgproc/.nanparc @@ -1,2 +1,2 @@ -version 0.3.11 +version 0.3.12 language rust diff --git a/imgproc/CHANGELOG.md b/imgproc/CHANGELOG.md new file mode 100644 index 000000000..a362d251d --- /dev/null +++ b/imgproc/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog + +## [0.3.12] - 2024-12-14 + +### Added + +- move imgproc to main rust-sdks monorepo diff --git a/imgproc/Cargo.toml b/imgproc/Cargo.toml index 6f49dccd6..8356ec8fe 100644 --- a/imgproc/Cargo.toml +++ b/imgproc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "imgproc" -version = "0.3.11" +version = "0.3.12" edition = "2021" authors = ["Theo Monnom "] license = "MIT OR Apache-2.0" diff --git a/libwebrtc/.nanparc b/libwebrtc/.nanparc index 5bf971c37..9ad0822e5 100644 --- a/libwebrtc/.nanparc +++ b/libwebrtc/.nanparc @@ -1,2 +1,2 @@ -version 0.3.7 +version 0.3.8 language rust diff --git a/libwebrtc/CHANGELOG.md b/libwebrtc/CHANGELOG.md new file mode 100644 index 000000000..04423d14f --- /dev/null +++ b/libwebrtc/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog + +## [0.3.8] - 2024-12-14 + +### Added + +- bump libwebrtc to m125 diff --git a/libwebrtc/Cargo.toml b/libwebrtc/Cargo.toml index 1f8f0c988..9cd679f3b 100644 --- a/libwebrtc/Cargo.toml +++ b/libwebrtc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libwebrtc" -version = "0.3.7" +version = "0.3.8" edition = "2021" homepage = "https://livekit.io" license = "Apache-2.0" diff --git a/livekit-ffi/.nanparc b/livekit-ffi/.nanparc index 37b95fd32..eb053fb66 100644 --- a/livekit-ffi/.nanparc +++ b/livekit-ffi/.nanparc @@ -1,2 +1,2 @@ -version 0.12.2 +version 0.12.3 language rust diff --git a/livekit-ffi/CHANGELOG.md b/livekit-ffi/CHANGELOG.md new file mode 100644 index 000000000..c0b38a7af --- /dev/null +++ b/livekit-ffi/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog + +## [0.12.3] - 2024-12-14 + +### Added + +- bump libwebrtc to m125 diff --git a/livekit/.nanparc b/livekit/.nanparc index 0841e27c2..29c21da5f 100644 --- a/livekit/.nanparc +++ b/livekit/.nanparc @@ -1,2 +1,2 @@ -version 0.7.0 +version 0.7.1 language rust diff --git a/livekit/CHANGELOG.md b/livekit/CHANGELOG.md new file mode 100644 index 000000000..8e6a78304 --- /dev/null +++ b/livekit/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog + +## [0.7.1] - 2024-12-14 + +### Added + +- bump libwebrtc to m125 diff --git a/livekit/Cargo.toml b/livekit/Cargo.toml index 29113321a..a59ff58c3 100644 --- a/livekit/Cargo.toml +++ b/livekit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit" -version = "0.7.0" +version = "0.7.1" edition = "2021" license = "Apache-2.0" description = "Rust Client SDK for LiveKit" diff --git a/webrtc-sys/.nanparc b/webrtc-sys/.nanparc index d431df850..10b95b2c7 100644 --- a/webrtc-sys/.nanparc +++ b/webrtc-sys/.nanparc @@ -1,2 +1,2 @@ -version 0.3.5 +version 0.3.6 language rust diff --git a/webrtc-sys/CHANGELOG.md b/webrtc-sys/CHANGELOG.md new file mode 100644 index 000000000..215126331 --- /dev/null +++ b/webrtc-sys/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog + +## [0.3.6] - 2024-12-14 + +### Added + +- bump libwebrtc to m125 diff --git a/webrtc-sys/Cargo.toml b/webrtc-sys/Cargo.toml index 7c5e9b173..dfc6c7c6e 100644 --- a/webrtc-sys/Cargo.toml +++ b/webrtc-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "webrtc-sys" -version = "0.3.5" +version = "0.3.6" edition = "2021" homepage = "https://livekit.io" license = "Apache-2.0" diff --git a/webrtc-sys/build/.nanparc b/webrtc-sys/build/.nanparc index d431df850..10b95b2c7 100644 --- a/webrtc-sys/build/.nanparc +++ b/webrtc-sys/build/.nanparc @@ -1,2 +1,2 @@ -version 0.3.5 +version 0.3.6 language rust diff --git a/webrtc-sys/build/CHANGELOG.md b/webrtc-sys/build/CHANGELOG.md new file mode 100644 index 000000000..215126331 --- /dev/null +++ b/webrtc-sys/build/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog + +## [0.3.6] - 2024-12-14 + +### Added + +- bump libwebrtc to m125 diff --git a/webrtc-sys/build/Cargo.toml b/webrtc-sys/build/Cargo.toml index 7248cd1bb..c12a0acbc 100644 --- a/webrtc-sys/build/Cargo.toml +++ b/webrtc-sys/build/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "webrtc-sys-build" -version = "0.3.5" +version = "0.3.6" edition = "2021" license = "Apache-2.0" description = "Build utilities when working with libwebrtc" diff --git a/yuv-sys/.nanparc b/yuv-sys/.nanparc index 10b95b2c7..5bf971c37 100644 --- a/yuv-sys/.nanparc +++ b/yuv-sys/.nanparc @@ -1,2 +1,2 @@ -version 0.3.6 +version 0.3.7 language rust diff --git a/yuv-sys/CHANGELOG.md b/yuv-sys/CHANGELOG.md new file mode 100644 index 000000000..f13664759 --- /dev/null +++ b/yuv-sys/CHANGELOG.md @@ -0,0 +1,11 @@ +# Changelog + +## [0.3.7] - 2024-12-14 + +### Added + +- move yuv-sys to main rust-sdks monorepo + +### Fixed + +- fix yuv simd errors diff --git a/yuv-sys/Cargo.toml b/yuv-sys/Cargo.toml index 384815730..2bd72148e 100644 --- a/yuv-sys/Cargo.toml +++ b/yuv-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yuv-sys" -version = "0.3.6" +version = "0.3.7" edition = "2021" authors = ["Theo Monnom "] license = "MIT OR Apache-2.0" From 3d9168a840d4f4b4e8b15ec7c70a077860574513 Mon Sep 17 00:00:00 2001 From: lukasIO Date: Mon, 16 Dec 2024 13:59:54 +0100 Subject: [PATCH 113/274] Add FFI events for data streams (#510) * update protocol and streams boilerplate * wip filestream reader * text stream reader * finish data stream receiving * cleanup * receiving working * working with mutex * working with RWLock * cleanup * simplify * better lock scope * filestream fixes * add example * remove rust client api and replace with raw events * cleanup imports * more cleanup * ffi imports * implement ffi --- livekit-ffi/protocol/room.proto | 54 +++++++++++ livekit-ffi/src/conversion/room.rs | 48 +++++++++ livekit-ffi/src/livekit.proto.rs | 134 +++++++++++++++++++++++++- livekit-ffi/src/server/room.rs | 6 ++ livekit/src/room/mod.rs | 27 +++++- livekit/src/rtc_engine/mod.rs | 12 +++ livekit/src/rtc_engine/rtc_session.rs | 16 +++ 7 files changed, 292 insertions(+), 5 deletions(-) diff --git a/livekit-ffi/protocol/room.proto b/livekit-ffi/protocol/room.proto index 4d0f3d92d..c6f595699 100644 --- a/livekit-ffi/protocol/room.proto +++ b/livekit-ffi/protocol/room.proto @@ -367,6 +367,8 @@ message RoomEvent { DataPacketReceived data_packet_received = 27; TranscriptionReceived transcription_received = 28; ChatMessageReceived chat_message = 29; + DataStream.Header stream_header = 30; + DataStream.Chunk stream_chunk = 31; } } @@ -526,3 +528,55 @@ message Reconnected {} message RoomEOS {} +message DataStream { + + // enum for operation types (specific to TextHeader) + enum OperationType { + CREATE = 0; + UPDATE = 1; + DELETE = 2; + REACTION = 3; + } + + // header properties specific to text streams + message TextHeader { + required OperationType operation_type = 1; + required int32 version = 2; // Optional: Version for updates/edits + required string reply_to_stream_id = 3; // Optional: Reply to specific message + repeated string attached_stream_ids = 4; // file attachments for text streams + required bool generated = 5; // true if the text has been generated by an agent from a participant's audio transcription + + } + + // header properties specific to file or image streams + message FileHeader { + required string file_name = 1; // name of the file + } + + // main DataStream.Header that contains a oneof for specific headers + message Header { + required string stream_id = 1; // unique identifier for this data stream + required int64 timestamp = 2; // using int64 for Unix timestamp + required string topic = 3; + required string mime_type = 4; + optional uint64 total_length = 5; // only populated for finite streams, if it's a stream of unknown size this stays empty + optional uint64 total_chunks = 6; // only populated for finite streams, if it's a stream of unknown size this stays empty + map extensions = 7; // user defined extensions map that can carry additional info + + // oneof to choose between specific header types + oneof content_header { + TextHeader text_header = 8; + FileHeader file_header = 9; + } + } + + message Chunk { + required string stream_id = 1; // unique identifier for this data stream to map it to the correct header + required uint64 chunk_index = 2; + required bytes content = 3; // content as binary (bytes) + required bool complete = 4; // true only if this is the last chunk of this stream - can also be sent with empty content + required int32 version = 5; // a version indicating that this chunk_index has been retroactively modified and the original one needs to be replaced + optional bytes iv = 6; // optional, initialization vector for AES-GCM encryption + } +} + diff --git a/livekit-ffi/src/conversion/room.rs b/livekit-ffi/src/conversion/room.rs index dac00067d..521c5c8c5 100644 --- a/livekit-ffi/src/conversion/room.rs +++ b/livekit-ffi/src/conversion/room.rs @@ -281,3 +281,51 @@ impl From for proto::ChatMessage { } } } + +impl From for proto::data_stream::Header { + fn from(msg: livekit_protocol::data_stream::Header) -> Self { + let content_header = match msg.content_header { + Some(livekit_protocol::data_stream::header::ContentHeader::TextHeader(text_header)) => { + Some(proto::data_stream::header::ContentHeader::TextHeader( + proto::data_stream::TextHeader { + operation_type: text_header.operation_type, + version: text_header.version, + reply_to_stream_id: text_header.reply_to_stream_id, + attached_stream_ids: text_header.attached_stream_ids, + generated: text_header.generated, + }, + )) + } + Some(livekit_protocol::data_stream::header::ContentHeader::FileHeader(file_header)) => { + Some(proto::data_stream::header::ContentHeader::FileHeader( + proto::data_stream::FileHeader { file_name: file_header.file_name }, + )) + } + None => None, + }; + + proto::data_stream::Header { + stream_id: msg.stream_id, + timestamp: msg.timestamp, + topic: msg.topic, + mime_type: msg.mime_type, + total_chunks: msg.total_chunks, + total_length: msg.total_length, + extensions: msg.extensions, + content_header, + } + } +} + +impl From for proto::data_stream::Chunk { + fn from(msg: livekit_protocol::data_stream::Chunk) -> Self { + proto::data_stream::Chunk { + stream_id: msg.stream_id, + content: msg.content, + complete: msg.complete, + chunk_index: msg.chunk_index, + version: msg.version, + iv: msg.iv, + } + } +} diff --git a/livekit-ffi/src/livekit.proto.rs b/livekit-ffi/src/livekit.proto.rs index 700666903..c45f678c7 100644 --- a/livekit-ffi/src/livekit.proto.rs +++ b/livekit-ffi/src/livekit.proto.rs @@ -1,4 +1,5 @@ // @generated +// This file is @generated by prost-build. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FrameCryptor { @@ -2634,7 +2635,7 @@ pub struct OwnedBuffer { pub struct RoomEvent { #[prost(uint64, required, tag="1")] pub room_handle: u64, - #[prost(oneof="room_event::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29")] + #[prost(oneof="room_event::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31")] pub message: ::core::option::Option, } /// Nested message and enum types in `RoomEvent`. @@ -2700,6 +2701,10 @@ pub mod room_event { TranscriptionReceived(super::TranscriptionReceived), #[prost(message, tag="29")] ChatMessage(super::ChatMessageReceived), + #[prost(message, tag="30")] + StreamHeader(super::data_stream::Header), + #[prost(message, tag="31")] + StreamChunk(super::data_stream::Chunk), } } #[allow(clippy::derive_partial_eq_without_eq)] @@ -2974,6 +2979,133 @@ pub struct Reconnected { #[derive(Clone, PartialEq, ::prost::Message)] pub struct RoomEos { } +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DataStream { +} +/// Nested message and enum types in `DataStream`. +pub mod data_stream { + /// header properties specific to text streams + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] + pub struct TextHeader { + #[prost(enumeration="OperationType", required, tag="1")] + pub operation_type: i32, + /// Optional: Version for updates/edits + #[prost(int32, required, tag="2")] + pub version: i32, + /// Optional: Reply to specific message + #[prost(string, required, tag="3")] + pub reply_to_stream_id: ::prost::alloc::string::String, + /// file attachments for text streams + #[prost(string, repeated, tag="4")] + pub attached_stream_ids: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + /// true if the text has been generated by an agent from a participant's audio transcription + #[prost(bool, required, tag="5")] + pub generated: bool, + } + /// header properties specific to file or image streams + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] + pub struct FileHeader { + /// name of the file + #[prost(string, required, tag="1")] + pub file_name: ::prost::alloc::string::String, + } + /// main DataStream.Header that contains a oneof for specific headers + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] + pub struct Header { + /// unique identifier for this data stream + #[prost(string, required, tag="1")] + pub stream_id: ::prost::alloc::string::String, + /// using int64 for Unix timestamp + #[prost(int64, required, tag="2")] + pub timestamp: i64, + #[prost(string, required, tag="3")] + pub topic: ::prost::alloc::string::String, + #[prost(string, required, tag="4")] + pub mime_type: ::prost::alloc::string::String, + /// only populated for finite streams, if it's a stream of unknown size this stays empty + #[prost(uint64, optional, tag="5")] + pub total_length: ::core::option::Option, + /// only populated for finite streams, if it's a stream of unknown size this stays empty + #[prost(uint64, optional, tag="6")] + pub total_chunks: ::core::option::Option, + /// user defined extensions map that can carry additional info + #[prost(map="string, string", tag="7")] + pub extensions: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + /// oneof to choose between specific header types + #[prost(oneof="header::ContentHeader", tags="8, 9")] + pub content_header: ::core::option::Option, + } + /// Nested message and enum types in `Header`. + pub mod header { + /// oneof to choose between specific header types + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum ContentHeader { + #[prost(message, tag="8")] + TextHeader(super::TextHeader), + #[prost(message, tag="9")] + FileHeader(super::FileHeader), + } + } + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] + pub struct Chunk { + /// unique identifier for this data stream to map it to the correct header + #[prost(string, required, tag="1")] + pub stream_id: ::prost::alloc::string::String, + #[prost(uint64, required, tag="2")] + pub chunk_index: u64, + /// content as binary (bytes) + #[prost(bytes="vec", required, tag="3")] + pub content: ::prost::alloc::vec::Vec, + /// true only if this is the last chunk of this stream - can also be sent with empty content + #[prost(bool, required, tag="4")] + pub complete: bool, + /// a version indicating that this chunk_index has been retroactively modified and the original one needs to be replaced + #[prost(int32, required, tag="5")] + pub version: i32, + /// optional, initialization vector for AES-GCM encryption + #[prost(bytes="vec", optional, tag="6")] + pub iv: ::core::option::Option<::prost::alloc::vec::Vec>, + } + /// enum for operation types (specific to TextHeader) + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] + #[repr(i32)] + pub enum OperationType { + Create = 0, + Update = 1, + Delete = 2, + Reaction = 3, + } + impl OperationType { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + OperationType::Create => "CREATE", + OperationType::Update => "UPDATE", + OperationType::Delete => "DELETE", + OperationType::Reaction => "REACTION", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "CREATE" => Some(Self::Create), + "UPDATE" => Some(Self::Update), + "DELETE" => Some(Self::Delete), + "REACTION" => Some(Self::Reaction), + _ => None, + } + } + } +} #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] pub enum IceTransportType { diff --git a/livekit-ffi/src/server/room.rs b/livekit-ffi/src/server/room.rs index 1367897e8..ca0d26ac8 100644 --- a/livekit-ffi/src/server/room.rs +++ b/livekit-ffi/src/server/room.rs @@ -1137,6 +1137,12 @@ async fn forward_event( state: proto::EncryptionState::from(state).into(), })); } + RoomEvent::StreamHeaderReceived { header } => { + let _ = send_event(proto::room_event::Message::StreamHeader(header.into())); + } + RoomEvent::StreamChunkReceived { chunk } => { + let _ = send_event(proto::room_event::Message::StreamChunk(chunk.into())); + } _ => { log::warn!("unhandled room event: {:?}", event); } diff --git a/livekit/src/room/mod.rs b/livekit/src/room/mod.rs index 21e288773..502efb2c1 100644 --- a/livekit/src/room/mod.rs +++ b/livekit/src/room/mod.rs @@ -31,10 +31,7 @@ use parking_lot::RwLock; pub use proto::DisconnectReason; use proto::{promise::Promise, SignalTarget}; use thiserror::Error; -use tokio::{ - signal, - sync::{mpsc, oneshot, Mutex as AsyncMutex}, -}; +use tokio::sync::{mpsc, oneshot, Mutex as AsyncMutex}; pub use self::{ e2ee::{manager::E2eeManager, E2eeOptions}, @@ -171,6 +168,12 @@ pub enum RoomEvent { message: ChatMessage, participant: Option, }, + StreamHeaderReceived { + header: proto::data_stream::Header, + }, + StreamChunkReceived { + chunk: proto::data_stream::Chunk, + }, E2eeStateChanged { participant: Participant, state: EncryptionState, @@ -720,6 +723,12 @@ impl RoomSession { EngineEvent::LocalTrackSubscribed { track_sid } => { self.handle_track_subscribed(track_sid) } + EngineEvent::DataStreamHeader { header } => { + self.handle_data_stream_header(header); + } + EngineEvent::DataStreamChunk { chunk } => { + self.handle_data_stream_chunk(chunk); + } _ => {} } @@ -1230,6 +1239,16 @@ impl RoomSession { }); } + fn handle_data_stream_header(&self, header: proto::data_stream::Header) { + let event = RoomEvent::StreamHeaderReceived { header }; + self.dispatcher.dispatch(&event); + } + + fn handle_data_stream_chunk(&self, chunk: proto::data_stream::Chunk) { + let event = RoomEvent::StreamChunkReceived { chunk }; + self.dispatcher.dispatch(&event); + } + /// Create a new participant /// Also add it to the participants list fn create_participant( diff --git a/livekit/src/rtc_engine/mod.rs b/livekit/src/rtc_engine/mod.rs index 4b3c44933..d4f5dec97 100644 --- a/livekit/src/rtc_engine/mod.rs +++ b/livekit/src/rtc_engine/mod.rs @@ -159,6 +159,12 @@ pub enum EngineEvent { LocalTrackSubscribed { track_sid: String, }, + DataStreamHeader { + header: proto::data_stream::Header, + }, + DataStreamChunk { + chunk: proto::data_stream::Chunk, + }, } /// Represents a running RtcSession with the ability to close the session @@ -524,6 +530,12 @@ impl EngineInner { SessionEvent::LocalTrackSubscribed { track_sid } => { let _ = self.engine_tx.send(EngineEvent::LocalTrackSubscribed { track_sid }); } + SessionEvent::DataStreamHeader { header } => { + let _ = self.engine_tx.send(EngineEvent::DataStreamHeader { header }); + } + SessionEvent::DataStreamChunk { chunk } => { + let _ = self.engine_tx.send(EngineEvent::DataStreamChunk { chunk }); + } } Ok(()) } diff --git a/livekit/src/rtc_engine/rtc_session.rs b/livekit/src/rtc_engine/rtc_session.rs index 3b6febb0e..b3cb4f094 100644 --- a/livekit/src/rtc_engine/rtc_session.rs +++ b/livekit/src/rtc_engine/rtc_session.rs @@ -135,6 +135,12 @@ pub enum SessionEvent { action: proto::leave_request::Action, retry_now: bool, }, + DataStreamHeader { + header: proto::data_stream::Header, + }, + DataStreamChunk { + chunk: proto::data_stream::Chunk, + }, } #[derive(Serialize, Deserialize)] @@ -723,6 +729,16 @@ impl SessionInner { message: ChatMessage::from(message.clone()), }); } + proto::data_packet::Value::StreamHeader(message) => { + let _ = self + .emitter + .send(SessionEvent::DataStreamHeader { header: message.clone() }); + } + proto::data_packet::Value::StreamChunk(message) => { + let _ = self + .emitter + .send(SessionEvent::DataStreamChunk { chunk: message.clone() }); + } _ => {} } } From e04a3cc4e9a43902f9640c628654095f284fd332 Mon Sep 17 00:00:00 2001 From: Daisuke Murase Date: Mon, 16 Dec 2024 21:43:38 -0800 Subject: [PATCH 114/274] Fix `play_from_disk` example to work with recent SDK (#529) * fix compilation error * fix wave header parser to allow other chunks before the data chunk --- examples/play_from_disk/src/main.rs | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/examples/play_from_disk/src/main.rs b/examples/play_from_disk/src/main.rs index b96d4e5c7..21f0de17a 100644 --- a/examples/play_from_disk/src/main.rs +++ b/examples/play_from_disk/src/main.rs @@ -7,10 +7,10 @@ use livekit::{ }, Room, RoomOptions, }; -use std::{env, mem::size_of, sync::Arc, time::Duration}; +use std::{env, io::SeekFrom, mem::size_of, sync::Arc, time::Duration}; use std::{error::Error, io}; use thiserror::Error; -use tokio::io::{AsyncRead, AsyncReadExt, BufReader}; +use tokio::io::{AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt, BufReader}; #[derive(Debug, Error)] pub enum WavError { @@ -20,7 +20,7 @@ pub enum WavError { Io(#[from] io::Error), } -pub struct WavReader { +pub struct WavReader { reader: R, } @@ -39,7 +39,7 @@ pub struct WavHeader { bits_per_sample: u16, } -impl WavReader { +impl WavReader { pub fn new(reader: R) -> Self { Self { reader } } @@ -76,8 +76,19 @@ impl WavReader { let byte_rate = self.reader.read_u32_le().await?; let block_align = self.reader.read_u16_le().await?; let bits_per_sample = self.reader.read_u16_le().await?; - self.reader.read_exact(&mut data_chunk).await?; - let data_size = self.reader.read_u32_le().await?; + + let mut data_size = 0; + loop { + self.reader.read_exact(&mut data_chunk).await?; + data_size = self.reader.read_u32_le().await?; + + if &data_chunk == b"data" { + break; + } else { + // skip non data chunks + self.reader.seek(SeekFrom::Current(data_size.into())).await?; + } + } if &data_chunk != b"data" { return Err(WavError::InvalidHeader("Invalid data chunk")); @@ -123,12 +134,13 @@ async fn main() -> Result<(), Box> { .await .unwrap(); let room = Arc::new(room); - log::info!("Connected to room: {} - {}", room.name(), room.sid()); + log::info!("Connected to room: {} - {}", room.name(), room.sid().await); let source = NativeAudioSource::new( AudioSourceOptions::default(), header.sample_rate, header.num_channels as u32, + 1000, ); let track = LocalAudioTrack::create_audio_track("file", RtcAudioSource::Native(source.clone())); From ca95b5440ba9aecb3113c0ab066c6b9159632256 Mon Sep 17 00:00:00 2001 From: Daisuke Murase Date: Mon, 16 Dec 2024 21:46:12 -0800 Subject: [PATCH 115/274] fix compilation error (#528) --- examples/save_to_disk/src/main.rs | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/examples/save_to_disk/src/main.rs b/examples/save_to_disk/src/main.rs index d378dedce..2b68d190d 100644 --- a/examples/save_to_disk/src/main.rs +++ b/examples/save_to_disk/src/main.rs @@ -12,9 +12,9 @@ const FILE_PATH: &str = "record.wav"; #[derive(Debug, Clone, Copy)] pub struct WavHeader { - pub sample_rate: u32, + pub sample_rate: i32, pub bit_depth: u16, - pub num_channels: u32, + pub num_channels: i32, } pub struct WavWriter { @@ -43,8 +43,8 @@ impl WavWriter { fn write_header(&mut self) -> Result<(), std::io::Error> { let byte_rate = self.header.sample_rate - * self.header.bit_depth as u32 - * self.header.num_channels as u32; + * self.header.bit_depth as i32 + * self.header.num_channels; let block_align = byte_rate as u16 / self.header.sample_rate as u16; @@ -55,8 +55,8 @@ impl WavWriter { self.data.put_u32_le(16); // Subchunk1Size (16 for PCM) self.data.put_u16_le(1); // AudioFormat (1 for PCM) self.data.put_u16_le(self.header.num_channels as u16); - self.data.put_u32_le(self.header.sample_rate); - self.data.put_u32_le(byte_rate); + self.data.put_i32_le(self.header.sample_rate); + self.data.put_i32_le(byte_rate); self.data.put_u16_le(block_align); self.data.put_u16_le(self.header.bit_depth); self.data.put_slice(b"data"); @@ -118,15 +118,18 @@ async fn record_track(audio_track: RemoteAudioTrack) -> Result<(), std::io::Erro println!("Recording track {:?}", audio_track.sid()); let rtc_track = audio_track.rtc_track(); + let sample_rate = 48000; + let num_channels = 2; + let header = WavHeader { - sample_rate: 48000, + sample_rate, bit_depth: 16, - num_channels: 2, + num_channels, }; let mut resampler = audio_resampler::AudioResampler::default(); let mut wav_writer = WavWriter::create(FILE_PATH, header).await?; - let mut audio_stream = NativeAudioStream::new(rtc_track); + let mut audio_stream = NativeAudioStream::new(rtc_track, sample_rate, num_channels); let max_record = 5 * header.sample_rate * header.num_channels; let mut sample_count = 0; @@ -136,8 +139,8 @@ async fn record_track(audio_track: RemoteAudioTrack) -> Result<(), std::io::Erro frame.samples_per_channel, frame.num_channels, frame.sample_rate, - header.num_channels, - header.sample_rate, + header.num_channels as u32, + header.sample_rate as u32, ); for sample in data { From 7e01c918546daff87b9f52e39923f7a2c47d2a78 Mon Sep 17 00:00:00 2001 From: Daisuke Murase Date: Mon, 16 Dec 2024 21:47:47 -0800 Subject: [PATCH 116/274] fix compilation error (#530) --- examples/wgpu_room/src/service.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/wgpu_room/src/service.rs b/examples/wgpu_room/src/service.rs index 9d3a26278..a075efb22 100644 --- a/examples/wgpu_room/src/service.rs +++ b/examples/wgpu_room/src/service.rs @@ -122,14 +122,14 @@ async fn service_task(inner: Arc, mut cmd_rx: mpsc::UnboundedRecei key_provider, }); + let mut options = RoomOptions::default(); + options.auto_subscribe = auto_subscribe; + options.e2ee = e2ee; + let res = Room::connect( &url, &token, - RoomOptions { - auto_subscribe, - e2ee, - ..Default::default() - }, + options, ) .await; From 3b1931cdc2cecb412f5760c218f5375f79a6a0d6 Mon Sep 17 00:00:00 2001 From: aoife cassidy Date: Sun, 22 Dec 2024 22:00:33 +0200 Subject: [PATCH 117/274] bump livekit-ffi to 0.12.4 (#536) --- .nanpa/bump-ffi.kdl | 1 + 1 file changed, 1 insertion(+) create mode 100644 .nanpa/bump-ffi.kdl diff --git a/.nanpa/bump-ffi.kdl b/.nanpa/bump-ffi.kdl new file mode 100644 index 000000000..659633490 --- /dev/null +++ b/.nanpa/bump-ffi.kdl @@ -0,0 +1 @@ +patch type="added" package="livekit-ffi" "bump libwebrtc to m125" From cc889daa26c2ea50258932708a24e095dcc850cc Mon Sep 17 00:00:00 2001 From: "ilo-nanpa[bot]" <174912985+ilo-nanpa[bot]@users.noreply.github.com> Date: Sun, 22 Dec 2024 20:02:05 +0000 Subject: [PATCH 118/274] nanpa: bump --- .nanpa/bump-ffi.kdl | 1 - Cargo.toml | 2 +- livekit-ffi/.nanparc | 2 +- livekit-ffi/CHANGELOG.md | 6 ++++++ livekit-ffi/Cargo.toml | 2 +- 5 files changed, 9 insertions(+), 4 deletions(-) delete mode 100644 .nanpa/bump-ffi.kdl diff --git a/.nanpa/bump-ffi.kdl b/.nanpa/bump-ffi.kdl deleted file mode 100644 index 659633490..000000000 --- a/.nanpa/bump-ffi.kdl +++ /dev/null @@ -1 +0,0 @@ -patch type="added" package="livekit-ffi" "bump libwebrtc to m125" diff --git a/Cargo.toml b/Cargo.toml index 35de291b8..60819ca90 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ imgproc = { version = "0.3.12", path = "imgproc" } yuv-sys = { version = "0.3.7", path = "yuv-sys" } libwebrtc = { version = "0.3.8", path = "libwebrtc" } livekit-api = { version = "0.4.1", path = "livekit-api" } -livekit-ffi = { version = "0.12.3", path = "livekit-ffi" } +livekit-ffi = { version = "0.12.4", path = "livekit-ffi" } livekit-protocol = { version = "0.3.6", path = "livekit-protocol" } livekit-runtime = { version = "0.3.1", path = "livekit-runtime" } livekit = { version = "0.7.1", path = "livekit" } diff --git a/livekit-ffi/.nanparc b/livekit-ffi/.nanparc index eb053fb66..bfb75eb65 100644 --- a/livekit-ffi/.nanparc +++ b/livekit-ffi/.nanparc @@ -1,2 +1,2 @@ -version 0.12.3 +version 0.12.4 language rust diff --git a/livekit-ffi/CHANGELOG.md b/livekit-ffi/CHANGELOG.md index c0b38a7af..434f26950 100644 --- a/livekit-ffi/CHANGELOG.md +++ b/livekit-ffi/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [0.12.4] - 2024-12-22 + +### Added + +- bump libwebrtc to m125 + ## [0.12.3] - 2024-12-14 ### Added diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index b7cab7fda..a2bfd9ccc 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.12.3" +version = "0.12.4" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" From 8e2d312a2deec48f49cf5936cfe16fd77cea4c7d Mon Sep 17 00:00:00 2001 From: Ben Cherry Date: Fri, 3 Jan 2025 12:04:17 -0800 Subject: [PATCH 119/274] Fix deadlocked engine in RPC (#532) * example * Fix deadlocked engine in RPC * generated protobuf * p * c * c * nanpa * mv --------- Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- .nanpa/rpc-deadlock.kdl | 1 + examples/rpc/src/main.rs | 74 ++++++++++++++++++++++++++++++++++++++-- livekit/src/room/mod.rs | 23 +++++++------ 3 files changed, 86 insertions(+), 12 deletions(-) create mode 100644 .nanpa/rpc-deadlock.kdl diff --git a/.nanpa/rpc-deadlock.kdl b/.nanpa/rpc-deadlock.kdl new file mode 100644 index 000000000..35f6dbe7a --- /dev/null +++ b/.nanpa/rpc-deadlock.kdl @@ -0,0 +1 @@ +patch type="fixed" "Fixed deadlock with nested RPC calls" package="livekit" diff --git a/examples/rpc/src/main.rs b/examples/rpc/src/main.rs index 11e0694b7..bc53ab69e 100644 --- a/examples/rpc/src/main.rs +++ b/examples/rpc/src/main.rs @@ -54,7 +54,7 @@ async fn main() -> Result<(), Box> { connect_participant("math-genius", &room_name, &url, &api_key, &api_secret) )?; - register_receiver_methods(&greeters_room, &math_genius_room).await; + register_receiver_methods(greeters_room.clone(), math_genius_room.clone()).await; println!("\n\nRunning greeting example..."); perform_greeting(&callers_room).await?; @@ -67,6 +67,9 @@ async fn main() -> Result<(), Box> { sleep(Duration::from_secs(2)).await; perform_quantum_hypergeometric_series(&callers_room).await?; + println!("\n\nRunning nested calculation example..."); + perform_nested_calculation(&callers_room).await?; + println!("\n\nParticipants done, disconnecting..."); callers_room.close().await?; greeters_room.close().await?; @@ -77,7 +80,7 @@ async fn main() -> Result<(), Box> { Ok(()) } -async fn register_receiver_methods(greeters_room: &Arc, math_genius_room: &Arc) { +async fn register_receiver_methods(greeters_room: Arc, math_genius_room: Arc) { greeters_room.local_participant().register_rpc_method("arrival".to_string(), |data| { Box::pin(async move { println!( @@ -133,6 +136,41 @@ async fn register_receiver_methods(greeters_room: &Arc, math_genius_room: Ok(json!({"result": result}).to_string()) }) }); + + math_genius_room.local_participant().register_rpc_method("nested-calculation".to_string(), move |data| { + let math_genius_room = math_genius_room.clone(); + Box::pin(async move { + let json_data: Value = serde_json::from_str(&data.payload).unwrap(); + let number = json_data["number"].as_f64().unwrap(); + println!( + "[{}] [Math Genius] {} wants me to do a nested calculation on {}.", + elapsed_time(), + data.caller_identity, + number + ); + + match math_genius_room.local_participant().perform_rpc(PerformRpcData { + destination_identity: data.caller_identity.to_string(), + method: "provide-intermediate".to_string(), + payload: json!({"original": number}).to_string(), + ..Default::default() + }).await { + Ok(intermediate_response) => { + let intermediate: Value = serde_json::from_str(&intermediate_response).unwrap(); + let intermediate_value = intermediate["value"].as_f64().unwrap(); + let final_result = intermediate_value * 2.0; + println!("[{}] [Math Genius] Got intermediate value {}, final result is {}", + elapsed_time(), intermediate_value, final_result); + Ok(json!({"result": final_result}).to_string()) + } + Err(e) => Err(RpcError { + code: 1, + message: "Failed to get intermediate result".to_string(), + data: None, + }), + } + }) + }); } async fn perform_greeting(room: &Arc) -> Result<(), Box> { @@ -230,6 +268,38 @@ async fn perform_division(room: &Arc) -> Result<(), Box) -> Result<(), Box> { + room.local_participant().register_rpc_method("provide-intermediate".to_string(), |data| { + Box::pin(async move { + let json_data: Value = serde_json::from_str(&data.payload).unwrap(); + let original = json_data["original"].as_f64().unwrap(); + let intermediate = original + 10.0; + println!("[{}] [Caller] Providing intermediate calculation: {} + 10 = {}", + elapsed_time(), original, intermediate); + Ok(json!({"value": intermediate}).to_string()) + }) + }); + + println!("[{}] Starting nested calculation with value 5", elapsed_time()); + match room + .local_participant() + .perform_rpc(PerformRpcData { + destination_identity: "math-genius".to_string(), + method: "nested-calculation".to_string(), + payload: json!({"number": 5.0}).to_string(), + ..Default::default() + }) + .await + { + Ok(response) => { + let parsed_response: Value = serde_json::from_str(&response)?; + println!("[{}] Final result: {}", elapsed_time(), parsed_response["result"]); + } + Err(e) => log::error!("[{}] RPC call failed: {:?}", elapsed_time(), e), + } + Ok(()) +} + async fn connect_participant( identity: &str, room_name: &str, diff --git a/livekit/src/room/mod.rs b/livekit/src/room/mod.rs index 502efb2c1..5653002a1 100644 --- a/livekit/src/room/mod.rs +++ b/livekit/src/room/mod.rs @@ -699,16 +699,19 @@ impl RoomSession { log::warn!("Received RPC request with null caller identity"); return Ok(()); } - self.local_participant - .handle_incoming_rpc_request( - caller_identity.unwrap(), - request_id, - method, - payload, - response_timeout, - version, - ) - .await; + let local_participant = self.local_participant.clone(); + livekit_runtime::spawn(async move { + local_participant + .handle_incoming_rpc_request( + caller_identity.unwrap(), + request_id, + method, + payload, + response_timeout, + version, + ) + .await; + }); } EngineEvent::RpcResponse { request_id, payload, error } => { self.local_participant.handle_incoming_rpc_response(request_id, payload, error); From 25dbc651beb774ed336d3eb95a31e4881fdd4deb Mon Sep 17 00:00:00 2001 From: Daisuke Murase Date: Fri, 3 Jan 2025 14:42:58 -0800 Subject: [PATCH 120/274] bump livekit-ffi to 0.12.5 (#541) * bump ffi * bump livekit as well --- .nanpa/bump-ffi.kdl | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .nanpa/bump-ffi.kdl diff --git a/.nanpa/bump-ffi.kdl b/.nanpa/bump-ffi.kdl new file mode 100644 index 000000000..13fdcda9e --- /dev/null +++ b/.nanpa/bump-ffi.kdl @@ -0,0 +1,2 @@ +patch type="added" package="livekit-ffi" "Fix deadlock issue in nested RPC calls." +patch type="added" package="livekit" "bump" From e0846c2ff34de65e41ff6ab86a3400069fdb11e5 Mon Sep 17 00:00:00 2001 From: "ilo-nanpa[bot]" <174912985+ilo-nanpa[bot]@users.noreply.github.com> Date: Sat, 4 Jan 2025 00:21:26 +0000 Subject: [PATCH 121/274] nanpa: bump --- .nanpa/bump-ffi.kdl | 2 -- .nanpa/rpc-deadlock.kdl | 1 - Cargo.toml | 4 ++-- livekit-ffi/.nanparc | 2 +- livekit-ffi/CHANGELOG.md | 6 ++++++ livekit-ffi/Cargo.toml | 2 +- livekit/.nanparc | 2 +- livekit/CHANGELOG.md | 10 ++++++++++ livekit/Cargo.toml | 2 +- 9 files changed, 22 insertions(+), 9 deletions(-) delete mode 100644 .nanpa/bump-ffi.kdl delete mode 100644 .nanpa/rpc-deadlock.kdl diff --git a/.nanpa/bump-ffi.kdl b/.nanpa/bump-ffi.kdl deleted file mode 100644 index 13fdcda9e..000000000 --- a/.nanpa/bump-ffi.kdl +++ /dev/null @@ -1,2 +0,0 @@ -patch type="added" package="livekit-ffi" "Fix deadlock issue in nested RPC calls." -patch type="added" package="livekit" "bump" diff --git a/.nanpa/rpc-deadlock.kdl b/.nanpa/rpc-deadlock.kdl deleted file mode 100644 index 35f6dbe7a..000000000 --- a/.nanpa/rpc-deadlock.kdl +++ /dev/null @@ -1 +0,0 @@ -patch type="fixed" "Fixed deadlock with nested RPC calls" package="livekit" diff --git a/Cargo.toml b/Cargo.toml index 60819ca90..04e18189c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,10 +19,10 @@ imgproc = { version = "0.3.12", path = "imgproc" } yuv-sys = { version = "0.3.7", path = "yuv-sys" } libwebrtc = { version = "0.3.8", path = "libwebrtc" } livekit-api = { version = "0.4.1", path = "livekit-api" } -livekit-ffi = { version = "0.12.4", path = "livekit-ffi" } +livekit-ffi = { version = "0.12.5", path = "livekit-ffi" } livekit-protocol = { version = "0.3.6", path = "livekit-protocol" } livekit-runtime = { version = "0.3.1", path = "livekit-runtime" } -livekit = { version = "0.7.1", path = "livekit" } +livekit = { version = "0.7.2", path = "livekit" } soxr-sys = { version = "0.1.0", path = "soxr-sys" } webrtc-sys-build = { version = "0.3.6", path = "webrtc-sys/build" } webrtc-sys = { version = "0.3.6", path = "webrtc-sys" } diff --git a/livekit-ffi/.nanparc b/livekit-ffi/.nanparc index bfb75eb65..8214b3d1e 100644 --- a/livekit-ffi/.nanparc +++ b/livekit-ffi/.nanparc @@ -1,2 +1,2 @@ -version 0.12.4 +version 0.12.5 language rust diff --git a/livekit-ffi/CHANGELOG.md b/livekit-ffi/CHANGELOG.md index 434f26950..a67a64744 100644 --- a/livekit-ffi/CHANGELOG.md +++ b/livekit-ffi/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [0.12.5] - 2025-01-04 + +### Added + +- Fix deadlock issue in nested RPC calls. + ## [0.12.4] - 2024-12-22 ### Added diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index a2bfd9ccc..3af6e6bf0 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.12.4" +version = "0.12.5" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" diff --git a/livekit/.nanparc b/livekit/.nanparc index 29c21da5f..4d2203e27 100644 --- a/livekit/.nanparc +++ b/livekit/.nanparc @@ -1,2 +1,2 @@ -version 0.7.1 +version 0.7.2 language rust diff --git a/livekit/CHANGELOG.md b/livekit/CHANGELOG.md index 8e6a78304..36c57cfa4 100644 --- a/livekit/CHANGELOG.md +++ b/livekit/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## [0.7.2] - 2025-01-04 + +### Added + +- bump + +### Fixed + +- Fixed deadlock with nested RPC calls + ## [0.7.1] - 2024-12-14 ### Added diff --git a/livekit/Cargo.toml b/livekit/Cargo.toml index a59ff58c3..176be38d2 100644 --- a/livekit/Cargo.toml +++ b/livekit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit" -version = "0.7.1" +version = "0.7.2" edition = "2021" license = "Apache-2.0" description = "Rust Client SDK for LiveKit" From 32a582c09f86a32d52928a4d438f3c5eb43ded88 Mon Sep 17 00:00:00 2001 From: Daisuke Murase Date: Mon, 6 Jan 2025 15:39:29 -0800 Subject: [PATCH 122/274] Close track FFI handles automatically when a FfiRoom is closed. (#539) * trying to dispose associated stream handles when a room is closed * update Cargo.lock * remove debug log * store empty handle for existing ffi clients * impl FfiHandle for () * add nanpa changeset --- .nanpa/free-stream-handles-when-room-is-closed.kdl | 1 + Cargo.lock | 14 +++++++------- livekit-ffi/src/server/mod.rs | 5 +++-- livekit-ffi/src/server/requests.rs | 2 +- livekit-ffi/src/server/room.rs | 10 +++++++++- 5 files changed, 21 insertions(+), 11 deletions(-) create mode 100644 .nanpa/free-stream-handles-when-room-is-closed.kdl diff --git a/.nanpa/free-stream-handles-when-room-is-closed.kdl b/.nanpa/free-stream-handles-when-room-is-closed.kdl new file mode 100644 index 000000000..527848437 --- /dev/null +++ b/.nanpa/free-stream-handles-when-room-is-closed.kdl @@ -0,0 +1 @@ +patch type="fixed" package="livekit-ffi" "Automatically close audio/video stream handles when the associated room is closed" \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index e915bba8a..7fc649ecd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1321,7 +1321,7 @@ dependencies = [ [[package]] name = "imgproc" -version = "0.3.11" +version = "0.3.12" dependencies = [ "yuv-sys", ] @@ -1537,7 +1537,7 @@ dependencies = [ [[package]] name = "libwebrtc" -version = "0.3.7" +version = "0.3.8" dependencies = [ "cxx", "env_logger", @@ -1593,7 +1593,7 @@ checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "livekit" -version = "0.7.0" +version = "0.7.1" dependencies = [ "chrono", "futures-util", @@ -1641,7 +1641,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.12.3" +version = "0.12.4" dependencies = [ "console-subscriber", "dashmap", @@ -3293,7 +3293,7 @@ checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" [[package]] name = "webrtc-sys" -version = "0.3.5" +version = "0.3.6" dependencies = [ "cc", "cxx", @@ -3306,7 +3306,7 @@ dependencies = [ [[package]] name = "webrtc-sys-build" -version = "0.3.5" +version = "0.3.6" dependencies = [ "fs2", "regex", @@ -3578,7 +3578,7 @@ dependencies = [ [[package]] name = "yuv-sys" -version = "0.3.6" +version = "0.3.7" dependencies = [ "bindgen", "cc", diff --git a/livekit-ffi/src/server/mod.rs b/livekit-ffi/src/server/mod.rs index cd015b5f8..9c0f7dc82 100644 --- a/livekit-ffi/src/server/mod.rs +++ b/livekit-ffi/src/server/mod.rs @@ -70,6 +70,7 @@ impl FfiHandle for Arc> {} impl FfiHandle for AudioFrame<'static> {} impl FfiHandle for BoxVideoBuffer {} impl FfiHandle for Box<[u8]> {} +impl FfiHandle for () {} pub struct FfiServer { /// Store all Ffi handles inside an HashMap, if this isn't efficient enough @@ -133,7 +134,7 @@ impl FfiServer { log::info!("initializing ffi server v{}", env!("CARGO_PKG_VERSION")); // TODO: Move this log } - pub async fn dispose(&self) { + pub async fn dispose(&'static self) { self.logger.set_capture_logs(false); log::info!("disposing ffi server"); @@ -146,7 +147,7 @@ impl FfiServer { } for room in rooms { - room.close().await; + room.close(self).await; } // Drop all handles diff --git a/livekit-ffi/src/server/requests.rs b/livekit-ffi/src/server/requests.rs index f14ab0d75..327402893 100644 --- a/livekit-ffi/src/server/requests.rs +++ b/livekit-ffi/src/server/requests.rs @@ -67,7 +67,7 @@ fn on_disconnect( let ffi_room = server.retrieve_handle::(disconnect.room_handle).unwrap().clone(); - ffi_room.close().await; + ffi_room.close(server).await; let _ = server.send_event(proto::ffi_event::Message::Disconnect(proto::DisconnectCallback { diff --git a/livekit-ffi/src/server/room.rs b/livekit-ffi/src/server/room.rs index ca0d26ac8..fe6be4110 100644 --- a/livekit-ffi/src/server/room.rs +++ b/livekit-ffi/src/server/room.rs @@ -266,7 +266,15 @@ impl FfiRoom { } /// Close the room and stop the tasks - pub async fn close(&self) { + pub async fn close(&self, server: &'static FfiServer) { + // drop associated track handles + for (_, &handle) in self.inner.track_handle_lookup.lock().iter() { + if server.drop_handle(handle) { + // Store an empty handle for the FFI client that assumes a handle exists for this id. + server.store_handle(handle, ()); + } + } + let _ = self.inner.room.close().await; let handle = self.handle.lock().await.take(); From c6132e864d33bd1def502693aa758eb157bdd7fc Mon Sep 17 00:00:00 2001 From: "ilo-nanpa[bot]" <174912985+ilo-nanpa[bot]@users.noreply.github.com> Date: Tue, 7 Jan 2025 02:18:15 +0000 Subject: [PATCH 123/274] nanpa: bump --- .nanpa/free-stream-handles-when-room-is-closed.kdl | 1 - Cargo.toml | 2 +- livekit-ffi/.nanparc | 2 +- livekit-ffi/CHANGELOG.md | 6 ++++++ livekit-ffi/Cargo.toml | 2 +- 5 files changed, 9 insertions(+), 4 deletions(-) delete mode 100644 .nanpa/free-stream-handles-when-room-is-closed.kdl diff --git a/.nanpa/free-stream-handles-when-room-is-closed.kdl b/.nanpa/free-stream-handles-when-room-is-closed.kdl deleted file mode 100644 index 527848437..000000000 --- a/.nanpa/free-stream-handles-when-room-is-closed.kdl +++ /dev/null @@ -1 +0,0 @@ -patch type="fixed" package="livekit-ffi" "Automatically close audio/video stream handles when the associated room is closed" \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 04e18189c..837cfaf64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ imgproc = { version = "0.3.12", path = "imgproc" } yuv-sys = { version = "0.3.7", path = "yuv-sys" } libwebrtc = { version = "0.3.8", path = "libwebrtc" } livekit-api = { version = "0.4.1", path = "livekit-api" } -livekit-ffi = { version = "0.12.5", path = "livekit-ffi" } +livekit-ffi = { version = "0.12.6", path = "livekit-ffi" } livekit-protocol = { version = "0.3.6", path = "livekit-protocol" } livekit-runtime = { version = "0.3.1", path = "livekit-runtime" } livekit = { version = "0.7.2", path = "livekit" } diff --git a/livekit-ffi/.nanparc b/livekit-ffi/.nanparc index 8214b3d1e..71a6ca890 100644 --- a/livekit-ffi/.nanparc +++ b/livekit-ffi/.nanparc @@ -1,2 +1,2 @@ -version 0.12.5 +version 0.12.6 language rust diff --git a/livekit-ffi/CHANGELOG.md b/livekit-ffi/CHANGELOG.md index a67a64744..4968dd87d 100644 --- a/livekit-ffi/CHANGELOG.md +++ b/livekit-ffi/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [0.12.6] - 2025-01-07 + +### Fixed + +- Automatically close audio/video stream handles when the associated room is closed + ## [0.12.5] - 2025-01-04 ### Added diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 3af6e6bf0..634bf7d1d 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.12.5" +version = "0.12.6" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" From 5047dae4066e219e4cc33699fbaba5abd52bda28 Mon Sep 17 00:00:00 2001 From: lukasIO Date: Wed, 8 Jan 2025 09:25:27 +0100 Subject: [PATCH 124/274] DataStream sending support and recv fixes (#533) * sending support and recv fixes * fix duplicated function * fixes * fix compilation errors * add error callback * fix forwarding issues * fix optional params * fix conversion * fix optional fields * required/optional changes * remove totalchunks * fix none --- livekit-ffi/protocol/ffi.proto | 11 ++ livekit-ffi/protocol/room.proto | 66 +++++++-- livekit-ffi/src/conversion/room.rs | 60 ++++++++- livekit-ffi/src/livekit.proto.rs | 127 ++++++++++++++---- livekit-ffi/src/server/requests.rs | 28 ++++ livekit-ffi/src/server/room.rs | 70 +++++++++- livekit/src/room/mod.rs | 26 ++-- .../src/room/participant/local_participant.rs | 13 ++ livekit/src/rtc_engine/mod.rs | 14 +- livekit/src/rtc_engine/rtc_session.rs | 16 ++- 10 files changed, 366 insertions(+), 65 deletions(-) diff --git a/livekit-ffi/protocol/ffi.proto b/livekit-ffi/protocol/ffi.proto index 4ff6df53c..8c715bbf1 100644 --- a/livekit-ffi/protocol/ffi.proto +++ b/livekit-ffi/protocol/ffi.proto @@ -111,6 +111,11 @@ message FfiRequest { // Track Publication EnableRemoteTrackPublicationRequest enable_remote_track_publication = 42; UpdateRemoteTrackPublicationDimensionRequest update_remote_track_publication_dimension = 43; + + // Data Streams + SendStreamHeaderRequest send_stream_header = 44; + SendStreamChunkRequest send_stream_chunk = 45; + } } @@ -168,6 +173,10 @@ message FfiResponse { // Track Publication EnableRemoteTrackPublicationResponse enable_remote_track_publication = 41; UpdateRemoteTrackPublicationDimensionResponse update_remote_track_publication_dimension = 42; + + // Data Streams + SendStreamHeaderResponse send_stream_header = 43; + SendStreamChunkResponse send_stream_chunk = 44; } } @@ -199,6 +208,8 @@ message FfiEvent { SendChatMessageCallback chat_message = 22; PerformRpcCallback perform_rpc = 23; RpcMethodInvocationEvent rpc_method_invocation = 24; + SendStreamHeaderCallback send_stream_header = 25; + SendStreamChunkCallback send_stream_chunk = 26; } } diff --git a/livekit-ffi/protocol/room.proto b/livekit-ffi/protocol/room.proto index c6f595699..90f14089b 100644 --- a/livekit-ffi/protocol/room.proto +++ b/livekit-ffi/protocol/room.proto @@ -367,8 +367,8 @@ message RoomEvent { DataPacketReceived data_packet_received = 27; TranscriptionReceived transcription_received = 28; ChatMessageReceived chat_message = 29; - DataStream.Header stream_header = 30; - DataStream.Chunk stream_chunk = 31; + DataStreamHeaderReceived stream_header_received = 30; + DataStreamChunkReceived stream_chunk_received = 31; } } @@ -541,10 +541,10 @@ message DataStream { // header properties specific to text streams message TextHeader { required OperationType operation_type = 1; - required int32 version = 2; // Optional: Version for updates/edits - required string reply_to_stream_id = 3; // Optional: Reply to specific message + optional int32 version = 2; // Optional: Version for updates/edits + optional string reply_to_stream_id = 3; // Optional: Reply to specific message repeated string attached_stream_ids = 4; // file attachments for text streams - required bool generated = 5; // true if the text has been generated by an agent from a participant's audio transcription + optional bool generated = 5; // true if the text has been generated by an agent from a participant's audio transcription } @@ -557,16 +557,15 @@ message DataStream { message Header { required string stream_id = 1; // unique identifier for this data stream required int64 timestamp = 2; // using int64 for Unix timestamp - required string topic = 3; - required string mime_type = 4; + required string mime_type = 3; + required string topic = 4; optional uint64 total_length = 5; // only populated for finite streams, if it's a stream of unknown size this stays empty - optional uint64 total_chunks = 6; // only populated for finite streams, if it's a stream of unknown size this stays empty - map extensions = 7; // user defined extensions map that can carry additional info + map extensions = 6; // user defined extensions map that can carry additional info // oneof to choose between specific header types oneof content_header { - TextHeader text_header = 8; - FileHeader file_header = 9; + TextHeader text_header = 7; + FileHeader file_header = 8; } } @@ -574,9 +573,50 @@ message DataStream { required string stream_id = 1; // unique identifier for this data stream to map it to the correct header required uint64 chunk_index = 2; required bytes content = 3; // content as binary (bytes) - required bool complete = 4; // true only if this is the last chunk of this stream - can also be sent with empty content - required int32 version = 5; // a version indicating that this chunk_index has been retroactively modified and the original one needs to be replaced + optional bool complete = 4; // true only if this is the last chunk of this stream - can also be sent with empty content + optional int32 version = 5; // a version indicating that this chunk_index has been retroactively modified and the original one needs to be replaced optional bytes iv = 6; // optional, initialization vector for AES-GCM encryption } } +message DataStreamHeaderReceived { + required string participant_identity = 1; + required DataStream.Header header = 2; +} + +message DataStreamChunkReceived { + required string participant_identity = 1; + required DataStream.Chunk chunk = 2; +} + +message SendStreamHeaderRequest { + required uint64 local_participant_handle = 1; + required DataStream.Header header = 2; + repeated string destination_identities = 3; + optional string sender_identity = 4; +} + +message SendStreamChunkRequest { + required uint64 local_participant_handle = 1; + required DataStream.Chunk chunk = 2; + repeated string destination_identities = 3; + optional string sender_identity = 4; +} + +message SendStreamHeaderResponse { + required uint64 async_id = 1; +} + +message SendStreamChunkResponse { + required uint64 async_id = 1; +} + +message SendStreamHeaderCallback { + required uint64 async_id = 1; + optional string error = 2; +} + +message SendStreamChunkCallback { + required uint64 async_id = 1; + optional string error = 2; +} \ No newline at end of file diff --git a/livekit-ffi/src/conversion/room.rs b/livekit-ffi/src/conversion/room.rs index 521c5c8c5..42479342f 100644 --- a/livekit-ffi/src/conversion/room.rs +++ b/livekit-ffi/src/conversion/room.rs @@ -289,10 +289,10 @@ impl From for proto::data_stream::Header Some(proto::data_stream::header::ContentHeader::TextHeader( proto::data_stream::TextHeader { operation_type: text_header.operation_type, - version: text_header.version, - reply_to_stream_id: text_header.reply_to_stream_id, + version: Some(text_header.version), + reply_to_stream_id: Some(text_header.reply_to_stream_id), attached_stream_ids: text_header.attached_stream_ids, - generated: text_header.generated, + generated: Some(text_header.generated), }, )) } @@ -309,7 +309,6 @@ impl From for proto::data_stream::Header timestamp: msg.timestamp, topic: msg.topic, mime_type: msg.mime_type, - total_chunks: msg.total_chunks, total_length: msg.total_length, extensions: msg.extensions, content_header, @@ -317,14 +316,63 @@ impl From for proto::data_stream::Header } } +impl From for livekit_protocol::data_stream::Header { + fn from(msg: proto::data_stream::Header) -> Self { + let content_header = match msg.content_header { + Some(proto::data_stream::header::ContentHeader::TextHeader(text_header)) => { + Some(livekit_protocol::data_stream::header::ContentHeader::TextHeader( + livekit_protocol::data_stream::TextHeader { + operation_type: text_header.operation_type, + version: text_header.version.unwrap_or_default(), + reply_to_stream_id: text_header.reply_to_stream_id.unwrap_or_default(), + attached_stream_ids: text_header.attached_stream_ids, + generated: text_header.generated.unwrap_or(false), + }, + )) + } + Some(proto::data_stream::header::ContentHeader::FileHeader(file_header)) => { + Some(livekit_protocol::data_stream::header::ContentHeader::FileHeader( + livekit_protocol::data_stream::FileHeader { file_name: file_header.file_name }, + )) + } + None => None, + }; + + livekit_protocol::data_stream::Header { + stream_id: msg.stream_id, + timestamp: msg.timestamp, + topic: msg.topic, + mime_type: msg.mime_type, + total_length: msg.total_length, + total_chunks: None, + extensions: msg.extensions, + content_header, + encryption_type: 0, + } + } +} + impl From for proto::data_stream::Chunk { fn from(msg: livekit_protocol::data_stream::Chunk) -> Self { proto::data_stream::Chunk { stream_id: msg.stream_id, content: msg.content, - complete: msg.complete, + complete: Some(msg.complete), + chunk_index: msg.chunk_index, + version: Some(msg.version), + iv: msg.iv, + } + } +} + +impl From for livekit_protocol::data_stream::Chunk { + fn from(msg: proto::data_stream::Chunk) -> Self { + livekit_protocol::data_stream::Chunk { + stream_id: msg.stream_id, + content: msg.content, + complete: msg.complete.unwrap_or(false), chunk_index: msg.chunk_index, - version: msg.version, + version: msg.version.unwrap_or(0), iv: msg.iv, } } diff --git a/livekit-ffi/src/livekit.proto.rs b/livekit-ffi/src/livekit.proto.rs index c45f678c7..13b495481 100644 --- a/livekit-ffi/src/livekit.proto.rs +++ b/livekit-ffi/src/livekit.proto.rs @@ -2702,9 +2702,9 @@ pub mod room_event { #[prost(message, tag="29")] ChatMessage(super::ChatMessageReceived), #[prost(message, tag="30")] - StreamHeader(super::data_stream::Header), + StreamHeaderReceived(super::DataStreamHeaderReceived), #[prost(message, tag="31")] - StreamChunk(super::data_stream::Chunk), + StreamChunkReceived(super::DataStreamChunkReceived), } } #[allow(clippy::derive_partial_eq_without_eq)] @@ -2992,17 +2992,17 @@ pub mod data_stream { #[prost(enumeration="OperationType", required, tag="1")] pub operation_type: i32, /// Optional: Version for updates/edits - #[prost(int32, required, tag="2")] - pub version: i32, + #[prost(int32, optional, tag="2")] + pub version: ::core::option::Option, /// Optional: Reply to specific message - #[prost(string, required, tag="3")] - pub reply_to_stream_id: ::prost::alloc::string::String, + #[prost(string, optional, tag="3")] + pub reply_to_stream_id: ::core::option::Option<::prost::alloc::string::String>, /// file attachments for text streams #[prost(string, repeated, tag="4")] pub attached_stream_ids: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, /// true if the text has been generated by an agent from a participant's audio transcription - #[prost(bool, required, tag="5")] - pub generated: bool, + #[prost(bool, optional, tag="5")] + pub generated: ::core::option::Option, } /// header properties specific to file or image streams #[allow(clippy::derive_partial_eq_without_eq)] @@ -3023,20 +3023,17 @@ pub mod data_stream { #[prost(int64, required, tag="2")] pub timestamp: i64, #[prost(string, required, tag="3")] - pub topic: ::prost::alloc::string::String, - #[prost(string, required, tag="4")] pub mime_type: ::prost::alloc::string::String, + #[prost(string, required, tag="4")] + pub topic: ::prost::alloc::string::String, /// only populated for finite streams, if it's a stream of unknown size this stays empty #[prost(uint64, optional, tag="5")] pub total_length: ::core::option::Option, - /// only populated for finite streams, if it's a stream of unknown size this stays empty - #[prost(uint64, optional, tag="6")] - pub total_chunks: ::core::option::Option, /// user defined extensions map that can carry additional info - #[prost(map="string, string", tag="7")] + #[prost(map="string, string", tag="6")] pub extensions: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, /// oneof to choose between specific header types - #[prost(oneof="header::ContentHeader", tags="8, 9")] + #[prost(oneof="header::ContentHeader", tags="7, 8")] pub content_header: ::core::option::Option, } /// Nested message and enum types in `Header`. @@ -3045,9 +3042,9 @@ pub mod data_stream { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum ContentHeader { - #[prost(message, tag="8")] + #[prost(message, tag="7")] TextHeader(super::TextHeader), - #[prost(message, tag="9")] + #[prost(message, tag="8")] FileHeader(super::FileHeader), } } @@ -3063,11 +3060,11 @@ pub mod data_stream { #[prost(bytes="vec", required, tag="3")] pub content: ::prost::alloc::vec::Vec, /// true only if this is the last chunk of this stream - can also be sent with empty content - #[prost(bool, required, tag="4")] - pub complete: bool, + #[prost(bool, optional, tag="4")] + pub complete: ::core::option::Option, /// a version indicating that this chunk_index has been retroactively modified and the original one needs to be replaced - #[prost(int32, required, tag="5")] - pub version: i32, + #[prost(int32, optional, tag="5")] + pub version: ::core::option::Option, /// optional, initialization vector for AES-GCM encryption #[prost(bytes="vec", optional, tag="6")] pub iv: ::core::option::Option<::prost::alloc::vec::Vec>, @@ -3106,6 +3103,74 @@ pub mod data_stream { } } } +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DataStreamHeaderReceived { + #[prost(string, required, tag="1")] + pub participant_identity: ::prost::alloc::string::String, + #[prost(message, required, tag="2")] + pub header: data_stream::Header, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DataStreamChunkReceived { + #[prost(string, required, tag="1")] + pub participant_identity: ::prost::alloc::string::String, + #[prost(message, required, tag="2")] + pub chunk: data_stream::Chunk, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SendStreamHeaderRequest { + #[prost(uint64, required, tag="1")] + pub local_participant_handle: u64, + #[prost(message, required, tag="2")] + pub header: data_stream::Header, + #[prost(string, repeated, tag="3")] + pub destination_identities: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(string, optional, tag="4")] + pub sender_identity: ::core::option::Option<::prost::alloc::string::String>, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SendStreamChunkRequest { + #[prost(uint64, required, tag="1")] + pub local_participant_handle: u64, + #[prost(message, required, tag="2")] + pub chunk: data_stream::Chunk, + #[prost(string, repeated, tag="3")] + pub destination_identities: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(string, optional, tag="4")] + pub sender_identity: ::core::option::Option<::prost::alloc::string::String>, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SendStreamHeaderResponse { + #[prost(uint64, required, tag="1")] + pub async_id: u64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SendStreamChunkResponse { + #[prost(uint64, required, tag="1")] + pub async_id: u64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SendStreamHeaderCallback { + #[prost(uint64, required, tag="1")] + pub async_id: u64, + #[prost(string, optional, tag="2")] + pub error: ::core::option::Option<::prost::alloc::string::String>, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SendStreamChunkCallback { + #[prost(uint64, required, tag="1")] + pub async_id: u64, + #[prost(string, optional, tag="2")] + pub error: ::core::option::Option<::prost::alloc::string::String>, +} #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] pub enum IceTransportType { @@ -3878,7 +3943,7 @@ pub struct RpcMethodInvocationEvent { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiRequest { - #[prost(oneof="ffi_request::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43")] + #[prost(oneof="ffi_request::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45")] pub message: ::core::option::Option, } /// Nested message and enum types in `FfiRequest`. @@ -3976,13 +4041,18 @@ pub mod ffi_request { EnableRemoteTrackPublication(super::EnableRemoteTrackPublicationRequest), #[prost(message, tag="43")] UpdateRemoteTrackPublicationDimension(super::UpdateRemoteTrackPublicationDimensionRequest), + /// Data Streams + #[prost(message, tag="44")] + SendStreamHeader(super::SendStreamHeaderRequest), + #[prost(message, tag="45")] + SendStreamChunk(super::SendStreamChunkRequest), } } /// This is the output of livekit_ffi_request function. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiResponse { - #[prost(oneof="ffi_response::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42")] + #[prost(oneof="ffi_response::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44")] pub message: ::core::option::Option, } /// Nested message and enum types in `FfiResponse`. @@ -4078,6 +4148,11 @@ pub mod ffi_response { EnableRemoteTrackPublication(super::EnableRemoteTrackPublicationResponse), #[prost(message, tag="42")] UpdateRemoteTrackPublicationDimension(super::UpdateRemoteTrackPublicationDimensionResponse), + /// Data Streams + #[prost(message, tag="43")] + SendStreamHeader(super::SendStreamHeaderResponse), + #[prost(message, tag="44")] + SendStreamChunk(super::SendStreamChunkResponse), } } /// To minimize complexity, participant events are not included in the protocol. @@ -4086,7 +4161,7 @@ pub mod ffi_response { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiEvent { - #[prost(oneof="ffi_event::Message", tags="1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24")] + #[prost(oneof="ffi_event::Message", tags="1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26")] pub message: ::core::option::Option, } /// Nested message and enum types in `FfiEvent`. @@ -4140,6 +4215,10 @@ pub mod ffi_event { PerformRpc(super::PerformRpcCallback), #[prost(message, tag="24")] RpcMethodInvocation(super::RpcMethodInvocationEvent), + #[prost(message, tag="25")] + SendStreamHeader(super::SendStreamHeaderCallback), + #[prost(message, tag="26")] + SendStreamChunk(super::SendStreamChunkCallback), } } /// Stop all rooms synchronously (Do we need async here?). diff --git a/livekit-ffi/src/server/requests.rs b/livekit-ffi/src/server/requests.rs index 327402893..16739361c 100644 --- a/livekit-ffi/src/server/requests.rs +++ b/livekit-ffi/src/server/requests.rs @@ -236,6 +236,28 @@ fn on_edit_chat_message( Ok(ffi_participant.room.edit_chat_message(server, edit_chat_message)) } +fn on_send_stream_header( + server: &'static FfiServer, + stream_header_message: proto::SendStreamHeaderRequest, +) -> FfiResult { + let ffi_participant = server + .retrieve_handle::(stream_header_message.local_participant_handle)? + .clone(); + + Ok(ffi_participant.room.send_stream_header(server, stream_header_message)) +} + +fn on_send_stream_chunk( + server: &'static FfiServer, + stream_chunk_message: proto::SendStreamChunkRequest, +) -> FfiResult { + let ffi_participant = server + .retrieve_handle::(stream_chunk_message.local_participant_handle)? + .clone(); + + Ok(ffi_participant.room.send_stream_chunk(server, stream_chunk_message)) +} + /// Create a new video track from a source fn on_create_video_track( server: &'static FfiServer, @@ -1035,6 +1057,12 @@ pub fn handle_request( on_update_remote_track_publication_dimension(server, request)?, ) } + proto::ffi_request::Message::SendStreamHeader(request) => { + proto::ffi_response::Message::SendStreamHeader(on_send_stream_header(server, request)?) + } + proto::ffi_request::Message::SendStreamChunk(request) => { + proto::ffi_response::Message::SendStreamChunk(on_send_stream_chunk(server, request)?) + } }); Ok(res) diff --git a/livekit-ffi/src/server/room.rs b/livekit-ffi/src/server/room.rs index fe6be4110..75fbfa769 100644 --- a/livekit-ffi/src/server/room.rs +++ b/livekit-ffi/src/server/room.rs @@ -18,6 +18,7 @@ use std::{collections::HashSet, slice, sync::Arc}; use livekit::prelude::*; use livekit::ChatMessage; +use livekit_protocol as lk_proto; use parking_lot::Mutex; use tokio::sync::{broadcast, mpsc, oneshot, Mutex as AsyncMutex}; use tokio::task::JoinHandle; @@ -674,6 +675,63 @@ impl RoomInner { proto::SendChatMessageResponse { async_id } } + pub fn send_stream_header( + self: &Arc, + server: &'static FfiServer, + send_stream_header: proto::SendStreamHeaderRequest, + ) -> proto::SendStreamHeaderResponse { + let packet = lk_proto::DataPacket { + kind: proto::DataPacketKind::KindReliable.into(), + participant_identity: send_stream_header.sender_identity.unwrap(), + destination_identities: send_stream_header.destination_identities, + value: livekit_protocol::data_packet::Value::StreamHeader( + send_stream_header.header.into(), + ) + .into(), + }; + let async_id = server.next_id(); + let inner = self.clone(); + let handle = server.async_runtime.spawn(async move { + let res = inner.room.local_participant().publish_raw_data(packet, true).await; + let cb = proto::SendStreamHeaderCallback { + async_id, + error: res.err().map(|e| e.to_string()), + }; + let _ = server.send_event(proto::ffi_event::Message::SendStreamHeader(cb)); + }); + server.watch_panic(handle); + proto::SendStreamHeaderResponse { async_id } + } + + pub fn send_stream_chunk( + self: &Arc, + server: &'static FfiServer, + send_stream_chunk: proto::SendStreamChunkRequest, + ) -> proto::SendStreamChunkResponse { + let packet = lk_proto::DataPacket { + kind: proto::DataPacketKind::KindReliable.into(), + participant_identity: send_stream_chunk.sender_identity.unwrap(), + destination_identities: send_stream_chunk.destination_identities, + value: livekit_protocol::data_packet::Value::StreamChunk( + send_stream_chunk.chunk.into(), + ) + .into(), + }; + let async_id = server.next_id(); + let inner = self.clone(); + let handle = server.async_runtime.spawn(async move { + let res: Result<(), RoomError> = + inner.room.local_participant().publish_raw_data(packet, true).await; + let cb = proto::SendStreamChunkCallback { + async_id, + error: res.err().map(|e| e.to_string()), + }; + let _ = server.send_event(proto::ffi_event::Message::SendStreamChunk(cb)); + }); + server.watch_panic(handle); + proto::SendStreamChunkResponse { async_id } + } + pub fn store_rpc_method_invocation_waiter( &self, invocation_id: u64, @@ -1145,11 +1203,15 @@ async fn forward_event( state: proto::EncryptionState::from(state).into(), })); } - RoomEvent::StreamHeaderReceived { header } => { - let _ = send_event(proto::room_event::Message::StreamHeader(header.into())); + RoomEvent::StreamHeaderReceived { header, participant_identity } => { + let _ = send_event(proto::room_event::Message::StreamHeaderReceived( + proto::DataStreamHeaderReceived { header: header.into(), participant_identity }, + )); } - RoomEvent::StreamChunkReceived { chunk } => { - let _ = send_event(proto::room_event::Message::StreamChunk(chunk.into())); + RoomEvent::StreamChunkReceived { chunk, participant_identity } => { + let _ = send_event(proto::room_event::Message::StreamChunkReceived( + proto::DataStreamChunkReceived { chunk: chunk.into(), participant_identity }, + )); } _ => { log::warn!("unhandled room event: {:?}", event); diff --git a/livekit/src/room/mod.rs b/livekit/src/room/mod.rs index 5653002a1..051b56f05 100644 --- a/livekit/src/room/mod.rs +++ b/livekit/src/room/mod.rs @@ -170,9 +170,11 @@ pub enum RoomEvent { }, StreamHeaderReceived { header: proto::data_stream::Header, + participant_identity: String, }, StreamChunkReceived { chunk: proto::data_stream::Chunk, + participant_identity: String, }, E2eeStateChanged { participant: Participant, @@ -726,11 +728,11 @@ impl RoomSession { EngineEvent::LocalTrackSubscribed { track_sid } => { self.handle_track_subscribed(track_sid) } - EngineEvent::DataStreamHeader { header } => { - self.handle_data_stream_header(header); + EngineEvent::DataStreamHeader { header, participant_identity } => { + self.handle_data_stream_header(header, participant_identity); } - EngineEvent::DataStreamChunk { chunk } => { - self.handle_data_stream_chunk(chunk); + EngineEvent::DataStreamChunk { chunk, participant_identity } => { + self.handle_data_stream_chunk(chunk, participant_identity); } _ => {} } @@ -1242,13 +1244,21 @@ impl RoomSession { }); } - fn handle_data_stream_header(&self, header: proto::data_stream::Header) { - let event = RoomEvent::StreamHeaderReceived { header }; + fn handle_data_stream_header( + &self, + header: proto::data_stream::Header, + participant_identity: String, + ) { + let event = RoomEvent::StreamHeaderReceived { header, participant_identity }; self.dispatcher.dispatch(&event); } - fn handle_data_stream_chunk(&self, chunk: proto::data_stream::Chunk) { - let event = RoomEvent::StreamChunkReceived { chunk }; + fn handle_data_stream_chunk( + &self, + chunk: proto::data_stream::Chunk, + participant_identity: String, + ) { + let event = RoomEvent::StreamChunkReceived { chunk, participant_identity }; self.dispatcher.dispatch(&event); } diff --git a/livekit/src/room/participant/local_participant.rs b/livekit/src/room/participant/local_participant.rs index 71ff4d775..bbf269c50 100644 --- a/livekit/src/room/participant/local_participant.rs +++ b/livekit/src/room/participant/local_participant.rs @@ -437,6 +437,19 @@ impl LocalParticipant { } } + /** internal */ + pub async fn publish_raw_data( + self, + packet: proto::DataPacket, + reliable: bool, + ) -> RoomResult<()> { + let kind = match reliable { + true => DataPacketKind::Reliable, + false => DataPacketKind::Lossy, + }; + self.inner.rtc_engine.publish_data(&packet, kind).await.map_err(Into::into) + } + pub async fn publish_data(&self, packet: DataPacket) -> RoomResult<()> { let kind = match packet.reliable { true => DataPacketKind::Reliable, diff --git a/livekit/src/rtc_engine/mod.rs b/livekit/src/rtc_engine/mod.rs index d4f5dec97..2cd545215 100644 --- a/livekit/src/rtc_engine/mod.rs +++ b/livekit/src/rtc_engine/mod.rs @@ -161,9 +161,11 @@ pub enum EngineEvent { }, DataStreamHeader { header: proto::data_stream::Header, + participant_identity: String, }, DataStreamChunk { chunk: proto::data_stream::Chunk, + participant_identity: String, }, } @@ -530,11 +532,15 @@ impl EngineInner { SessionEvent::LocalTrackSubscribed { track_sid } => { let _ = self.engine_tx.send(EngineEvent::LocalTrackSubscribed { track_sid }); } - SessionEvent::DataStreamHeader { header } => { - let _ = self.engine_tx.send(EngineEvent::DataStreamHeader { header }); + SessionEvent::DataStreamHeader { header, participant_identity } => { + let _ = self + .engine_tx + .send(EngineEvent::DataStreamHeader { header, participant_identity }); } - SessionEvent::DataStreamChunk { chunk } => { - let _ = self.engine_tx.send(EngineEvent::DataStreamChunk { chunk }); + SessionEvent::DataStreamChunk { chunk, participant_identity } => { + let _ = self + .engine_tx + .send(EngineEvent::DataStreamChunk { chunk, participant_identity }); } } Ok(()) diff --git a/livekit/src/rtc_engine/rtc_session.rs b/livekit/src/rtc_engine/rtc_session.rs index b3cb4f094..91dcbfddb 100644 --- a/livekit/src/rtc_engine/rtc_session.rs +++ b/livekit/src/rtc_engine/rtc_session.rs @@ -137,9 +137,11 @@ pub enum SessionEvent { }, DataStreamHeader { header: proto::data_stream::Header, + participant_identity: String, }, DataStreamChunk { chunk: proto::data_stream::Chunk, + participant_identity: String, }, } @@ -730,14 +732,16 @@ impl SessionInner { }); } proto::data_packet::Value::StreamHeader(message) => { - let _ = self - .emitter - .send(SessionEvent::DataStreamHeader { header: message.clone() }); + let _ = self.emitter.send(SessionEvent::DataStreamHeader { + header: message.clone(), + participant_identity: data.participant_identity.clone(), + }); } proto::data_packet::Value::StreamChunk(message) => { - let _ = self - .emitter - .send(SessionEvent::DataStreamChunk { chunk: message.clone() }); + let _ = self.emitter.send(SessionEvent::DataStreamChunk { + chunk: message.clone(), + participant_identity: data.participant_identity.clone(), + }); } _ => {} } From b2a3a271a3459218b6c446b947a2a06756fbf1d3 Mon Sep 17 00:00:00 2001 From: Daisuke Murase Date: Thu, 16 Jan 2025 09:39:47 -0800 Subject: [PATCH 125/274] Add DataStream Trailer for livekit-ffi (#547) * set submodule livekit-protocol/protocol to v1.31.0 * generate proto.rs for v1.31.0 * add default value for now to newly introduced properties in protocol 1.31.0 * update DataStream Header proto to align with changes in the livekit-protocol * implement data stream tailer in ffi * generated protobuf * add nanpa changeset * make sender_identity required --------- Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- .nanpa/data-stream-trailer.kdl | 2 + Cargo.lock | 4 +- livekit-api/src/services/ingress.rs | 2 + livekit-api/src/services/sip.rs | 39 +- livekit-ffi/protocol/ffi.proto | 10 +- livekit-ffi/protocol/room.proto | 33 +- livekit-ffi/src/conversion/room.rs | 15 +- livekit-ffi/src/livekit.proto.rs | 67 +- livekit-ffi/src/server/requests.rs | 15 + livekit-ffi/src/server/room.rs | 32 +- livekit-protocol/protocol | 2 +- livekit-protocol/src/livekit.rs | 243 +++++- livekit-protocol/src/livekit.serde.rs | 1045 +++++++++++++++++++++++-- 13 files changed, 1380 insertions(+), 129 deletions(-) create mode 100644 .nanpa/data-stream-trailer.kdl diff --git a/.nanpa/data-stream-trailer.kdl b/.nanpa/data-stream-trailer.kdl new file mode 100644 index 000000000..84706c57a --- /dev/null +++ b/.nanpa/data-stream-trailer.kdl @@ -0,0 +1,2 @@ +patch type="changed" package="livekit-protocol" "Update protocol version to v1.31.0" +patch type="added" package="livekit-ffi" "Add DataStream.Trailer support" diff --git a/Cargo.lock b/Cargo.lock index 7fc649ecd..b0532c222 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1593,7 +1593,7 @@ checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "livekit" -version = "0.7.1" +version = "0.7.2" dependencies = [ "chrono", "futures-util", @@ -1641,7 +1641,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.12.4" +version = "0.12.6" dependencies = [ "console-subscriber", "dashmap", diff --git a/livekit-api/src/services/ingress.rs b/livekit-api/src/services/ingress.rs index e46463f82..8e32fa334 100644 --- a/livekit-api/src/services/ingress.rs +++ b/livekit-api/src/services/ingress.rs @@ -93,6 +93,7 @@ impl IngressClient { bypass_transcoding: options.bypass_transcoding, enable_transcoding: options.enable_transcoding, url: options.url, + enabled: Default::default(), // TODO: support this attribute }, self.base .auth_header(VideoGrants { ingress_admin: true, ..Default::default() }, None)?, @@ -121,6 +122,7 @@ impl IngressClient { video: Some(options.video), bypass_transcoding: options.bypass_transcoding, enable_transcoding: options.enable_transcoding, + enabled: Default::default(), // TODO: support this attribute }, self.base .auth_header(VideoGrants { ingress_admin: true, ..Default::default() }, None)?, diff --git a/livekit-api/src/services/sip.rs b/livekit-api/src/services/sip.rs index aca2d11e9..d8a2b982d 100644 --- a/livekit-api/src/services/sip.rs +++ b/livekit-api/src/services/sip.rs @@ -191,6 +191,10 @@ impl SIPClient { krisp_enabled: options.krisp_enabled.unwrap_or(false), max_call_duration: Self::duration_to_proto(options.max_call_duration), ringing_timeout: Self::duration_to_proto(options.ringing_timeout), + + // TODO: support these attributes + include_headers: Default::default(), + media_encryption: Default::default(), }), }, self.base.auth_header( @@ -228,6 +232,10 @@ impl SIPClient { headers: options.headers.unwrap_or_default(), headers_to_attributes: options.headers_to_attributes.unwrap_or_default(), attributes_to_headers: options.attributes_to_headers.unwrap_or_default(), + + // TODO: support these attributes + include_headers: Default::default(), + media_encryption: Default::default(), }), }, self.base.auth_header( @@ -269,7 +277,11 @@ impl SIPClient { .request( SVC, "ListSIPInboundTrunk", - proto::ListSipInboundTrunkRequest {}, + proto::ListSipInboundTrunkRequest { + // TODO: support these attributes + trunk_ids: Default::default(), + numbers: Default::default(), + }, self.base.auth_header( Default::default(), Some(SIPGrants { admin: true, ..Default::default() }), @@ -289,7 +301,11 @@ impl SIPClient { .request( SVC, "ListSIPOutboundTrunk", - proto::ListSipOutboundTrunkRequest {}, + proto::ListSipOutboundTrunkRequest { + // TODO: support these attributes + trunk_ids: Default::default(), + numbers: Default::default(), + }, self.base.auth_header( Default::default(), Some(SIPGrants { admin: true, ..Default::default() }), @@ -332,6 +348,10 @@ impl SIPClient { inbound_numbers: options.allowed_numbers.to_owned(), hide_phone_number: options.hide_phone_number, rule: Some(proto::SipDispatchRule { rule: Some(rule.to_owned()) }), + + // TODO: support these attributes + room_preset: Default::default(), + room_config: Default::default(), }, self.base.auth_header( Default::default(), @@ -351,7 +371,11 @@ impl SIPClient { .request( SVC, "ListSIPDispatchRule", - proto::ListSipDispatchRuleRequest {}, + proto::ListSipDispatchRuleRequest { + // TODO: support these attributes + dispatch_rule_ids: Default::default(), + trunk_ids: Default::default(), + }, self.base.auth_header( Default::default(), Some(SIPGrants { admin: true, ..Default::default() }), @@ -414,7 +438,14 @@ impl SIPClient { hide_phone_number: options.hide_phone_number.unwrap_or(false), max_call_duration: Self::duration_to_proto(options.max_call_duration), ringing_timeout: Self::duration_to_proto(options.ringing_timeout), - enable_krisp: options.enable_krisp.unwrap_or(false), + + // TODO: rename local proto as well + krisp_enabled: options.enable_krisp.unwrap_or(false), + + // TODO: support these attributes + headers: Default::default(), + include_headers: Default::default(), + media_encryption: Default::default(), }, self.base.auth_header( Default::default(), diff --git a/livekit-ffi/protocol/ffi.proto b/livekit-ffi/protocol/ffi.proto index 8c715bbf1..daf69f83f 100644 --- a/livekit-ffi/protocol/ffi.proto +++ b/livekit-ffi/protocol/ffi.proto @@ -115,7 +115,7 @@ message FfiRequest { // Data Streams SendStreamHeaderRequest send_stream_header = 44; SendStreamChunkRequest send_stream_chunk = 45; - + SendStreamTrailerRequest send_stream_trailer = 46; } } @@ -174,9 +174,10 @@ message FfiResponse { EnableRemoteTrackPublicationResponse enable_remote_track_publication = 41; UpdateRemoteTrackPublicationDimensionResponse update_remote_track_publication_dimension = 42; - // Data Streams - SendStreamHeaderResponse send_stream_header = 43; - SendStreamChunkResponse send_stream_chunk = 44; + // Data Streams + SendStreamHeaderResponse send_stream_header = 43; + SendStreamChunkResponse send_stream_chunk = 44; + SendStreamTrailerResponse send_stream_trailer = 45; } } @@ -210,6 +211,7 @@ message FfiEvent { RpcMethodInvocationEvent rpc_method_invocation = 24; SendStreamHeaderCallback send_stream_header = 25; SendStreamChunkCallback send_stream_chunk = 26; + SendStreamTrailerCallback send_stream_trailer = 27; } } diff --git a/livekit-ffi/protocol/room.proto b/livekit-ffi/protocol/room.proto index 90f14089b..f1d68e5c4 100644 --- a/livekit-ffi/protocol/room.proto +++ b/livekit-ffi/protocol/room.proto @@ -573,9 +573,14 @@ message DataStream { required string stream_id = 1; // unique identifier for this data stream to map it to the correct header required uint64 chunk_index = 2; required bytes content = 3; // content as binary (bytes) - optional bool complete = 4; // true only if this is the last chunk of this stream - can also be sent with empty content - optional int32 version = 5; // a version indicating that this chunk_index has been retroactively modified and the original one needs to be replaced - optional bytes iv = 6; // optional, initialization vector for AES-GCM encryption + optional int32 version = 4; // a version indicating that this chunk_index has been retroactively modified and the original one needs to be replaced + optional bytes iv = 5; // optional, initialization vector for AES-GCM encryption + } + + message Trailer { + required string stream_id = 1; // unique identifier for this data stream + required string reason = 2; // reason why the stream was closed (could contain "error" / "interrupted" / empty for expected end) + map extensions = 3; // finalizing updates for the stream, can also include additional insights for errors or endTime for transcription } } @@ -593,14 +598,21 @@ message SendStreamHeaderRequest { required uint64 local_participant_handle = 1; required DataStream.Header header = 2; repeated string destination_identities = 3; - optional string sender_identity = 4; + required string sender_identity = 4; } message SendStreamChunkRequest { required uint64 local_participant_handle = 1; required DataStream.Chunk chunk = 2; repeated string destination_identities = 3; - optional string sender_identity = 4; + required string sender_identity = 4; +} + +message SendStreamTrailerRequest { + required uint64 local_participant_handle = 1; + required DataStream.Trailer trailer = 2; + repeated string destination_identities = 3; + required string sender_identity = 4; } message SendStreamHeaderResponse { @@ -611,6 +623,10 @@ message SendStreamChunkResponse { required uint64 async_id = 1; } +message SendStreamTrailerResponse { + required uint64 async_id = 1; +} + message SendStreamHeaderCallback { required uint64 async_id = 1; optional string error = 2; @@ -619,4 +635,9 @@ message SendStreamHeaderCallback { message SendStreamChunkCallback { required uint64 async_id = 1; optional string error = 2; -} \ No newline at end of file +} + +message SendStreamTrailerCallback { + required uint64 async_id = 1; + optional string error = 2; +} diff --git a/livekit-ffi/src/conversion/room.rs b/livekit-ffi/src/conversion/room.rs index 42479342f..260c280a8 100644 --- a/livekit-ffi/src/conversion/room.rs +++ b/livekit-ffi/src/conversion/room.rs @@ -344,7 +344,6 @@ impl From for livekit_protocol::data_stream::Header topic: msg.topic, mime_type: msg.mime_type, total_length: msg.total_length, - total_chunks: None, extensions: msg.extensions, content_header, encryption_type: 0, @@ -357,7 +356,6 @@ impl From for proto::data_stream::Chunk { proto::data_stream::Chunk { stream_id: msg.stream_id, content: msg.content, - complete: Some(msg.complete), chunk_index: msg.chunk_index, version: Some(msg.version), iv: msg.iv, @@ -370,10 +368,21 @@ impl From for livekit_protocol::data_stream::Chunk { livekit_protocol::data_stream::Chunk { stream_id: msg.stream_id, content: msg.content, - complete: msg.complete.unwrap_or(false), chunk_index: msg.chunk_index, version: msg.version.unwrap_or(0), iv: msg.iv, } } } + +impl From for proto::data_stream::Trailer { + fn from(msg: livekit_protocol::data_stream::Trailer) -> Self { + Self { stream_id: msg.stream_id, reason: msg.reason, extensions: msg.extensions } + } +} + +impl From for livekit_protocol::data_stream::Trailer { + fn from(msg: proto::data_stream::Trailer) -> Self { + Self { stream_id: msg.stream_id, reason: msg.reason, extensions: msg.extensions } + } +} diff --git a/livekit-ffi/src/livekit.proto.rs b/livekit-ffi/src/livekit.proto.rs index 13b495481..f987e3b4f 100644 --- a/livekit-ffi/src/livekit.proto.rs +++ b/livekit-ffi/src/livekit.proto.rs @@ -1,5 +1,4 @@ // @generated -// This file is @generated by prost-build. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FrameCryptor { @@ -3059,16 +3058,26 @@ pub mod data_stream { /// content as binary (bytes) #[prost(bytes="vec", required, tag="3")] pub content: ::prost::alloc::vec::Vec, - /// true only if this is the last chunk of this stream - can also be sent with empty content - #[prost(bool, optional, tag="4")] - pub complete: ::core::option::Option, /// a version indicating that this chunk_index has been retroactively modified and the original one needs to be replaced - #[prost(int32, optional, tag="5")] + #[prost(int32, optional, tag="4")] pub version: ::core::option::Option, /// optional, initialization vector for AES-GCM encryption - #[prost(bytes="vec", optional, tag="6")] + #[prost(bytes="vec", optional, tag="5")] pub iv: ::core::option::Option<::prost::alloc::vec::Vec>, } + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] + pub struct Trailer { + /// unique identifier for this data stream + #[prost(string, required, tag="1")] + pub stream_id: ::prost::alloc::string::String, + /// reason why the stream was closed (could contain "error" / "interrupted" / empty for expected end) + #[prost(string, required, tag="2")] + pub reason: ::prost::alloc::string::String, + /// finalizing updates for the stream, can also include additional insights for errors or endTime for transcription + #[prost(map="string, string", tag="3")] + pub extensions: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + } /// enum for operation types (specific to TextHeader) #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] @@ -3128,8 +3137,8 @@ pub struct SendStreamHeaderRequest { pub header: data_stream::Header, #[prost(string, repeated, tag="3")] pub destination_identities: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, - #[prost(string, optional, tag="4")] - pub sender_identity: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, required, tag="4")] + pub sender_identity: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -3140,8 +3149,20 @@ pub struct SendStreamChunkRequest { pub chunk: data_stream::Chunk, #[prost(string, repeated, tag="3")] pub destination_identities: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, - #[prost(string, optional, tag="4")] - pub sender_identity: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, required, tag="4")] + pub sender_identity: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SendStreamTrailerRequest { + #[prost(uint64, required, tag="1")] + pub local_participant_handle: u64, + #[prost(message, required, tag="2")] + pub trailer: data_stream::Trailer, + #[prost(string, repeated, tag="3")] + pub destination_identities: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(string, required, tag="4")] + pub sender_identity: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -3157,6 +3178,12 @@ pub struct SendStreamChunkResponse { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct SendStreamTrailerResponse { + #[prost(uint64, required, tag="1")] + pub async_id: u64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct SendStreamHeaderCallback { #[prost(uint64, required, tag="1")] pub async_id: u64, @@ -3171,6 +3198,14 @@ pub struct SendStreamChunkCallback { #[prost(string, optional, tag="2")] pub error: ::core::option::Option<::prost::alloc::string::String>, } +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SendStreamTrailerCallback { + #[prost(uint64, required, tag="1")] + pub async_id: u64, + #[prost(string, optional, tag="2")] + pub error: ::core::option::Option<::prost::alloc::string::String>, +} #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] pub enum IceTransportType { @@ -3943,7 +3978,7 @@ pub struct RpcMethodInvocationEvent { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiRequest { - #[prost(oneof="ffi_request::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45")] + #[prost(oneof="ffi_request::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46")] pub message: ::core::option::Option, } /// Nested message and enum types in `FfiRequest`. @@ -4046,13 +4081,15 @@ pub mod ffi_request { SendStreamHeader(super::SendStreamHeaderRequest), #[prost(message, tag="45")] SendStreamChunk(super::SendStreamChunkRequest), + #[prost(message, tag="46")] + SendStreamTrailer(super::SendStreamTrailerRequest), } } /// This is the output of livekit_ffi_request function. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiResponse { - #[prost(oneof="ffi_response::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44")] + #[prost(oneof="ffi_response::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45")] pub message: ::core::option::Option, } /// Nested message and enum types in `FfiResponse`. @@ -4153,6 +4190,8 @@ pub mod ffi_response { SendStreamHeader(super::SendStreamHeaderResponse), #[prost(message, tag="44")] SendStreamChunk(super::SendStreamChunkResponse), + #[prost(message, tag="45")] + SendStreamTrailer(super::SendStreamTrailerResponse), } } /// To minimize complexity, participant events are not included in the protocol. @@ -4161,7 +4200,7 @@ pub mod ffi_response { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiEvent { - #[prost(oneof="ffi_event::Message", tags="1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26")] + #[prost(oneof="ffi_event::Message", tags="1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27")] pub message: ::core::option::Option, } /// Nested message and enum types in `FfiEvent`. @@ -4219,6 +4258,8 @@ pub mod ffi_event { SendStreamHeader(super::SendStreamHeaderCallback), #[prost(message, tag="26")] SendStreamChunk(super::SendStreamChunkCallback), + #[prost(message, tag="27")] + SendStreamTrailer(super::SendStreamTrailerCallback), } } /// Stop all rooms synchronously (Do we need async here?). diff --git a/livekit-ffi/src/server/requests.rs b/livekit-ffi/src/server/requests.rs index 16739361c..a002a67ba 100644 --- a/livekit-ffi/src/server/requests.rs +++ b/livekit-ffi/src/server/requests.rs @@ -258,6 +258,16 @@ fn on_send_stream_chunk( Ok(ffi_participant.room.send_stream_chunk(server, stream_chunk_message)) } +fn on_send_stream_trailer( + server: &'static FfiServer, + stream_trailer_message: proto::SendStreamTrailerRequest, +) -> FfiResult { + let ffi_participant = server + .retrieve_handle::(stream_trailer_message.local_participant_handle)? + .clone(); + Ok(ffi_participant.room.send_stream_trailer(server, stream_trailer_message)) +} + /// Create a new video track from a source fn on_create_video_track( server: &'static FfiServer, @@ -1063,6 +1073,11 @@ pub fn handle_request( proto::ffi_request::Message::SendStreamChunk(request) => { proto::ffi_response::Message::SendStreamChunk(on_send_stream_chunk(server, request)?) } + proto::ffi_request::Message::SendStreamTrailer(request) => { + proto::ffi_response::Message::SendStreamTrailer(on_send_stream_trailer( + server, request, + )?) + } }); Ok(res) diff --git a/livekit-ffi/src/server/room.rs b/livekit-ffi/src/server/room.rs index 75fbfa769..61d32161b 100644 --- a/livekit-ffi/src/server/room.rs +++ b/livekit-ffi/src/server/room.rs @@ -682,7 +682,7 @@ impl RoomInner { ) -> proto::SendStreamHeaderResponse { let packet = lk_proto::DataPacket { kind: proto::DataPacketKind::KindReliable.into(), - participant_identity: send_stream_header.sender_identity.unwrap(), + participant_identity: send_stream_header.sender_identity, destination_identities: send_stream_header.destination_identities, value: livekit_protocol::data_packet::Value::StreamHeader( send_stream_header.header.into(), @@ -710,7 +710,7 @@ impl RoomInner { ) -> proto::SendStreamChunkResponse { let packet = lk_proto::DataPacket { kind: proto::DataPacketKind::KindReliable.into(), - participant_identity: send_stream_chunk.sender_identity.unwrap(), + participant_identity: send_stream_chunk.sender_identity, destination_identities: send_stream_chunk.destination_identities, value: livekit_protocol::data_packet::Value::StreamChunk( send_stream_chunk.chunk.into(), @@ -732,6 +732,34 @@ impl RoomInner { proto::SendStreamChunkResponse { async_id } } + pub fn send_stream_trailer( + self: &Arc, + server: &'static FfiServer, + send_stream_trailer: proto::SendStreamTrailerRequest, + ) -> proto::SendStreamTrailerResponse { + let packet = lk_proto::DataPacket { + kind: proto::DataPacketKind::KindReliable.into(), + participant_identity: send_stream_trailer.sender_identity, + destination_identities: send_stream_trailer.destination_identities, + value: livekit_protocol::data_packet::Value::StreamTrailer( + send_stream_trailer.trailer.into(), + ) + .into(), + }; + let async_id = server.next_id(); + let inner = self.clone(); + let handle = server.async_runtime.spawn(async move { + let res = inner.room.local_participant().publish_raw_data(packet, true).await; + let cb = proto::SendStreamTrailerCallback { + async_id, + error: res.err().map(|e| e.to_string()), + }; + let _ = server.send_event(proto::ffi_event::Message::SendStreamTrailer(cb)); + }); + server.watch_panic(handle); + proto::SendStreamTrailerResponse { async_id } + } + pub fn store_rpc_method_invocation_waiter( &self, invocation_id: u64, diff --git a/livekit-protocol/protocol b/livekit-protocol/protocol index 095606bc8..553f87b84 160000 --- a/livekit-protocol/protocol +++ b/livekit-protocol/protocol @@ -1 +1 @@ -Subproject commit 095606bc8e0e73535c6bf4867645dfff0825f121 +Subproject commit 553f87b849effe55777f80cb93766509868fe4f5 diff --git a/livekit-protocol/src/livekit.rs b/livekit-protocol/src/livekit.rs index 9c3107e4a..73f358d0a 100644 --- a/livekit-protocol/src/livekit.rs +++ b/livekit-protocol/src/livekit.rs @@ -515,7 +515,7 @@ pub struct DataPacket { /// identities of participants who will receive the message (sent to all by default) #[prost(string, repeated, tag="5")] pub destination_identities: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, - #[prost(oneof="data_packet::Value", tags="2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14")] + #[prost(oneof="data_packet::Value", tags="2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15")] pub value: ::core::option::Option, } /// Nested message and enum types in `DataPacket`. @@ -571,6 +571,8 @@ pub mod data_packet { StreamHeader(super::data_stream::Header), #[prost(message, tag="14")] StreamChunk(super::data_stream::Chunk), + #[prost(message, tag="15")] + StreamTrailer(super::data_stream::Trailer), } } #[allow(clippy::derive_partial_eq_without_eq)] @@ -1166,9 +1168,6 @@ pub mod data_stream { /// only populated for finite streams, if it's a stream of unknown size this stays empty #[prost(uint64, optional, tag="5")] pub total_length: ::core::option::Option, - /// only populated for finite streams, if it's a stream of unknown size this stays empty - #[prost(uint64, optional, tag="6")] - pub total_chunks: ::core::option::Option, /// defaults to NONE #[prost(enumeration="super::encryption::Type", tag="7")] pub encryption_type: i32, @@ -1202,16 +1201,26 @@ pub mod data_stream { /// content as binary (bytes) #[prost(bytes="vec", tag="3")] pub content: ::prost::alloc::vec::Vec, - /// true only if this is the last chunk of this stream - can also be sent with empty content - #[prost(bool, tag="4")] - pub complete: bool, /// a version indicating that this chunk_index has been retroactively modified and the original one needs to be replaced - #[prost(int32, tag="5")] + #[prost(int32, tag="4")] pub version: i32, /// optional, initialization vector for AES-GCM encryption - #[prost(bytes="vec", optional, tag="6")] + #[prost(bytes="vec", optional, tag="5")] pub iv: ::core::option::Option<::prost::alloc::vec::Vec>, } + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] + pub struct Trailer { + /// unique identifier for this data stream + #[prost(string, tag="1")] + pub stream_id: ::prost::alloc::string::String, + /// reason why the stream was closed (could contain "error" / "interrupted" / empty for expected end) + #[prost(string, tag="2")] + pub reason: ::prost::alloc::string::String, + /// finalizing updates for the stream, can also include additional insights for errors or endTime for transcription + #[prost(map="string, string", tag="3")] + pub extensions: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + } /// enum for operation types (specific to TextHeader) #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] @@ -2209,6 +2218,8 @@ pub struct EgressInfo { pub room_id: ::prost::alloc::string::String, #[prost(string, tag="13")] pub room_name: ::prost::alloc::string::String, + #[prost(enumeration="EgressSourceType", tag="26")] + pub source_type: i32, #[prost(enumeration="EgressStatus", tag="3")] pub status: i32, #[prost(int64, tag="10")] @@ -2233,7 +2244,7 @@ pub struct EgressInfo { pub image_results: ::prost::alloc::vec::Vec, #[prost(string, tag="23")] pub manifest_location: ::prost::alloc::string::String, - /// next ID: 26 + /// next ID: 27 #[prost(bool, tag="25")] pub backup_storage_used: bool, #[prost(oneof="egress_info::Request", tags="4, 14, 19, 5, 6")] @@ -2655,6 +2666,32 @@ impl EgressStatus { } } } +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum EgressSourceType { + Web = 0, + Sdk = 1, +} +impl EgressSourceType { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + EgressSourceType::Web => "EGRESS_SOURCE_TYPE_WEB", + EgressSourceType::Sdk => "EGRESS_SOURCE_TYPE_SDK", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "EGRESS_SOURCE_TYPE_WEB" => Some(Self::Web), + "EGRESS_SOURCE_TYPE_SDK" => Some(Self::Sdk), + _ => None, + } + } +} #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SignalRequest { @@ -4079,6 +4116,9 @@ pub struct CreateIngressRequest { pub audio: ::core::option::Option, #[prost(message, optional, tag="7")] pub video: ::core::option::Option, + /// The default value is true and when set to false, the new connection attempts will be rejected + #[prost(bool, optional, tag="12")] + pub enabled: ::core::option::Option, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -4186,6 +4226,9 @@ pub struct IngressInfo { /// Description of error/stream non compliance and debug info for publisher otherwise (received bitrate, resolution, bandwidth) #[prost(message, optional, tag="12")] pub state: ::core::option::Option, + /// The default value is true and when set to false, the new connection attempts will be rejected + #[prost(bool, optional, tag="16")] + pub enabled: ::core::option::Option, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -4301,6 +4344,9 @@ pub struct UpdateIngressRequest { pub audio: ::core::option::Option, #[prost(message, optional, tag="7")] pub video: ::core::option::Option, + /// The default value is true and when set to false, the new connection attempts will be rejected + #[prost(bool, optional, tag="11")] + pub enabled: ::core::option::Option, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -4636,6 +4682,14 @@ pub struct SipInboundTrunkInfo { /// Keys are the names of attributes and values are the names of X-* headers they will be mapped to. #[prost(map="string, string", tag="14")] pub attributes_to_headers: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + /// Map SIP headers from INVITE to sip.h.* participant attributes automatically. + /// + /// When the names of required headers is known, using headers_to_attributes is strongly recommended. + /// + /// When mapping INVITE headers to response headers with attributes_to_headers map, + /// lowercase header names should be used, for example: sip.h.x-custom-header. + #[prost(enumeration="SipHeaderOptions", tag="15")] + pub include_headers: i32, /// Max time for the caller to wait for track subscription. #[prost(message, optional, tag="11")] pub ringing_timeout: ::core::option::Option<::pbjson_types::Duration>, @@ -4644,6 +4698,8 @@ pub struct SipInboundTrunkInfo { pub max_call_duration: ::core::option::Option<::pbjson_types::Duration>, #[prost(bool, tag="13")] pub krisp_enabled: bool, + #[prost(enumeration="SipMediaEncryption", tag="16")] + pub media_encryption: i32, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -4691,6 +4747,16 @@ pub struct SipOutboundTrunkInfo { /// Keys are the names of attributes and values are the names of X-* headers they will be mapped to. #[prost(map="string, string", tag="11")] pub attributes_to_headers: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + /// Map SIP headers from 200 OK to sip.h.* participant attributes automatically. + /// + /// When the names of required headers is known, using headers_to_attributes is strongly recommended. + /// + /// When mapping 200 OK headers to follow-up request headers with attributes_to_headers map, + /// lowercase header names should be used, for example: sip.h.x-custom-header. + #[prost(enumeration="SipHeaderOptions", tag="12")] + pub include_headers: i32, + #[prost(enumeration="SipMediaEncryption", tag="13")] + pub media_encryption: i32, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -4726,9 +4792,17 @@ pub struct ListSipTrunkResponse { #[prost(message, repeated, tag="1")] pub items: ::prost::alloc::vec::Vec, } +/// ListSIPInboundTrunkRequest lists inbound trunks for given filters. If no filters are set, all trunks are listed. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ListSipInboundTrunkRequest { + /// Trunk IDs to list. If this option is set, the response will contains trunks in the same order. + /// If any of the trunks is missing, a nil item in that position will be sent in the response. + #[prost(string, repeated, tag="1")] + pub trunk_ids: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + /// Only list trunks that contain one of the numbers, including wildcard trunks. + #[prost(string, repeated, tag="2")] + pub numbers: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -4736,9 +4810,17 @@ pub struct ListSipInboundTrunkResponse { #[prost(message, repeated, tag="1")] pub items: ::prost::alloc::vec::Vec, } +/// ListSIPOutboundTrunkRequest lists outbound trunks for given filters. If no filters are set, all trunks are listed. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ListSipOutboundTrunkRequest { + /// Trunk IDs to list. If this option is set, the response will contains trunks in the same order. + /// If any of the trunks is missing, a nil item in that position will be sent in the response. + #[prost(string, repeated, tag="1")] + pub trunk_ids: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + /// Only list trunks that contain one of the numbers, including wildcard trunks. + #[prost(string, repeated, tag="2")] + pub numbers: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -4836,6 +4918,12 @@ pub struct CreateSipDispatchRuleRequest { /// Participants created by this rule will inherit these attributes. #[prost(map="string, string", tag="7")] pub attributes: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + /// Cloud-only, config preset to use + #[prost(string, tag="8")] + pub room_preset: ::prost::alloc::string::String, + /// RoomConfiguration to use if the participant initiates the room + #[prost(message, optional, tag="9")] + pub room_config: ::core::option::Option, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -4862,10 +4950,29 @@ pub struct SipDispatchRuleInfo { /// Participants created by this rule will inherit these attributes. #[prost(map="string, string", tag="8")] pub attributes: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + /// Cloud-only, config preset to use + #[prost(string, tag="9")] + pub room_preset: ::prost::alloc::string::String, + /// RoomConfiguration to use if the participant initiates the room + #[prost(message, optional, tag="10")] + pub room_config: ::core::option::Option, + #[prost(bool, tag="11")] + pub krisp_enabled: bool, + /// NEXT ID: 13 + #[prost(enumeration="SipMediaEncryption", tag="12")] + pub media_encryption: i32, } +/// ListSIPDispatchRuleRequest lists dispatch rules for given filters. If no filters are set, all rules are listed. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ListSipDispatchRuleRequest { + /// Rule IDs to list. If this option is set, the response will contains rules in the same order. + /// If any of the rules is missing, a nil item in that position will be sent in the response. + #[prost(string, repeated, tag="1")] + pub dispatch_rule_ids: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + /// Only list rules that contain one of the Trunk IDs, including wildcard rules. + #[prost(string, repeated, tag="2")] + pub trunk_ids: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -4922,6 +5029,17 @@ pub struct CreateSipParticipantRequest { /// If true, a random value for identity will be used and numbers will be omitted from attributes. #[prost(bool, tag="10")] pub hide_phone_number: bool, + /// These headers are sent as-is and may help identify this call as coming from LiveKit for the other SIP endpoint. + #[prost(map="string, string", tag="16")] + pub headers: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + /// Map SIP headers from 200 OK to sip.h.* participant attributes automatically. + /// + /// When the names of required headers is known, using headers_to_attributes is strongly recommended. + /// + /// When mapping 200 OK headers to follow-up request headers with attributes_to_headers map, + /// lowercase header names should be used, for example: sip.h.x-custom-header. + #[prost(enumeration="SipHeaderOptions", tag="17")] + pub include_headers: i32, /// Max time for the callee to answer the call. #[prost(message, optional, tag="11")] pub ringing_timeout: ::core::option::Option<::pbjson_types::Duration>, @@ -4929,10 +5047,11 @@ pub struct CreateSipParticipantRequest { #[prost(message, optional, tag="12")] pub max_call_duration: ::core::option::Option<::pbjson_types::Duration>, /// Enable voice isolation for the callee. - /// - /// NEXT ID: 16 #[prost(bool, tag="14")] - pub enable_krisp: bool, + pub krisp_enabled: bool, + /// NEXT ID: 19 + #[prost(enumeration="SipMediaEncryption", tag="18")] + pub media_encryption: i32, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -4958,6 +5077,9 @@ pub struct TransferSipParticipantRequest { /// Optionally play dialtone to the SIP participant as an audible indicator of being transferred #[prost(bool, tag="4")] pub play_dialtone: bool, + /// Add the following headers to the REFER SIP request. + #[prost(map="string, string", tag="5")] + pub headers: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -4979,6 +5101,8 @@ pub struct SipCallInfo { pub to_uri: ::core::option::Option, #[prost(enumeration="SipFeature", repeated, tag="14")] pub enabled_features: ::prost::alloc::vec::Vec, + #[prost(enumeration="SipCallDirection", tag="15")] + pub call_direction: i32, #[prost(enumeration="SipCallStatus", tag="8")] pub call_status: i32, #[prost(int64, tag="9")] @@ -5040,6 +5164,70 @@ impl SipTransport { } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] +pub enum SipHeaderOptions { + /// do not map any headers, except ones mapped explicitly + SipNoHeaders = 0, + /// map all X-* headers to sip.h.x-* attributes + SipXHeaders = 1, + /// map all headers to sip.h.* attributes + SipAllHeaders = 2, +} +impl SipHeaderOptions { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + SipHeaderOptions::SipNoHeaders => "SIP_NO_HEADERS", + SipHeaderOptions::SipXHeaders => "SIP_X_HEADERS", + SipHeaderOptions::SipAllHeaders => "SIP_ALL_HEADERS", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "SIP_NO_HEADERS" => Some(Self::SipNoHeaders), + "SIP_X_HEADERS" => Some(Self::SipXHeaders), + "SIP_ALL_HEADERS" => Some(Self::SipAllHeaders), + _ => None, + } + } +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum SipMediaEncryption { + /// do not enable encryption + SipMediaEncryptDisable = 0, + /// use encryption if available + SipMediaEncryptAllow = 1, + /// require encryption + SipMediaEncryptRequire = 2, +} +impl SipMediaEncryption { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + SipMediaEncryption::SipMediaEncryptDisable => "SIP_MEDIA_ENCRYPT_DISABLE", + SipMediaEncryption::SipMediaEncryptAllow => "SIP_MEDIA_ENCRYPT_ALLOW", + SipMediaEncryption::SipMediaEncryptRequire => "SIP_MEDIA_ENCRYPT_REQUIRE", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "SIP_MEDIA_ENCRYPT_DISABLE" => Some(Self::SipMediaEncryptDisable), + "SIP_MEDIA_ENCRYPT_ALLOW" => Some(Self::SipMediaEncryptAllow), + "SIP_MEDIA_ENCRYPT_REQUIRE" => Some(Self::SipMediaEncryptRequire), + _ => None, + } + } +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] pub enum SipCallStatus { /// Incoming call is being handled by the SIP service. The SIP participant hasn't joined a LiveKit room yet ScsCallIncoming = 0, @@ -5104,5 +5292,34 @@ impl SipFeature { } } } +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum SipCallDirection { + ScdUnknown = 0, + ScdInbound = 1, + ScdOutbound = 2, +} +impl SipCallDirection { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + SipCallDirection::ScdUnknown => "SCD_UNKNOWN", + SipCallDirection::ScdInbound => "SCD_INBOUND", + SipCallDirection::ScdOutbound => "SCD_OUTBOUND", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "SCD_UNKNOWN" => Some(Self::ScdUnknown), + "SCD_INBOUND" => Some(Self::ScdInbound), + "SCD_OUTBOUND" => Some(Self::ScdOutbound), + _ => None, + } + } +} include!("livekit.serde.rs"); // @@protoc_insertion_point(module) diff --git a/livekit-protocol/src/livekit.serde.rs b/livekit-protocol/src/livekit.serde.rs index 5da36416c..ae08809a4 100644 --- a/livekit-protocol/src/livekit.serde.rs +++ b/livekit-protocol/src/livekit.serde.rs @@ -3311,6 +3311,9 @@ impl serde::Serialize for CreateIngressRequest { if self.video.is_some() { len += 1; } + if self.enabled.is_some() { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.CreateIngressRequest", len)?; if self.input_type != 0 { let v = IngressInput::try_from(self.input_type) @@ -3347,6 +3350,9 @@ impl serde::Serialize for CreateIngressRequest { if let Some(v) = self.video.as_ref() { struct_ser.serialize_field("video", v)?; } + if let Some(v) = self.enabled.as_ref() { + struct_ser.serialize_field("enabled", v)?; + } struct_ser.end() } } @@ -3375,6 +3381,7 @@ impl<'de> serde::Deserialize<'de> for CreateIngressRequest { "enableTranscoding", "audio", "video", + "enabled", ]; #[allow(clippy::enum_variant_names)] @@ -3390,6 +3397,7 @@ impl<'de> serde::Deserialize<'de> for CreateIngressRequest { EnableTranscoding, Audio, Video, + Enabled, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -3423,6 +3431,7 @@ impl<'de> serde::Deserialize<'de> for CreateIngressRequest { "enableTranscoding" | "enable_transcoding" => Ok(GeneratedField::EnableTranscoding), "audio" => Ok(GeneratedField::Audio), "video" => Ok(GeneratedField::Video), + "enabled" => Ok(GeneratedField::Enabled), _ => Ok(GeneratedField::__SkipField__), } } @@ -3453,6 +3462,7 @@ impl<'de> serde::Deserialize<'de> for CreateIngressRequest { let mut enable_transcoding__ = None; let mut audio__ = None; let mut video__ = None; + let mut enabled__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::InputType => { @@ -3521,6 +3531,12 @@ impl<'de> serde::Deserialize<'de> for CreateIngressRequest { } video__ = map_.next_value()?; } + GeneratedField::Enabled => { + if enabled__.is_some() { + return Err(serde::de::Error::duplicate_field("enabled")); + } + enabled__ = map_.next_value()?; + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -3538,6 +3554,7 @@ impl<'de> serde::Deserialize<'de> for CreateIngressRequest { enable_transcoding: enable_transcoding__, audio: audio__, video: video__, + enabled: enabled__, }) } } @@ -3891,6 +3908,12 @@ impl serde::Serialize for CreateSipDispatchRuleRequest { if !self.attributes.is_empty() { len += 1; } + if !self.room_preset.is_empty() { + len += 1; + } + if self.room_config.is_some() { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.CreateSIPDispatchRuleRequest", len)?; if let Some(v) = self.rule.as_ref() { struct_ser.serialize_field("rule", v)?; @@ -3913,6 +3936,12 @@ impl serde::Serialize for CreateSipDispatchRuleRequest { if !self.attributes.is_empty() { struct_ser.serialize_field("attributes", &self.attributes)?; } + if !self.room_preset.is_empty() { + struct_ser.serialize_field("roomPreset", &self.room_preset)?; + } + if let Some(v) = self.room_config.as_ref() { + struct_ser.serialize_field("roomConfig", v)?; + } struct_ser.end() } } @@ -3933,6 +3962,10 @@ impl<'de> serde::Deserialize<'de> for CreateSipDispatchRuleRequest { "name", "metadata", "attributes", + "room_preset", + "roomPreset", + "room_config", + "roomConfig", ]; #[allow(clippy::enum_variant_names)] @@ -3944,6 +3977,8 @@ impl<'de> serde::Deserialize<'de> for CreateSipDispatchRuleRequest { Name, Metadata, Attributes, + RoomPreset, + RoomConfig, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -3973,6 +4008,8 @@ impl<'de> serde::Deserialize<'de> for CreateSipDispatchRuleRequest { "name" => Ok(GeneratedField::Name), "metadata" => Ok(GeneratedField::Metadata), "attributes" => Ok(GeneratedField::Attributes), + "roomPreset" | "room_preset" => Ok(GeneratedField::RoomPreset), + "roomConfig" | "room_config" => Ok(GeneratedField::RoomConfig), _ => Ok(GeneratedField::__SkipField__), } } @@ -3999,6 +4036,8 @@ impl<'de> serde::Deserialize<'de> for CreateSipDispatchRuleRequest { let mut name__ = None; let mut metadata__ = None; let mut attributes__ = None; + let mut room_preset__ = None; + let mut room_config__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::Rule => { @@ -4045,6 +4084,18 @@ impl<'de> serde::Deserialize<'de> for CreateSipDispatchRuleRequest { map_.next_value::>()? ); } + GeneratedField::RoomPreset => { + if room_preset__.is_some() { + return Err(serde::de::Error::duplicate_field("roomPreset")); + } + room_preset__ = Some(map_.next_value()?); + } + GeneratedField::RoomConfig => { + if room_config__.is_some() { + return Err(serde::de::Error::duplicate_field("roomConfig")); + } + room_config__ = map_.next_value()?; + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -4058,6 +4109,8 @@ impl<'de> serde::Deserialize<'de> for CreateSipDispatchRuleRequest { name: name__.unwrap_or_default(), metadata: metadata__.unwrap_or_default(), attributes: attributes__.unwrap_or_default(), + room_preset: room_preset__.unwrap_or_default(), + room_config: room_config__, }) } } @@ -4298,13 +4351,22 @@ impl serde::Serialize for CreateSipParticipantRequest { if self.hide_phone_number { len += 1; } + if !self.headers.is_empty() { + len += 1; + } + if self.include_headers != 0 { + len += 1; + } if self.ringing_timeout.is_some() { len += 1; } if self.max_call_duration.is_some() { len += 1; } - if self.enable_krisp { + if self.krisp_enabled { + len += 1; + } + if self.media_encryption != 0 { len += 1; } let mut struct_ser = serializer.serialize_struct("livekit.CreateSIPParticipantRequest", len)?; @@ -4344,14 +4406,27 @@ impl serde::Serialize for CreateSipParticipantRequest { if self.hide_phone_number { struct_ser.serialize_field("hidePhoneNumber", &self.hide_phone_number)?; } + if !self.headers.is_empty() { + struct_ser.serialize_field("headers", &self.headers)?; + } + if self.include_headers != 0 { + let v = SipHeaderOptions::try_from(self.include_headers) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.include_headers)))?; + struct_ser.serialize_field("includeHeaders", &v)?; + } if let Some(v) = self.ringing_timeout.as_ref() { struct_ser.serialize_field("ringingTimeout", v)?; } if let Some(v) = self.max_call_duration.as_ref() { struct_ser.serialize_field("maxCallDuration", v)?; } - if self.enable_krisp { - struct_ser.serialize_field("enableKrisp", &self.enable_krisp)?; + if self.krisp_enabled { + struct_ser.serialize_field("krispEnabled", &self.krisp_enabled)?; + } + if self.media_encryption != 0 { + let v = SipMediaEncryption::try_from(self.media_encryption) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.media_encryption)))?; + struct_ser.serialize_field("mediaEncryption", &v)?; } struct_ser.end() } @@ -4386,12 +4461,17 @@ impl<'de> serde::Deserialize<'de> for CreateSipParticipantRequest { "playDialtone", "hide_phone_number", "hidePhoneNumber", + "headers", + "include_headers", + "includeHeaders", "ringing_timeout", "ringingTimeout", "max_call_duration", "maxCallDuration", - "enable_krisp", - "enableKrisp", + "krisp_enabled", + "krispEnabled", + "media_encryption", + "mediaEncryption", ]; #[allow(clippy::enum_variant_names)] @@ -4408,9 +4488,12 @@ impl<'de> serde::Deserialize<'de> for CreateSipParticipantRequest { PlayRingtone, PlayDialtone, HidePhoneNumber, + Headers, + IncludeHeaders, RingingTimeout, MaxCallDuration, - EnableKrisp, + KrispEnabled, + MediaEncryption, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -4445,9 +4528,12 @@ impl<'de> serde::Deserialize<'de> for CreateSipParticipantRequest { "playRingtone" | "play_ringtone" => Ok(GeneratedField::PlayRingtone), "playDialtone" | "play_dialtone" => Ok(GeneratedField::PlayDialtone), "hidePhoneNumber" | "hide_phone_number" => Ok(GeneratedField::HidePhoneNumber), + "headers" => Ok(GeneratedField::Headers), + "includeHeaders" | "include_headers" => Ok(GeneratedField::IncludeHeaders), "ringingTimeout" | "ringing_timeout" => Ok(GeneratedField::RingingTimeout), "maxCallDuration" | "max_call_duration" => Ok(GeneratedField::MaxCallDuration), - "enableKrisp" | "enable_krisp" => Ok(GeneratedField::EnableKrisp), + "krispEnabled" | "krisp_enabled" => Ok(GeneratedField::KrispEnabled), + "mediaEncryption" | "media_encryption" => Ok(GeneratedField::MediaEncryption), _ => Ok(GeneratedField::__SkipField__), } } @@ -4479,9 +4565,12 @@ impl<'de> serde::Deserialize<'de> for CreateSipParticipantRequest { let mut play_ringtone__ = None; let mut play_dialtone__ = None; let mut hide_phone_number__ = None; + let mut headers__ = None; + let mut include_headers__ = None; let mut ringing_timeout__ = None; let mut max_call_duration__ = None; - let mut enable_krisp__ = None; + let mut krisp_enabled__ = None; + let mut media_encryption__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::SipTrunkId => { @@ -4558,6 +4647,20 @@ impl<'de> serde::Deserialize<'de> for CreateSipParticipantRequest { } hide_phone_number__ = Some(map_.next_value()?); } + GeneratedField::Headers => { + if headers__.is_some() { + return Err(serde::de::Error::duplicate_field("headers")); + } + headers__ = Some( + map_.next_value::>()? + ); + } + GeneratedField::IncludeHeaders => { + if include_headers__.is_some() { + return Err(serde::de::Error::duplicate_field("includeHeaders")); + } + include_headers__ = Some(map_.next_value::()? as i32); + } GeneratedField::RingingTimeout => { if ringing_timeout__.is_some() { return Err(serde::de::Error::duplicate_field("ringingTimeout")); @@ -4570,11 +4673,17 @@ impl<'de> serde::Deserialize<'de> for CreateSipParticipantRequest { } max_call_duration__ = map_.next_value()?; } - GeneratedField::EnableKrisp => { - if enable_krisp__.is_some() { - return Err(serde::de::Error::duplicate_field("enableKrisp")); + GeneratedField::KrispEnabled => { + if krisp_enabled__.is_some() { + return Err(serde::de::Error::duplicate_field("krispEnabled")); + } + krisp_enabled__ = Some(map_.next_value()?); + } + GeneratedField::MediaEncryption => { + if media_encryption__.is_some() { + return Err(serde::de::Error::duplicate_field("mediaEncryption")); } - enable_krisp__ = Some(map_.next_value()?); + media_encryption__ = Some(map_.next_value::()? as i32); } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; @@ -4594,9 +4703,12 @@ impl<'de> serde::Deserialize<'de> for CreateSipParticipantRequest { play_ringtone: play_ringtone__.unwrap_or_default(), play_dialtone: play_dialtone__.unwrap_or_default(), hide_phone_number: hide_phone_number__.unwrap_or_default(), + headers: headers__.unwrap_or_default(), + include_headers: include_headers__.unwrap_or_default(), ringing_timeout: ringing_timeout__, max_call_duration: max_call_duration__, - enable_krisp: enable_krisp__.unwrap_or_default(), + krisp_enabled: krisp_enabled__.unwrap_or_default(), + media_encryption: media_encryption__.unwrap_or_default(), }) } } @@ -5077,6 +5189,9 @@ impl serde::Serialize for DataPacket { data_packet::Value::StreamChunk(v) => { struct_ser.serialize_field("streamChunk", v)?; } + data_packet::Value::StreamTrailer(v) => { + struct_ser.serialize_field("streamTrailer", v)?; + } } } struct_ser.end() @@ -5112,6 +5227,8 @@ impl<'de> serde::Deserialize<'de> for DataPacket { "streamHeader", "stream_chunk", "streamChunk", + "stream_trailer", + "streamTrailer", ]; #[allow(clippy::enum_variant_names)] @@ -5130,6 +5247,7 @@ impl<'de> serde::Deserialize<'de> for DataPacket { RpcResponse, StreamHeader, StreamChunk, + StreamTrailer, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -5166,6 +5284,7 @@ impl<'de> serde::Deserialize<'de> for DataPacket { "rpcResponse" | "rpc_response" => Ok(GeneratedField::RpcResponse), "streamHeader" | "stream_header" => Ok(GeneratedField::StreamHeader), "streamChunk" | "stream_chunk" => Ok(GeneratedField::StreamChunk), + "streamTrailer" | "stream_trailer" => Ok(GeneratedField::StreamTrailer), _ => Ok(GeneratedField::__SkipField__), } } @@ -5284,6 +5403,13 @@ impl<'de> serde::Deserialize<'de> for DataPacket { return Err(serde::de::Error::duplicate_field("streamChunk")); } value__ = map_.next_value::<::std::option::Option<_>>()?.map(data_packet::Value::StreamChunk) +; + } + GeneratedField::StreamTrailer => { + if value__.is_some() { + return Err(serde::de::Error::duplicate_field("streamTrailer")); + } + value__ = map_.next_value::<::std::option::Option<_>>()?.map(data_packet::Value::StreamTrailer) ; } GeneratedField::__SkipField__ => { @@ -5462,9 +5588,6 @@ impl serde::Serialize for data_stream::Chunk { if !self.content.is_empty() { len += 1; } - if self.complete { - len += 1; - } if self.version != 0 { len += 1; } @@ -5485,9 +5608,6 @@ impl serde::Serialize for data_stream::Chunk { #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("content", pbjson::private::base64::encode(&self.content).as_str())?; } - if self.complete { - struct_ser.serialize_field("complete", &self.complete)?; - } if self.version != 0 { struct_ser.serialize_field("version", &self.version)?; } @@ -5511,7 +5631,6 @@ impl<'de> serde::Deserialize<'de> for data_stream::Chunk { "chunk_index", "chunkIndex", "content", - "complete", "version", "iv", ]; @@ -5521,7 +5640,6 @@ impl<'de> serde::Deserialize<'de> for data_stream::Chunk { StreamId, ChunkIndex, Content, - Complete, Version, Iv, __SkipField__, @@ -5549,7 +5667,6 @@ impl<'de> serde::Deserialize<'de> for data_stream::Chunk { "streamId" | "stream_id" => Ok(GeneratedField::StreamId), "chunkIndex" | "chunk_index" => Ok(GeneratedField::ChunkIndex), "content" => Ok(GeneratedField::Content), - "complete" => Ok(GeneratedField::Complete), "version" => Ok(GeneratedField::Version), "iv" => Ok(GeneratedField::Iv), _ => Ok(GeneratedField::__SkipField__), @@ -5574,7 +5691,6 @@ impl<'de> serde::Deserialize<'de> for data_stream::Chunk { let mut stream_id__ = None; let mut chunk_index__ = None; let mut content__ = None; - let mut complete__ = None; let mut version__ = None; let mut iv__ = None; while let Some(k) = map_.next_key()? { @@ -5601,12 +5717,6 @@ impl<'de> serde::Deserialize<'de> for data_stream::Chunk { Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) ; } - GeneratedField::Complete => { - if complete__.is_some() { - return Err(serde::de::Error::duplicate_field("complete")); - } - complete__ = Some(map_.next_value()?); - } GeneratedField::Version => { if version__.is_some() { return Err(serde::de::Error::duplicate_field("version")); @@ -5632,7 +5742,6 @@ impl<'de> serde::Deserialize<'de> for data_stream::Chunk { stream_id: stream_id__.unwrap_or_default(), chunk_index: chunk_index__.unwrap_or_default(), content: content__.unwrap_or_default(), - complete: complete__.unwrap_or_default(), version: version__.unwrap_or_default(), iv: iv__, }) @@ -5760,9 +5869,6 @@ impl serde::Serialize for data_stream::Header { if self.total_length.is_some() { len += 1; } - if self.total_chunks.is_some() { - len += 1; - } if self.encryption_type != 0 { len += 1; } @@ -5792,11 +5898,6 @@ impl serde::Serialize for data_stream::Header { #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("totalLength", ToString::to_string(&v).as_str())?; } - if let Some(v) = self.total_chunks.as_ref() { - #[allow(clippy::needless_borrow)] - #[allow(clippy::needless_borrows_for_generic_args)] - struct_ser.serialize_field("totalChunks", ToString::to_string(&v).as_str())?; - } if self.encryption_type != 0 { let v = encryption::Type::try_from(self.encryption_type) .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.encryption_type)))?; @@ -5833,8 +5934,6 @@ impl<'de> serde::Deserialize<'de> for data_stream::Header { "mimeType", "total_length", "totalLength", - "total_chunks", - "totalChunks", "encryption_type", "encryptionType", "extensions", @@ -5851,7 +5950,6 @@ impl<'de> serde::Deserialize<'de> for data_stream::Header { Topic, MimeType, TotalLength, - TotalChunks, EncryptionType, Extensions, TextHeader, @@ -5883,7 +5981,6 @@ impl<'de> serde::Deserialize<'de> for data_stream::Header { "topic" => Ok(GeneratedField::Topic), "mimeType" | "mime_type" => Ok(GeneratedField::MimeType), "totalLength" | "total_length" => Ok(GeneratedField::TotalLength), - "totalChunks" | "total_chunks" => Ok(GeneratedField::TotalChunks), "encryptionType" | "encryption_type" => Ok(GeneratedField::EncryptionType), "extensions" => Ok(GeneratedField::Extensions), "textHeader" | "text_header" => Ok(GeneratedField::TextHeader), @@ -5912,7 +6009,6 @@ impl<'de> serde::Deserialize<'de> for data_stream::Header { let mut topic__ = None; let mut mime_type__ = None; let mut total_length__ = None; - let mut total_chunks__ = None; let mut encryption_type__ = None; let mut extensions__ = None; let mut content_header__ = None; @@ -5952,14 +6048,6 @@ impl<'de> serde::Deserialize<'de> for data_stream::Header { map_.next_value::<::std::option::Option<::pbjson::private::NumberDeserialize<_>>>()?.map(|x| x.0) ; } - GeneratedField::TotalChunks => { - if total_chunks__.is_some() { - return Err(serde::de::Error::duplicate_field("totalChunks")); - } - total_chunks__ = - map_.next_value::<::std::option::Option<::pbjson::private::NumberDeserialize<_>>>()?.map(|x| x.0) - ; - } GeneratedField::EncryptionType => { if encryption_type__.is_some() { return Err(serde::de::Error::duplicate_field("encryptionType")); @@ -5999,7 +6087,6 @@ impl<'de> serde::Deserialize<'de> for data_stream::Header { topic: topic__.unwrap_or_default(), mime_type: mime_type__.unwrap_or_default(), total_length: total_length__, - total_chunks: total_chunks__, encryption_type: encryption_type__.unwrap_or_default(), extensions: extensions__.unwrap_or_default(), content_header: content_header__, @@ -6256,6 +6343,138 @@ impl<'de> serde::Deserialize<'de> for data_stream::TextHeader { deserializer.deserialize_struct("livekit.DataStream.TextHeader", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for data_stream::Trailer { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.stream_id.is_empty() { + len += 1; + } + if !self.reason.is_empty() { + len += 1; + } + if !self.extensions.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.DataStream.Trailer", len)?; + if !self.stream_id.is_empty() { + struct_ser.serialize_field("streamId", &self.stream_id)?; + } + if !self.reason.is_empty() { + struct_ser.serialize_field("reason", &self.reason)?; + } + if !self.extensions.is_empty() { + struct_ser.serialize_field("extensions", &self.extensions)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for data_stream::Trailer { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "stream_id", + "streamId", + "reason", + "extensions", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + StreamId, + Reason, + Extensions, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "streamId" | "stream_id" => Ok(GeneratedField::StreamId), + "reason" => Ok(GeneratedField::Reason), + "extensions" => Ok(GeneratedField::Extensions), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = data_stream::Trailer; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.DataStream.Trailer") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut stream_id__ = None; + let mut reason__ = None; + let mut extensions__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::StreamId => { + if stream_id__.is_some() { + return Err(serde::de::Error::duplicate_field("streamId")); + } + stream_id__ = Some(map_.next_value()?); + } + GeneratedField::Reason => { + if reason__.is_some() { + return Err(serde::de::Error::duplicate_field("reason")); + } + reason__ = Some(map_.next_value()?); + } + GeneratedField::Extensions => { + if extensions__.is_some() { + return Err(serde::de::Error::duplicate_field("extensions")); + } + extensions__ = Some( + map_.next_value::>()? + ); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(data_stream::Trailer { + stream_id: stream_id__.unwrap_or_default(), + reason: reason__.unwrap_or_default(), + extensions: extensions__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.DataStream.Trailer", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for DeleteAgentDispatchRequest { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -7234,6 +7453,9 @@ impl serde::Serialize for EgressInfo { if !self.room_name.is_empty() { len += 1; } + if self.source_type != 0 { + len += 1; + } if self.status != 0 { len += 1; } @@ -7289,6 +7511,11 @@ impl serde::Serialize for EgressInfo { if !self.room_name.is_empty() { struct_ser.serialize_field("roomName", &self.room_name)?; } + if self.source_type != 0 { + let v = EgressSourceType::try_from(self.source_type) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.source_type)))?; + struct_ser.serialize_field("sourceType", &v)?; + } if self.status != 0 { let v = EgressStatus::try_from(self.status) .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.status)))?; @@ -7384,6 +7611,8 @@ impl<'de> serde::Deserialize<'de> for EgressInfo { "roomId", "room_name", "roomName", + "source_type", + "sourceType", "status", "started_at", "startedAt", @@ -7424,6 +7653,7 @@ impl<'de> serde::Deserialize<'de> for EgressInfo { EgressId, RoomId, RoomName, + SourceType, Status, StartedAt, EndedAt, @@ -7470,6 +7700,7 @@ impl<'de> serde::Deserialize<'de> for EgressInfo { "egressId" | "egress_id" => Ok(GeneratedField::EgressId), "roomId" | "room_id" => Ok(GeneratedField::RoomId), "roomName" | "room_name" => Ok(GeneratedField::RoomName), + "sourceType" | "source_type" => Ok(GeneratedField::SourceType), "status" => Ok(GeneratedField::Status), "startedAt" | "started_at" => Ok(GeneratedField::StartedAt), "endedAt" | "ended_at" => Ok(GeneratedField::EndedAt), @@ -7513,6 +7744,7 @@ impl<'de> serde::Deserialize<'de> for EgressInfo { let mut egress_id__ = None; let mut room_id__ = None; let mut room_name__ = None; + let mut source_type__ = None; let mut status__ = None; let mut started_at__ = None; let mut ended_at__ = None; @@ -7548,6 +7780,12 @@ impl<'de> serde::Deserialize<'de> for EgressInfo { } room_name__ = Some(map_.next_value()?); } + GeneratedField::SourceType => { + if source_type__.is_some() { + return Err(serde::de::Error::duplicate_field("sourceType")); + } + source_type__ = Some(map_.next_value::()? as i32); + } GeneratedField::Status => { if status__.is_some() { return Err(serde::de::Error::duplicate_field("status")); @@ -7699,6 +7937,7 @@ impl<'de> serde::Deserialize<'de> for EgressInfo { egress_id: egress_id__.unwrap_or_default(), room_id: room_id__.unwrap_or_default(), room_name: room_name__.unwrap_or_default(), + source_type: source_type__.unwrap_or_default(), status: status__.unwrap_or_default(), started_at: started_at__.unwrap_or_default(), ended_at: ended_at__.unwrap_or_default(), @@ -7720,6 +7959,77 @@ impl<'de> serde::Deserialize<'de> for EgressInfo { deserializer.deserialize_struct("livekit.EgressInfo", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for EgressSourceType { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + let variant = match self { + Self::Web => "EGRESS_SOURCE_TYPE_WEB", + Self::Sdk => "EGRESS_SOURCE_TYPE_SDK", + }; + serializer.serialize_str(variant) + } +} +impl<'de> serde::Deserialize<'de> for EgressSourceType { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "EGRESS_SOURCE_TYPE_WEB", + "EGRESS_SOURCE_TYPE_SDK", + ]; + + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = EgressSourceType; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + fn visit_i64(self, v: i64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self) + }) + } + + fn visit_u64(self, v: u64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(v), &self) + }) + } + + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "EGRESS_SOURCE_TYPE_WEB" => Ok(EgressSourceType::Web), + "EGRESS_SOURCE_TYPE_SDK" => Ok(EgressSourceType::Sdk), + _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), + } + } + } + deserializer.deserialize_any(GeneratedVisitor) + } +} impl serde::Serialize for EgressStatus { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -10719,6 +11029,9 @@ impl serde::Serialize for IngressInfo { if self.state.is_some() { len += 1; } + if self.enabled.is_some() { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.IngressInfo", len)?; if !self.ingress_id.is_empty() { struct_ser.serialize_field("ingressId", &self.ingress_id)?; @@ -10767,6 +11080,9 @@ impl serde::Serialize for IngressInfo { if let Some(v) = self.state.as_ref() { struct_ser.serialize_field("state", v)?; } + if let Some(v) = self.enabled.as_ref() { + struct_ser.serialize_field("enabled", v)?; + } struct_ser.end() } } @@ -10801,6 +11117,7 @@ impl<'de> serde::Deserialize<'de> for IngressInfo { "participantMetadata", "reusable", "state", + "enabled", ]; #[allow(clippy::enum_variant_names)] @@ -10820,6 +11137,7 @@ impl<'de> serde::Deserialize<'de> for IngressInfo { ParticipantMetadata, Reusable, State, + Enabled, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -10857,6 +11175,7 @@ impl<'de> serde::Deserialize<'de> for IngressInfo { "participantMetadata" | "participant_metadata" => Ok(GeneratedField::ParticipantMetadata), "reusable" => Ok(GeneratedField::Reusable), "state" => Ok(GeneratedField::State), + "enabled" => Ok(GeneratedField::Enabled), _ => Ok(GeneratedField::__SkipField__), } } @@ -10891,6 +11210,7 @@ impl<'de> serde::Deserialize<'de> for IngressInfo { let mut participant_metadata__ = None; let mut reusable__ = None; let mut state__ = None; + let mut enabled__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::IngressId => { @@ -10983,6 +11303,12 @@ impl<'de> serde::Deserialize<'de> for IngressInfo { } state__ = map_.next_value()?; } + GeneratedField::Enabled => { + if enabled__.is_some() { + return Err(serde::de::Error::duplicate_field("enabled")); + } + enabled__ = map_.next_value()?; + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -11004,6 +11330,7 @@ impl<'de> serde::Deserialize<'de> for IngressInfo { participant_metadata: participant_metadata__.unwrap_or_default(), reusable: reusable__.unwrap_or_default(), state: state__, + enabled: enabled__, }) } } @@ -14555,8 +14882,20 @@ impl serde::Serialize for ListSipDispatchRuleRequest { S: serde::Serializer, { use serde::ser::SerializeStruct; - let len = 0; - let struct_ser = serializer.serialize_struct("livekit.ListSIPDispatchRuleRequest", len)?; + let mut len = 0; + if !self.dispatch_rule_ids.is_empty() { + len += 1; + } + if !self.trunk_ids.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.ListSIPDispatchRuleRequest", len)?; + if !self.dispatch_rule_ids.is_empty() { + struct_ser.serialize_field("dispatchRuleIds", &self.dispatch_rule_ids)?; + } + if !self.trunk_ids.is_empty() { + struct_ser.serialize_field("trunkIds", &self.trunk_ids)?; + } struct_ser.end() } } @@ -14567,10 +14906,16 @@ impl<'de> serde::Deserialize<'de> for ListSipDispatchRuleRequest { D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ + "dispatch_rule_ids", + "dispatchRuleIds", + "trunk_ids", + "trunkIds", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { + DispatchRuleIds, + TrunkIds, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -14592,7 +14937,11 @@ impl<'de> serde::Deserialize<'de> for ListSipDispatchRuleRequest { where E: serde::de::Error, { - Ok(GeneratedField::__SkipField__) + match value { + "dispatchRuleIds" | "dispatch_rule_ids" => Ok(GeneratedField::DispatchRuleIds), + "trunkIds" | "trunk_ids" => Ok(GeneratedField::TrunkIds), + _ => Ok(GeneratedField::__SkipField__), + } } } deserializer.deserialize_identifier(GeneratedVisitor) @@ -14610,10 +14959,30 @@ impl<'de> serde::Deserialize<'de> for ListSipDispatchRuleRequest { where V: serde::de::MapAccess<'de>, { - while map_.next_key::()?.is_some() { - let _ = map_.next_value::()?; + let mut dispatch_rule_ids__ = None; + let mut trunk_ids__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::DispatchRuleIds => { + if dispatch_rule_ids__.is_some() { + return Err(serde::de::Error::duplicate_field("dispatchRuleIds")); + } + dispatch_rule_ids__ = Some(map_.next_value()?); + } + GeneratedField::TrunkIds => { + if trunk_ids__.is_some() { + return Err(serde::de::Error::duplicate_field("trunkIds")); + } + trunk_ids__ = Some(map_.next_value()?); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } } Ok(ListSipDispatchRuleRequest { + dispatch_rule_ids: dispatch_rule_ids__.unwrap_or_default(), + trunk_ids: trunk_ids__.unwrap_or_default(), }) } } @@ -14722,8 +15091,20 @@ impl serde::Serialize for ListSipInboundTrunkRequest { S: serde::Serializer, { use serde::ser::SerializeStruct; - let len = 0; - let struct_ser = serializer.serialize_struct("livekit.ListSIPInboundTrunkRequest", len)?; + let mut len = 0; + if !self.trunk_ids.is_empty() { + len += 1; + } + if !self.numbers.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.ListSIPInboundTrunkRequest", len)?; + if !self.trunk_ids.is_empty() { + struct_ser.serialize_field("trunkIds", &self.trunk_ids)?; + } + if !self.numbers.is_empty() { + struct_ser.serialize_field("numbers", &self.numbers)?; + } struct_ser.end() } } @@ -14734,10 +15115,15 @@ impl<'de> serde::Deserialize<'de> for ListSipInboundTrunkRequest { D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ + "trunk_ids", + "trunkIds", + "numbers", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { + TrunkIds, + Numbers, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -14759,7 +15145,11 @@ impl<'de> serde::Deserialize<'de> for ListSipInboundTrunkRequest { where E: serde::de::Error, { - Ok(GeneratedField::__SkipField__) + match value { + "trunkIds" | "trunk_ids" => Ok(GeneratedField::TrunkIds), + "numbers" => Ok(GeneratedField::Numbers), + _ => Ok(GeneratedField::__SkipField__), + } } } deserializer.deserialize_identifier(GeneratedVisitor) @@ -14777,10 +15167,30 @@ impl<'de> serde::Deserialize<'de> for ListSipInboundTrunkRequest { where V: serde::de::MapAccess<'de>, { - while map_.next_key::()?.is_some() { - let _ = map_.next_value::()?; + let mut trunk_ids__ = None; + let mut numbers__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::TrunkIds => { + if trunk_ids__.is_some() { + return Err(serde::de::Error::duplicate_field("trunkIds")); + } + trunk_ids__ = Some(map_.next_value()?); + } + GeneratedField::Numbers => { + if numbers__.is_some() { + return Err(serde::de::Error::duplicate_field("numbers")); + } + numbers__ = Some(map_.next_value()?); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } } Ok(ListSipInboundTrunkRequest { + trunk_ids: trunk_ids__.unwrap_or_default(), + numbers: numbers__.unwrap_or_default(), }) } } @@ -14889,8 +15299,20 @@ impl serde::Serialize for ListSipOutboundTrunkRequest { S: serde::Serializer, { use serde::ser::SerializeStruct; - let len = 0; - let struct_ser = serializer.serialize_struct("livekit.ListSIPOutboundTrunkRequest", len)?; + let mut len = 0; + if !self.trunk_ids.is_empty() { + len += 1; + } + if !self.numbers.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.ListSIPOutboundTrunkRequest", len)?; + if !self.trunk_ids.is_empty() { + struct_ser.serialize_field("trunkIds", &self.trunk_ids)?; + } + if !self.numbers.is_empty() { + struct_ser.serialize_field("numbers", &self.numbers)?; + } struct_ser.end() } } @@ -14901,10 +15323,15 @@ impl<'de> serde::Deserialize<'de> for ListSipOutboundTrunkRequest { D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ + "trunk_ids", + "trunkIds", + "numbers", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { + TrunkIds, + Numbers, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -14926,7 +15353,11 @@ impl<'de> serde::Deserialize<'de> for ListSipOutboundTrunkRequest { where E: serde::de::Error, { - Ok(GeneratedField::__SkipField__) + match value { + "trunkIds" | "trunk_ids" => Ok(GeneratedField::TrunkIds), + "numbers" => Ok(GeneratedField::Numbers), + _ => Ok(GeneratedField::__SkipField__), + } } } deserializer.deserialize_identifier(GeneratedVisitor) @@ -14944,10 +15375,30 @@ impl<'de> serde::Deserialize<'de> for ListSipOutboundTrunkRequest { where V: serde::de::MapAccess<'de>, { - while map_.next_key::()?.is_some() { - let _ = map_.next_value::()?; + let mut trunk_ids__ = None; + let mut numbers__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::TrunkIds => { + if trunk_ids__.is_some() { + return Err(serde::de::Error::duplicate_field("trunkIds")); + } + trunk_ids__ = Some(map_.next_value()?); + } + GeneratedField::Numbers => { + if numbers__.is_some() { + return Err(serde::de::Error::duplicate_field("numbers")); + } + numbers__ = Some(map_.next_value()?); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } } Ok(ListSipOutboundTrunkRequest { + trunk_ids: trunk_ids__.unwrap_or_default(), + numbers: numbers__.unwrap_or_default(), }) } } @@ -22895,22 +23346,96 @@ impl<'de> serde::Deserialize<'de> for S3Upload { } } } - Ok(S3Upload { - access_key: access_key__.unwrap_or_default(), - secret: secret__.unwrap_or_default(), - session_token: session_token__.unwrap_or_default(), - region: region__.unwrap_or_default(), - endpoint: endpoint__.unwrap_or_default(), - bucket: bucket__.unwrap_or_default(), - force_path_style: force_path_style__.unwrap_or_default(), - metadata: metadata__.unwrap_or_default(), - tagging: tagging__.unwrap_or_default(), - content_disposition: content_disposition__.unwrap_or_default(), - proxy: proxy__, - }) + Ok(S3Upload { + access_key: access_key__.unwrap_or_default(), + secret: secret__.unwrap_or_default(), + session_token: session_token__.unwrap_or_default(), + region: region__.unwrap_or_default(), + endpoint: endpoint__.unwrap_or_default(), + bucket: bucket__.unwrap_or_default(), + force_path_style: force_path_style__.unwrap_or_default(), + metadata: metadata__.unwrap_or_default(), + tagging: tagging__.unwrap_or_default(), + content_disposition: content_disposition__.unwrap_or_default(), + proxy: proxy__, + }) + } + } + deserializer.deserialize_struct("livekit.S3Upload", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for SipCallDirection { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + let variant = match self { + Self::ScdUnknown => "SCD_UNKNOWN", + Self::ScdInbound => "SCD_INBOUND", + Self::ScdOutbound => "SCD_OUTBOUND", + }; + serializer.serialize_str(variant) + } +} +impl<'de> serde::Deserialize<'de> for SipCallDirection { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "SCD_UNKNOWN", + "SCD_INBOUND", + "SCD_OUTBOUND", + ]; + + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = SipCallDirection; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + fn visit_i64(self, v: i64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self) + }) + } + + fn visit_u64(self, v: u64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(v), &self) + }) + } + + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "SCD_UNKNOWN" => Ok(SipCallDirection::ScdUnknown), + "SCD_INBOUND" => Ok(SipCallDirection::ScdInbound), + "SCD_OUTBOUND" => Ok(SipCallDirection::ScdOutbound), + _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), + } } } - deserializer.deserialize_struct("livekit.S3Upload", FIELDS, GeneratedVisitor) + deserializer.deserialize_any(GeneratedVisitor) } } impl serde::Serialize for SipCallInfo { @@ -22945,6 +23470,9 @@ impl serde::Serialize for SipCallInfo { if !self.enabled_features.is_empty() { len += 1; } + if self.call_direction != 0 { + len += 1; + } if self.call_status != 0 { len += 1; } @@ -22992,6 +23520,11 @@ impl serde::Serialize for SipCallInfo { }).collect::, _>>()?; struct_ser.serialize_field("enabledFeatures", &v)?; } + if self.call_direction != 0 { + let v = SipCallDirection::try_from(self.call_direction) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.call_direction)))?; + struct_ser.serialize_field("callDirection", &v)?; + } if self.call_status != 0 { let v = SipCallStatus::try_from(self.call_status) .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.call_status)))?; @@ -23046,6 +23579,8 @@ impl<'de> serde::Deserialize<'de> for SipCallInfo { "toUri", "enabled_features", "enabledFeatures", + "call_direction", + "callDirection", "call_status", "callStatus", "created_at", @@ -23069,6 +23604,7 @@ impl<'de> serde::Deserialize<'de> for SipCallInfo { FromUri, ToUri, EnabledFeatures, + CallDirection, CallStatus, CreatedAt, StartedAt, @@ -23105,6 +23641,7 @@ impl<'de> serde::Deserialize<'de> for SipCallInfo { "fromUri" | "from_uri" => Ok(GeneratedField::FromUri), "toUri" | "to_uri" => Ok(GeneratedField::ToUri), "enabledFeatures" | "enabled_features" => Ok(GeneratedField::EnabledFeatures), + "callDirection" | "call_direction" => Ok(GeneratedField::CallDirection), "callStatus" | "call_status" => Ok(GeneratedField::CallStatus), "createdAt" | "created_at" => Ok(GeneratedField::CreatedAt), "startedAt" | "started_at" => Ok(GeneratedField::StartedAt), @@ -23138,6 +23675,7 @@ impl<'de> serde::Deserialize<'de> for SipCallInfo { let mut from_uri__ = None; let mut to_uri__ = None; let mut enabled_features__ = None; + let mut call_direction__ = None; let mut call_status__ = None; let mut created_at__ = None; let mut started_at__ = None; @@ -23194,6 +23732,12 @@ impl<'de> serde::Deserialize<'de> for SipCallInfo { } enabled_features__ = Some(map_.next_value::>()?.into_iter().map(|x| x as i32).collect()); } + GeneratedField::CallDirection => { + if call_direction__.is_some() { + return Err(serde::de::Error::duplicate_field("callDirection")); + } + call_direction__ = Some(map_.next_value::()? as i32); + } GeneratedField::CallStatus => { if call_status__.is_some() { return Err(serde::de::Error::duplicate_field("callStatus")); @@ -23250,6 +23794,7 @@ impl<'de> serde::Deserialize<'de> for SipCallInfo { from_uri: from_uri__, to_uri: to_uri__, enabled_features: enabled_features__.unwrap_or_default(), + call_direction: call_direction__.unwrap_or_default(), call_status: call_status__.unwrap_or_default(), created_at: created_at__.unwrap_or_default(), started_at: started_at__.unwrap_or_default(), @@ -23859,6 +24404,18 @@ impl serde::Serialize for SipDispatchRuleInfo { if !self.attributes.is_empty() { len += 1; } + if !self.room_preset.is_empty() { + len += 1; + } + if self.room_config.is_some() { + len += 1; + } + if self.krisp_enabled { + len += 1; + } + if self.media_encryption != 0 { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.SIPDispatchRuleInfo", len)?; if !self.sip_dispatch_rule_id.is_empty() { struct_ser.serialize_field("sipDispatchRuleId", &self.sip_dispatch_rule_id)?; @@ -23884,6 +24441,20 @@ impl serde::Serialize for SipDispatchRuleInfo { if !self.attributes.is_empty() { struct_ser.serialize_field("attributes", &self.attributes)?; } + if !self.room_preset.is_empty() { + struct_ser.serialize_field("roomPreset", &self.room_preset)?; + } + if let Some(v) = self.room_config.as_ref() { + struct_ser.serialize_field("roomConfig", v)?; + } + if self.krisp_enabled { + struct_ser.serialize_field("krispEnabled", &self.krisp_enabled)?; + } + if self.media_encryption != 0 { + let v = SipMediaEncryption::try_from(self.media_encryption) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.media_encryption)))?; + struct_ser.serialize_field("mediaEncryption", &v)?; + } struct_ser.end() } } @@ -23906,6 +24477,14 @@ impl<'de> serde::Deserialize<'de> for SipDispatchRuleInfo { "name", "metadata", "attributes", + "room_preset", + "roomPreset", + "room_config", + "roomConfig", + "krisp_enabled", + "krispEnabled", + "media_encryption", + "mediaEncryption", ]; #[allow(clippy::enum_variant_names)] @@ -23918,6 +24497,10 @@ impl<'de> serde::Deserialize<'de> for SipDispatchRuleInfo { Name, Metadata, Attributes, + RoomPreset, + RoomConfig, + KrispEnabled, + MediaEncryption, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -23948,6 +24531,10 @@ impl<'de> serde::Deserialize<'de> for SipDispatchRuleInfo { "name" => Ok(GeneratedField::Name), "metadata" => Ok(GeneratedField::Metadata), "attributes" => Ok(GeneratedField::Attributes), + "roomPreset" | "room_preset" => Ok(GeneratedField::RoomPreset), + "roomConfig" | "room_config" => Ok(GeneratedField::RoomConfig), + "krispEnabled" | "krisp_enabled" => Ok(GeneratedField::KrispEnabled), + "mediaEncryption" | "media_encryption" => Ok(GeneratedField::MediaEncryption), _ => Ok(GeneratedField::__SkipField__), } } @@ -23975,6 +24562,10 @@ impl<'de> serde::Deserialize<'de> for SipDispatchRuleInfo { let mut name__ = None; let mut metadata__ = None; let mut attributes__ = None; + let mut room_preset__ = None; + let mut room_config__ = None; + let mut krisp_enabled__ = None; + let mut media_encryption__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::SipDispatchRuleId => { @@ -24027,6 +24618,30 @@ impl<'de> serde::Deserialize<'de> for SipDispatchRuleInfo { map_.next_value::>()? ); } + GeneratedField::RoomPreset => { + if room_preset__.is_some() { + return Err(serde::de::Error::duplicate_field("roomPreset")); + } + room_preset__ = Some(map_.next_value()?); + } + GeneratedField::RoomConfig => { + if room_config__.is_some() { + return Err(serde::de::Error::duplicate_field("roomConfig")); + } + room_config__ = map_.next_value()?; + } + GeneratedField::KrispEnabled => { + if krisp_enabled__.is_some() { + return Err(serde::de::Error::duplicate_field("krispEnabled")); + } + krisp_enabled__ = Some(map_.next_value()?); + } + GeneratedField::MediaEncryption => { + if media_encryption__.is_some() { + return Err(serde::de::Error::duplicate_field("mediaEncryption")); + } + media_encryption__ = Some(map_.next_value::()? as i32); + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -24041,6 +24656,10 @@ impl<'de> serde::Deserialize<'de> for SipDispatchRuleInfo { name: name__.unwrap_or_default(), metadata: metadata__.unwrap_or_default(), attributes: attributes__.unwrap_or_default(), + room_preset: room_preset__.unwrap_or_default(), + room_config: room_config__, + krisp_enabled: krisp_enabled__.unwrap_or_default(), + media_encryption: media_encryption__.unwrap_or_default(), }) } } @@ -24118,6 +24737,80 @@ impl<'de> serde::Deserialize<'de> for SipFeature { deserializer.deserialize_any(GeneratedVisitor) } } +impl serde::Serialize for SipHeaderOptions { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + let variant = match self { + Self::SipNoHeaders => "SIP_NO_HEADERS", + Self::SipXHeaders => "SIP_X_HEADERS", + Self::SipAllHeaders => "SIP_ALL_HEADERS", + }; + serializer.serialize_str(variant) + } +} +impl<'de> serde::Deserialize<'de> for SipHeaderOptions { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "SIP_NO_HEADERS", + "SIP_X_HEADERS", + "SIP_ALL_HEADERS", + ]; + + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = SipHeaderOptions; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + fn visit_i64(self, v: i64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self) + }) + } + + fn visit_u64(self, v: u64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(v), &self) + }) + } + + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "SIP_NO_HEADERS" => Ok(SipHeaderOptions::SipNoHeaders), + "SIP_X_HEADERS" => Ok(SipHeaderOptions::SipXHeaders), + "SIP_ALL_HEADERS" => Ok(SipHeaderOptions::SipAllHeaders), + _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), + } + } + } + deserializer.deserialize_any(GeneratedVisitor) + } +} impl serde::Serialize for SipInboundTrunkInfo { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -24159,6 +24852,9 @@ impl serde::Serialize for SipInboundTrunkInfo { if !self.attributes_to_headers.is_empty() { len += 1; } + if self.include_headers != 0 { + len += 1; + } if self.ringing_timeout.is_some() { len += 1; } @@ -24168,6 +24864,9 @@ impl serde::Serialize for SipInboundTrunkInfo { if self.krisp_enabled { len += 1; } + if self.media_encryption != 0 { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.SIPInboundTrunkInfo", len)?; if !self.sip_trunk_id.is_empty() { struct_ser.serialize_field("sipTrunkId", &self.sip_trunk_id)?; @@ -24202,6 +24901,11 @@ impl serde::Serialize for SipInboundTrunkInfo { if !self.attributes_to_headers.is_empty() { struct_ser.serialize_field("attributesToHeaders", &self.attributes_to_headers)?; } + if self.include_headers != 0 { + let v = SipHeaderOptions::try_from(self.include_headers) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.include_headers)))?; + struct_ser.serialize_field("includeHeaders", &v)?; + } if let Some(v) = self.ringing_timeout.as_ref() { struct_ser.serialize_field("ringingTimeout", v)?; } @@ -24211,6 +24915,11 @@ impl serde::Serialize for SipInboundTrunkInfo { if self.krisp_enabled { struct_ser.serialize_field("krispEnabled", &self.krisp_enabled)?; } + if self.media_encryption != 0 { + let v = SipMediaEncryption::try_from(self.media_encryption) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.media_encryption)))?; + struct_ser.serialize_field("mediaEncryption", &v)?; + } struct_ser.end() } } @@ -24239,12 +24948,16 @@ impl<'de> serde::Deserialize<'de> for SipInboundTrunkInfo { "headersToAttributes", "attributes_to_headers", "attributesToHeaders", + "include_headers", + "includeHeaders", "ringing_timeout", "ringingTimeout", "max_call_duration", "maxCallDuration", "krisp_enabled", "krispEnabled", + "media_encryption", + "mediaEncryption", ]; #[allow(clippy::enum_variant_names)] @@ -24260,9 +24973,11 @@ impl<'de> serde::Deserialize<'de> for SipInboundTrunkInfo { Headers, HeadersToAttributes, AttributesToHeaders, + IncludeHeaders, RingingTimeout, MaxCallDuration, KrispEnabled, + MediaEncryption, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -24296,9 +25011,11 @@ impl<'de> serde::Deserialize<'de> for SipInboundTrunkInfo { "headers" => Ok(GeneratedField::Headers), "headersToAttributes" | "headers_to_attributes" => Ok(GeneratedField::HeadersToAttributes), "attributesToHeaders" | "attributes_to_headers" => Ok(GeneratedField::AttributesToHeaders), + "includeHeaders" | "include_headers" => Ok(GeneratedField::IncludeHeaders), "ringingTimeout" | "ringing_timeout" => Ok(GeneratedField::RingingTimeout), "maxCallDuration" | "max_call_duration" => Ok(GeneratedField::MaxCallDuration), "krispEnabled" | "krisp_enabled" => Ok(GeneratedField::KrispEnabled), + "mediaEncryption" | "media_encryption" => Ok(GeneratedField::MediaEncryption), _ => Ok(GeneratedField::__SkipField__), } } @@ -24329,9 +25046,11 @@ impl<'de> serde::Deserialize<'de> for SipInboundTrunkInfo { let mut headers__ = None; let mut headers_to_attributes__ = None; let mut attributes_to_headers__ = None; + let mut include_headers__ = None; let mut ringing_timeout__ = None; let mut max_call_duration__ = None; let mut krisp_enabled__ = None; + let mut media_encryption__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::SipTrunkId => { @@ -24406,6 +25125,12 @@ impl<'de> serde::Deserialize<'de> for SipInboundTrunkInfo { map_.next_value::>()? ); } + GeneratedField::IncludeHeaders => { + if include_headers__.is_some() { + return Err(serde::de::Error::duplicate_field("includeHeaders")); + } + include_headers__ = Some(map_.next_value::()? as i32); + } GeneratedField::RingingTimeout => { if ringing_timeout__.is_some() { return Err(serde::de::Error::duplicate_field("ringingTimeout")); @@ -24424,6 +25149,12 @@ impl<'de> serde::Deserialize<'de> for SipInboundTrunkInfo { } krisp_enabled__ = Some(map_.next_value()?); } + GeneratedField::MediaEncryption => { + if media_encryption__.is_some() { + return Err(serde::de::Error::duplicate_field("mediaEncryption")); + } + media_encryption__ = Some(map_.next_value::()? as i32); + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -24441,15 +25172,91 @@ impl<'de> serde::Deserialize<'de> for SipInboundTrunkInfo { headers: headers__.unwrap_or_default(), headers_to_attributes: headers_to_attributes__.unwrap_or_default(), attributes_to_headers: attributes_to_headers__.unwrap_or_default(), + include_headers: include_headers__.unwrap_or_default(), ringing_timeout: ringing_timeout__, max_call_duration: max_call_duration__, krisp_enabled: krisp_enabled__.unwrap_or_default(), + media_encryption: media_encryption__.unwrap_or_default(), }) } } deserializer.deserialize_struct("livekit.SIPInboundTrunkInfo", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for SipMediaEncryption { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + let variant = match self { + Self::SipMediaEncryptDisable => "SIP_MEDIA_ENCRYPT_DISABLE", + Self::SipMediaEncryptAllow => "SIP_MEDIA_ENCRYPT_ALLOW", + Self::SipMediaEncryptRequire => "SIP_MEDIA_ENCRYPT_REQUIRE", + }; + serializer.serialize_str(variant) + } +} +impl<'de> serde::Deserialize<'de> for SipMediaEncryption { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "SIP_MEDIA_ENCRYPT_DISABLE", + "SIP_MEDIA_ENCRYPT_ALLOW", + "SIP_MEDIA_ENCRYPT_REQUIRE", + ]; + + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = SipMediaEncryption; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + fn visit_i64(self, v: i64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self) + }) + } + + fn visit_u64(self, v: u64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(v), &self) + }) + } + + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "SIP_MEDIA_ENCRYPT_DISABLE" => Ok(SipMediaEncryption::SipMediaEncryptDisable), + "SIP_MEDIA_ENCRYPT_ALLOW" => Ok(SipMediaEncryption::SipMediaEncryptAllow), + "SIP_MEDIA_ENCRYPT_REQUIRE" => Ok(SipMediaEncryption::SipMediaEncryptRequire), + _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), + } + } + } + deserializer.deserialize_any(GeneratedVisitor) + } +} impl serde::Serialize for SipOutboundTrunkInfo { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -24491,6 +25298,12 @@ impl serde::Serialize for SipOutboundTrunkInfo { if !self.attributes_to_headers.is_empty() { len += 1; } + if self.include_headers != 0 { + len += 1; + } + if self.media_encryption != 0 { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.SIPOutboundTrunkInfo", len)?; if !self.sip_trunk_id.is_empty() { struct_ser.serialize_field("sipTrunkId", &self.sip_trunk_id)?; @@ -24527,6 +25340,16 @@ impl serde::Serialize for SipOutboundTrunkInfo { if !self.attributes_to_headers.is_empty() { struct_ser.serialize_field("attributesToHeaders", &self.attributes_to_headers)?; } + if self.include_headers != 0 { + let v = SipHeaderOptions::try_from(self.include_headers) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.include_headers)))?; + struct_ser.serialize_field("includeHeaders", &v)?; + } + if self.media_encryption != 0 { + let v = SipMediaEncryption::try_from(self.media_encryption) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.media_encryption)))?; + struct_ser.serialize_field("mediaEncryption", &v)?; + } struct_ser.end() } } @@ -24553,6 +25376,10 @@ impl<'de> serde::Deserialize<'de> for SipOutboundTrunkInfo { "headersToAttributes", "attributes_to_headers", "attributesToHeaders", + "include_headers", + "includeHeaders", + "media_encryption", + "mediaEncryption", ]; #[allow(clippy::enum_variant_names)] @@ -24568,6 +25395,8 @@ impl<'de> serde::Deserialize<'de> for SipOutboundTrunkInfo { Headers, HeadersToAttributes, AttributesToHeaders, + IncludeHeaders, + MediaEncryption, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -24601,6 +25430,8 @@ impl<'de> serde::Deserialize<'de> for SipOutboundTrunkInfo { "headers" => Ok(GeneratedField::Headers), "headersToAttributes" | "headers_to_attributes" => Ok(GeneratedField::HeadersToAttributes), "attributesToHeaders" | "attributes_to_headers" => Ok(GeneratedField::AttributesToHeaders), + "includeHeaders" | "include_headers" => Ok(GeneratedField::IncludeHeaders), + "mediaEncryption" | "media_encryption" => Ok(GeneratedField::MediaEncryption), _ => Ok(GeneratedField::__SkipField__), } } @@ -24631,6 +25462,8 @@ impl<'de> serde::Deserialize<'de> for SipOutboundTrunkInfo { let mut headers__ = None; let mut headers_to_attributes__ = None; let mut attributes_to_headers__ = None; + let mut include_headers__ = None; + let mut media_encryption__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::SipTrunkId => { @@ -24705,6 +25538,18 @@ impl<'de> serde::Deserialize<'de> for SipOutboundTrunkInfo { map_.next_value::>()? ); } + GeneratedField::IncludeHeaders => { + if include_headers__.is_some() { + return Err(serde::de::Error::duplicate_field("includeHeaders")); + } + include_headers__ = Some(map_.next_value::()? as i32); + } + GeneratedField::MediaEncryption => { + if media_encryption__.is_some() { + return Err(serde::de::Error::duplicate_field("mediaEncryption")); + } + media_encryption__ = Some(map_.next_value::()? as i32); + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -24722,6 +25567,8 @@ impl<'de> serde::Deserialize<'de> for SipOutboundTrunkInfo { headers: headers__.unwrap_or_default(), headers_to_attributes: headers_to_attributes__.unwrap_or_default(), attributes_to_headers: attributes_to_headers__.unwrap_or_default(), + include_headers: include_headers__.unwrap_or_default(), + media_encryption: media_encryption__.unwrap_or_default(), }) } } @@ -32730,6 +33577,9 @@ impl serde::Serialize for TransferSipParticipantRequest { if self.play_dialtone { len += 1; } + if !self.headers.is_empty() { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.TransferSIPParticipantRequest", len)?; if !self.participant_identity.is_empty() { struct_ser.serialize_field("participantIdentity", &self.participant_identity)?; @@ -32743,6 +33593,9 @@ impl serde::Serialize for TransferSipParticipantRequest { if self.play_dialtone { struct_ser.serialize_field("playDialtone", &self.play_dialtone)?; } + if !self.headers.is_empty() { + struct_ser.serialize_field("headers", &self.headers)?; + } struct_ser.end() } } @@ -32761,6 +33614,7 @@ impl<'de> serde::Deserialize<'de> for TransferSipParticipantRequest { "transferTo", "play_dialtone", "playDialtone", + "headers", ]; #[allow(clippy::enum_variant_names)] @@ -32769,6 +33623,7 @@ impl<'de> serde::Deserialize<'de> for TransferSipParticipantRequest { RoomName, TransferTo, PlayDialtone, + Headers, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -32795,6 +33650,7 @@ impl<'de> serde::Deserialize<'de> for TransferSipParticipantRequest { "roomName" | "room_name" => Ok(GeneratedField::RoomName), "transferTo" | "transfer_to" => Ok(GeneratedField::TransferTo), "playDialtone" | "play_dialtone" => Ok(GeneratedField::PlayDialtone), + "headers" => Ok(GeneratedField::Headers), _ => Ok(GeneratedField::__SkipField__), } } @@ -32818,6 +33674,7 @@ impl<'de> serde::Deserialize<'de> for TransferSipParticipantRequest { let mut room_name__ = None; let mut transfer_to__ = None; let mut play_dialtone__ = None; + let mut headers__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::ParticipantIdentity => { @@ -32844,6 +33701,14 @@ impl<'de> serde::Deserialize<'de> for TransferSipParticipantRequest { } play_dialtone__ = Some(map_.next_value()?); } + GeneratedField::Headers => { + if headers__.is_some() { + return Err(serde::de::Error::duplicate_field("headers")); + } + headers__ = Some( + map_.next_value::>()? + ); + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -32854,6 +33719,7 @@ impl<'de> serde::Deserialize<'de> for TransferSipParticipantRequest { room_name: room_name__.unwrap_or_default(), transfer_to: transfer_to__.unwrap_or_default(), play_dialtone: play_dialtone__.unwrap_or_default(), + headers: headers__.unwrap_or_default(), }) } } @@ -33029,6 +33895,9 @@ impl serde::Serialize for UpdateIngressRequest { if self.video.is_some() { len += 1; } + if self.enabled.is_some() { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.UpdateIngressRequest", len)?; if !self.ingress_id.is_empty() { struct_ser.serialize_field("ingressId", &self.ingress_id)?; @@ -33060,6 +33929,9 @@ impl serde::Serialize for UpdateIngressRequest { if let Some(v) = self.video.as_ref() { struct_ser.serialize_field("video", v)?; } + if let Some(v) = self.enabled.as_ref() { + struct_ser.serialize_field("enabled", v)?; + } struct_ser.end() } } @@ -33087,6 +33959,7 @@ impl<'de> serde::Deserialize<'de> for UpdateIngressRequest { "enableTranscoding", "audio", "video", + "enabled", ]; #[allow(clippy::enum_variant_names)] @@ -33101,6 +33974,7 @@ impl<'de> serde::Deserialize<'de> for UpdateIngressRequest { EnableTranscoding, Audio, Video, + Enabled, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -33133,6 +34007,7 @@ impl<'de> serde::Deserialize<'de> for UpdateIngressRequest { "enableTranscoding" | "enable_transcoding" => Ok(GeneratedField::EnableTranscoding), "audio" => Ok(GeneratedField::Audio), "video" => Ok(GeneratedField::Video), + "enabled" => Ok(GeneratedField::Enabled), _ => Ok(GeneratedField::__SkipField__), } } @@ -33162,6 +34037,7 @@ impl<'de> serde::Deserialize<'de> for UpdateIngressRequest { let mut enable_transcoding__ = None; let mut audio__ = None; let mut video__ = None; + let mut enabled__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::IngressId => { @@ -33224,6 +34100,12 @@ impl<'de> serde::Deserialize<'de> for UpdateIngressRequest { } video__ = map_.next_value()?; } + GeneratedField::Enabled => { + if enabled__.is_some() { + return Err(serde::de::Error::duplicate_field("enabled")); + } + enabled__ = map_.next_value()?; + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -33240,6 +34122,7 @@ impl<'de> serde::Deserialize<'de> for UpdateIngressRequest { enable_transcoding: enable_transcoding__, audio: audio__, video: video__, + enabled: enabled__, }) } } From 50cb059cbdbd5322e7fcda80d596633c78144cf7 Mon Sep 17 00:00:00 2001 From: lukasIO Date: Fri, 17 Jan 2025 15:29:39 +0100 Subject: [PATCH 126/274] Add data_stream trailer event (#549) * Add stream trailer room event for FFI forwarding * add event chain * add ffi forwarding * typo * update proto --- livekit-ffi/protocol/room.proto | 6 ++++++ livekit-ffi/src/livekit.proto.rs | 13 ++++++++++++- livekit-ffi/src/server/room.rs | 5 +++++ livekit/src/room/mod.rs | 16 ++++++++++++++++ livekit/src/rtc_engine/mod.rs | 9 +++++++++ livekit/src/rtc_engine/rtc_session.rs | 10 ++++++++++ 6 files changed, 58 insertions(+), 1 deletion(-) diff --git a/livekit-ffi/protocol/room.proto b/livekit-ffi/protocol/room.proto index f1d68e5c4..aa10986d0 100644 --- a/livekit-ffi/protocol/room.proto +++ b/livekit-ffi/protocol/room.proto @@ -369,6 +369,7 @@ message RoomEvent { ChatMessageReceived chat_message = 29; DataStreamHeaderReceived stream_header_received = 30; DataStreamChunkReceived stream_chunk_received = 31; + DataStreamTrailerReceived stream_trailer_received = 32; } } @@ -594,6 +595,11 @@ message DataStreamChunkReceived { required DataStream.Chunk chunk = 2; } +message DataStreamTrailerReceived { + required string participant_identity = 1; + required DataStream.Trailer trailer = 2; +} + message SendStreamHeaderRequest { required uint64 local_participant_handle = 1; required DataStream.Header header = 2; diff --git a/livekit-ffi/src/livekit.proto.rs b/livekit-ffi/src/livekit.proto.rs index f987e3b4f..45e0cd05a 100644 --- a/livekit-ffi/src/livekit.proto.rs +++ b/livekit-ffi/src/livekit.proto.rs @@ -1,4 +1,5 @@ // @generated +// This file is @generated by prost-build. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FrameCryptor { @@ -2634,7 +2635,7 @@ pub struct OwnedBuffer { pub struct RoomEvent { #[prost(uint64, required, tag="1")] pub room_handle: u64, - #[prost(oneof="room_event::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31")] + #[prost(oneof="room_event::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32")] pub message: ::core::option::Option, } /// Nested message and enum types in `RoomEvent`. @@ -2704,6 +2705,8 @@ pub mod room_event { StreamHeaderReceived(super::DataStreamHeaderReceived), #[prost(message, tag="31")] StreamChunkReceived(super::DataStreamChunkReceived), + #[prost(message, tag="32")] + StreamTrailerReceived(super::DataStreamTrailerReceived), } } #[allow(clippy::derive_partial_eq_without_eq)] @@ -3130,6 +3133,14 @@ pub struct DataStreamChunkReceived { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct DataStreamTrailerReceived { + #[prost(string, required, tag="1")] + pub participant_identity: ::prost::alloc::string::String, + #[prost(message, required, tag="2")] + pub trailer: data_stream::Trailer, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct SendStreamHeaderRequest { #[prost(uint64, required, tag="1")] pub local_participant_handle: u64, diff --git a/livekit-ffi/src/server/room.rs b/livekit-ffi/src/server/room.rs index 61d32161b..fcfeee261 100644 --- a/livekit-ffi/src/server/room.rs +++ b/livekit-ffi/src/server/room.rs @@ -1241,6 +1241,11 @@ async fn forward_event( proto::DataStreamChunkReceived { chunk: chunk.into(), participant_identity }, )); } + RoomEvent::StreamTrailerReceived { trailer, participant_identity } => { + let _ = send_event(proto::room_event::Message::StreamTrailerReceived( + proto::DataStreamTrailerReceived { trailer: trailer.into(), participant_identity }, + )); + } _ => { log::warn!("unhandled room event: {:?}", event); } diff --git a/livekit/src/room/mod.rs b/livekit/src/room/mod.rs index 051b56f05..9437e5ff0 100644 --- a/livekit/src/room/mod.rs +++ b/livekit/src/room/mod.rs @@ -176,6 +176,10 @@ pub enum RoomEvent { chunk: proto::data_stream::Chunk, participant_identity: String, }, + StreamTrailerReceived { + trailer: proto::data_stream::Trailer, + participant_identity: String, + }, E2eeStateChanged { participant: Participant, state: EncryptionState, @@ -734,6 +738,9 @@ impl RoomSession { EngineEvent::DataStreamChunk { chunk, participant_identity } => { self.handle_data_stream_chunk(chunk, participant_identity); } + EngineEvent::DataStreamTrailer { trailer, participant_identity } => { + self.handle_data_stream_trailer(trailer, participant_identity); + } _ => {} } @@ -1262,6 +1269,15 @@ impl RoomSession { self.dispatcher.dispatch(&event); } + fn handle_data_stream_trailer( + &self, + trailer: proto::data_stream::Trailer, + participant_identity: String, + ) { + let event = RoomEvent::StreamTrailerReceived { trailer, participant_identity }; + self.dispatcher.dispatch(&event); + } + /// Create a new participant /// Also add it to the participants list fn create_participant( diff --git a/livekit/src/rtc_engine/mod.rs b/livekit/src/rtc_engine/mod.rs index 2cd545215..0b2059095 100644 --- a/livekit/src/rtc_engine/mod.rs +++ b/livekit/src/rtc_engine/mod.rs @@ -167,6 +167,10 @@ pub enum EngineEvent { chunk: proto::data_stream::Chunk, participant_identity: String, }, + DataStreamTrailer { + trailer: proto::data_stream::Trailer, + participant_identity: String, + }, } /// Represents a running RtcSession with the ability to close the session @@ -542,6 +546,11 @@ impl EngineInner { .engine_tx .send(EngineEvent::DataStreamChunk { chunk, participant_identity }); } + SessionEvent::DataStreamTrailer { trailer, participant_identity } => { + let _ = self + .engine_tx + .send(EngineEvent::DataStreamTrailer { trailer, participant_identity }); + } } Ok(()) } diff --git a/livekit/src/rtc_engine/rtc_session.rs b/livekit/src/rtc_engine/rtc_session.rs index 91dcbfddb..8de9d9593 100644 --- a/livekit/src/rtc_engine/rtc_session.rs +++ b/livekit/src/rtc_engine/rtc_session.rs @@ -143,6 +143,10 @@ pub enum SessionEvent { chunk: proto::data_stream::Chunk, participant_identity: String, }, + DataStreamTrailer { + trailer: proto::data_stream::Trailer, + participant_identity: String, + }, } #[derive(Serialize, Deserialize)] @@ -743,6 +747,12 @@ impl SessionInner { participant_identity: data.participant_identity.clone(), }); } + proto::data_packet::Value::StreamTrailer(message) => { + let _ = self.emitter.send(SessionEvent::DataStreamTrailer { + trailer: message.clone(), + participant_identity: data.participant_identity.clone(), + }); + } _ => {} } } From f16241314cdab42f0ecd4c1268498bb4f64b7668 Mon Sep 17 00:00:00 2001 From: Daisuke Murase Date: Fri, 17 Jan 2025 09:07:42 -0800 Subject: [PATCH 127/274] Make `publish_data` wait until the DataChannel's bufferedAmount becomes low. (#545) * expose buffered_amount method to Rust * test to implement wait_for_dc_buffer_low * remove wait_for_low function, add functionality to wait it in publish_data * test FFI implementation * add callback * revert unused changes * not necessary to make this async * update lock * add nanpa changeset * create dc_task for more reliable data publishing * change get/set dc buffered_amount_low_threshold FFI functions to support Lossy kind as well * fmt * add logs if buffer amount become unexpected value * set default threshold to 2MB * fmt * ignore error here * add buffered_amount_low_threshold in RoomInfo * remove Get ffi function for dc buffered_low_threshold, instead, add it to RoomInfo * update changeset * flatten DataChannelOptions in protobuf * fmt --- .nanpa/dc-buffered-amount-low-threshold.kdl | 6 + libwebrtc/src/data_channel.rs | 4 + libwebrtc/src/native/data_channel.rs | 4 + livekit-ffi/protocol/ffi.proto | 6 + livekit-ffi/protocol/room.proto | 17 ++ livekit-ffi/src/conversion/room.rs | 6 + livekit-ffi/src/livekit.proto.rs | 41 +++- livekit-ffi/src/server/requests.rs | 19 ++ livekit-ffi/src/server/room.rs | 19 ++ livekit/src/room/mod.rs | 49 ++++- .../src/room/participant/local_participant.rs | 57 +++-- livekit/src/rtc_engine/mod.rs | 13 +- livekit/src/rtc_engine/rtc_events.rs | 23 ++- livekit/src/rtc_engine/rtc_session.rs | 195 ++++++++++++++++-- webrtc-sys/include/livekit/data_channel.h | 1 + webrtc-sys/src/data_channel.cpp | 4 + webrtc-sys/src/data_channel.rs | 1 + 17 files changed, 411 insertions(+), 54 deletions(-) create mode 100644 .nanpa/dc-buffered-amount-low-threshold.kdl diff --git a/.nanpa/dc-buffered-amount-low-threshold.kdl b/.nanpa/dc-buffered-amount-low-threshold.kdl new file mode 100644 index 000000000..e5a46b931 --- /dev/null +++ b/.nanpa/dc-buffered-amount-low-threshold.kdl @@ -0,0 +1,6 @@ +patch type="added" package="libwebrtc" "Expose DataChannel.bufferedAmount property" +patch type="fixed" package="livekit" "Wait for the buffered amount to become low before sending data during publish_data for Reliable Data Channel" +patch type="added" package="livekit" "Add an API to set buffer_amount_low_threshold for DataChannel" +patch type="added" package="livekit" "Update RoomInfo to contain buffer_amount_low_threshold for DataChannel" +patch type="added" package="livekit-ffi" "Add an API to set buffer_amount_low_threshold for DataChannel" +patch type="added" package="livekit-ffi" "Update RoomInfo to contain buffer_amount_low_threshold for DataChannel" diff --git a/libwebrtc/src/data_channel.rs b/libwebrtc/src/data_channel.rs index a8ac1f0e8..3b1629efa 100644 --- a/libwebrtc/src/data_channel.rs +++ b/libwebrtc/src/data_channel.rs @@ -97,6 +97,10 @@ impl DataChannel { self.handle.close() } + pub fn buffered_amount(&self) -> u64 { + self.handle.buffered_amount() + } + pub fn on_state_change(&self, callback: Option) { self.handle.on_state_change(callback) } diff --git a/libwebrtc/src/native/data_channel.rs b/libwebrtc/src/native/data_channel.rs index ba36f041e..fa5e6b75b 100644 --- a/libwebrtc/src/native/data_channel.rs +++ b/libwebrtc/src/native/data_channel.rs @@ -94,6 +94,10 @@ impl DataChannel { self.sys_handle.close(); } + pub fn buffered_amount(&self) -> u64 { + self.sys_handle.buffered_amount() + } + pub fn on_state_change(&self, handler: Option) { *self.observer.state_change_handler.lock() = handler; } diff --git a/livekit-ffi/protocol/ffi.proto b/livekit-ffi/protocol/ffi.proto index daf69f83f..8ccd2d17a 100644 --- a/livekit-ffi/protocol/ffi.proto +++ b/livekit-ffi/protocol/ffi.proto @@ -116,6 +116,9 @@ message FfiRequest { SendStreamHeaderRequest send_stream_header = 44; SendStreamChunkRequest send_stream_chunk = 45; SendStreamTrailerRequest send_stream_trailer = 46; + + // Data Channel + SetDataChannelBufferedAmountLowThresholdRequest set_data_channel_buffered_amount_low_threshold = 47; } } @@ -178,6 +181,9 @@ message FfiResponse { SendStreamHeaderResponse send_stream_header = 43; SendStreamChunkResponse send_stream_chunk = 44; SendStreamTrailerResponse send_stream_trailer = 45; + + // Data Channel + SetDataChannelBufferedAmountLowThresholdResponse set_data_channel_buffered_amount_low_threshold = 46; } } diff --git a/livekit-ffi/protocol/room.proto b/livekit-ffi/protocol/room.proto index aa10986d0..87ad8fb58 100644 --- a/livekit-ffi/protocol/room.proto +++ b/livekit-ffi/protocol/room.proto @@ -370,6 +370,7 @@ message RoomEvent { DataStreamHeaderReceived stream_header_received = 30; DataStreamChunkReceived stream_chunk_received = 31; DataStreamTrailerReceived stream_trailer_received = 32; + DataChannelBufferedAmountLowThresholdChanged data_channel_low_threshold_changed = 33; } } @@ -377,6 +378,8 @@ message RoomInfo { optional string sid = 1; required string name = 2; required string metadata = 3; + required uint64 lossy_dc_buffered_amount_low_threshold = 4; + required uint64 reliable_dc_buffered_amount_low_threshold = 5; } message OwnedRoom { @@ -647,3 +650,17 @@ message SendStreamTrailerCallback { required uint64 async_id = 1; optional string error = 2; } + +message SetDataChannelBufferedAmountLowThresholdRequest { + required uint64 local_participant_handle = 1; + required uint64 threshold = 2; + required DataPacketKind kind = 3; +} + +message SetDataChannelBufferedAmountLowThresholdResponse { +} + +message DataChannelBufferedAmountLowThresholdChanged { + required DataPacketKind kind = 1; + required uint64 threshold = 2; +} diff --git a/livekit-ffi/src/conversion/room.rs b/livekit-ffi/src/conversion/room.rs index 260c280a8..adf1bfce2 100644 --- a/livekit-ffi/src/conversion/room.rs +++ b/livekit-ffi/src/conversion/room.rs @@ -252,6 +252,12 @@ impl From<&FfiRoom> for proto::RoomInfo { sid: room.maybe_sid().map(|x| x.to_string()), name: room.name(), metadata: room.metadata(), + lossy_dc_buffered_amount_low_threshold: room + .data_channel_options(DataPacketKind::Lossy) + .buffered_amount_low_threshold, + reliable_dc_buffered_amount_low_threshold: room + .data_channel_options(DataPacketKind::Reliable) + .buffered_amount_low_threshold, } } } diff --git a/livekit-ffi/src/livekit.proto.rs b/livekit-ffi/src/livekit.proto.rs index 45e0cd05a..a10af08d6 100644 --- a/livekit-ffi/src/livekit.proto.rs +++ b/livekit-ffi/src/livekit.proto.rs @@ -1,5 +1,4 @@ // @generated -// This file is @generated by prost-build. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FrameCryptor { @@ -2635,7 +2634,7 @@ pub struct OwnedBuffer { pub struct RoomEvent { #[prost(uint64, required, tag="1")] pub room_handle: u64, - #[prost(oneof="room_event::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32")] + #[prost(oneof="room_event::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33")] pub message: ::core::option::Option, } /// Nested message and enum types in `RoomEvent`. @@ -2707,6 +2706,8 @@ pub mod room_event { StreamChunkReceived(super::DataStreamChunkReceived), #[prost(message, tag="32")] StreamTrailerReceived(super::DataStreamTrailerReceived), + #[prost(message, tag="33")] + DataChannelLowThresholdChanged(super::DataChannelBufferedAmountLowThresholdChanged), } } #[allow(clippy::derive_partial_eq_without_eq)] @@ -2718,6 +2719,10 @@ pub struct RoomInfo { pub name: ::prost::alloc::string::String, #[prost(string, required, tag="3")] pub metadata: ::prost::alloc::string::String, + #[prost(uint64, required, tag="4")] + pub lossy_dc_buffered_amount_low_threshold: u64, + #[prost(uint64, required, tag="5")] + pub reliable_dc_buffered_amount_low_threshold: u64, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -3217,6 +3222,28 @@ pub struct SendStreamTrailerCallback { #[prost(string, optional, tag="2")] pub error: ::core::option::Option<::prost::alloc::string::String>, } +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SetDataChannelBufferedAmountLowThresholdRequest { + #[prost(uint64, required, tag="1")] + pub local_participant_handle: u64, + #[prost(uint64, required, tag="2")] + pub threshold: u64, + #[prost(enumeration="DataPacketKind", required, tag="3")] + pub kind: i32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SetDataChannelBufferedAmountLowThresholdResponse { +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DataChannelBufferedAmountLowThresholdChanged { + #[prost(enumeration="DataPacketKind", required, tag="1")] + pub kind: i32, + #[prost(uint64, required, tag="2")] + pub threshold: u64, +} #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] pub enum IceTransportType { @@ -3989,7 +4016,7 @@ pub struct RpcMethodInvocationEvent { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiRequest { - #[prost(oneof="ffi_request::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46")] + #[prost(oneof="ffi_request::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47")] pub message: ::core::option::Option, } /// Nested message and enum types in `FfiRequest`. @@ -4094,13 +4121,16 @@ pub mod ffi_request { SendStreamChunk(super::SendStreamChunkRequest), #[prost(message, tag="46")] SendStreamTrailer(super::SendStreamTrailerRequest), + /// Data Channel + #[prost(message, tag="47")] + SetDataChannelBufferedAmountLowThreshold(super::SetDataChannelBufferedAmountLowThresholdRequest), } } /// This is the output of livekit_ffi_request function. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiResponse { - #[prost(oneof="ffi_response::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45")] + #[prost(oneof="ffi_response::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46")] pub message: ::core::option::Option, } /// Nested message and enum types in `FfiResponse`. @@ -4203,6 +4233,9 @@ pub mod ffi_response { SendStreamChunk(super::SendStreamChunkResponse), #[prost(message, tag="45")] SendStreamTrailer(super::SendStreamTrailerResponse), + /// Data Channel + #[prost(message, tag="46")] + SetDataChannelBufferedAmountLowThreshold(super::SetDataChannelBufferedAmountLowThresholdResponse), } } /// To minimize complexity, participant events are not included in the protocol. diff --git a/livekit-ffi/src/server/requests.rs b/livekit-ffi/src/server/requests.rs index a002a67ba..8a8b5be80 100644 --- a/livekit-ffi/src/server/requests.rs +++ b/livekit-ffi/src/server/requests.rs @@ -905,6 +905,20 @@ fn on_rpc_method_invocation_response( Ok(proto::RpcMethodInvocationResponseResponse { error }) } +fn on_set_data_channel_buffered_amount_low_threshold( + server: &'static FfiServer, + set_data_channel_buffered_amount_low_threshold: proto::SetDataChannelBufferedAmountLowThresholdRequest, +) -> FfiResult { + let ffi_participant = server + .retrieve_handle::( + set_data_channel_buffered_amount_low_threshold.local_participant_handle, + )? + .clone(); + Ok(ffi_participant.room.set_data_channel_buffered_amount_low_threshold( + set_data_channel_buffered_amount_low_threshold, + )) +} + #[allow(clippy::field_reassign_with_default)] // Avoid uggly format pub fn handle_request( server: &'static FfiServer, @@ -1078,6 +1092,11 @@ pub fn handle_request( server, request, )?) } + proto::ffi_request::Message::SetDataChannelBufferedAmountLowThreshold(request) => { + proto::ffi_response::Message::SetDataChannelBufferedAmountLowThreshold( + on_set_data_channel_buffered_amount_low_threshold(server, request)?, + ) + } }); Ok(res) diff --git a/livekit-ffi/src/server/room.rs b/livekit-ffi/src/server/room.rs index fcfeee261..e3eea0d96 100644 --- a/livekit-ffi/src/server/room.rs +++ b/livekit-ffi/src/server/room.rs @@ -774,6 +774,17 @@ impl RoomInner { ) -> Option>> { return self.rpc_method_invocation_waiters.lock().remove(&invocation_id); } + + pub fn set_data_channel_buffered_amount_low_threshold( + &self, + request: proto::SetDataChannelBufferedAmountLowThresholdRequest, + ) -> proto::SetDataChannelBufferedAmountLowThresholdResponse { + let _ = self.room.local_participant().set_data_channel_buffered_amount_low_threshold( + request.threshold, + request.kind().into(), + ); + proto::SetDataChannelBufferedAmountLowThresholdResponse {} + } } // Task used to publish data without blocking the client thread @@ -1246,6 +1257,14 @@ async fn forward_event( proto::DataStreamTrailerReceived { trailer: trailer.into(), participant_identity }, )); } + RoomEvent::DataChannelBufferedAmountLowThresholdChanged { kind, threshold } => { + let _ = send_event(proto::room_event::Message::DataChannelLowThresholdChanged( + proto::DataChannelBufferedAmountLowThresholdChanged { + kind: proto::DataPacketKind::from(kind).into(), + threshold, + }, + )); + } _ => { log::warn!("unhandled room event: {:?}", event); } diff --git a/livekit/src/room/mod.rs b/livekit/src/room/mod.rs index 9437e5ff0..762bec1f2 100644 --- a/livekit/src/room/mod.rs +++ b/livekit/src/room/mod.rs @@ -43,7 +43,7 @@ use crate::{ prelude::*, rtc_engine::{ EngineError, EngineEvent, EngineEvents, EngineOptions, EngineResult, RtcEngine, - SessionStats, + SessionStats, INITIAL_BUFFERED_AMOUNT_LOW_THRESHOLD, }, }; @@ -196,6 +196,10 @@ pub enum RoomEvent { }, Reconnecting, Reconnected, + DataChannelBufferedAmountLowThresholdChanged { + kind: DataPacketKind, + threshold: u64, + }, } #[derive(Debug, Clone, Copy, Eq, PartialEq)] @@ -360,6 +364,19 @@ impl Debug for Room { struct RoomInfo { metadata: String, state: ConnectionState, + lossy_dc_options: DataChannelOptions, + reliable_dc_options: DataChannelOptions, +} + +#[derive(Clone)] +pub struct DataChannelOptions { + pub buffered_amount_low_threshold: u64, +} + +impl Default for DataChannelOptions { + fn default() -> Self { + Self { buffered_amount_low_threshold: INITIAL_BUFFERED_AMOUNT_LOW_THRESHOLD } + } } pub(crate) struct RoomSession { @@ -506,6 +523,8 @@ impl Room { info: RwLock::new(RoomInfo { state: ConnectionState::Disconnected, metadata: room_info.metadata, + lossy_dc_options: Default::default(), + reliable_dc_options: Default::default(), }), remote_participants: Default::default(), active_speakers: Default::default(), @@ -623,6 +642,13 @@ impl Room { pub fn e2ee_manager(&self) -> &E2eeManager { &self.inner.e2ee_manager } + + pub fn data_channel_options(&self, kind: DataPacketKind) -> DataChannelOptions { + match kind { + DataPacketKind::Lossy => self.inner.info.read().lossy_dc_options.clone(), + DataPacketKind::Reliable => self.inner.info.read().reliable_dc_options.clone(), + } + } } impl RoomSession { @@ -741,6 +767,9 @@ impl RoomSession { EngineEvent::DataStreamTrailer { trailer, participant_identity } => { self.handle_data_stream_trailer(trailer, participant_identity); } + EngineEvent::DataChannelBufferedAmountLowThresholdChanged { kind, threshold } => { + self.handle_data_channel_buffered_low_threshold_change(kind, threshold); + } _ => {} } @@ -1278,6 +1307,24 @@ impl RoomSession { self.dispatcher.dispatch(&event); } + fn handle_data_channel_buffered_low_threshold_change( + &self, + kind: DataPacketKind, + threshold: u64, + ) { + let mut info = self.info.write(); + match kind { + DataPacketKind::Lossy => { + info.lossy_dc_options.buffered_amount_low_threshold = threshold; + } + DataPacketKind::Reliable => { + info.reliable_dc_options.buffered_amount_low_threshold = threshold; + } + } + let event = RoomEvent::DataChannelBufferedAmountLowThresholdChanged { kind, threshold }; + self.dispatcher.dispatch(&event); + } + /// Create a new participant /// Also add it to the participants list fn create_participant( diff --git a/livekit/src/room/participant/local_participant.rs b/livekit/src/room/participant/local_participant.rs index bbf269c50..ad0db760c 100644 --- a/livekit/src/room/participant/local_participant.rs +++ b/livekit/src/room/participant/local_participant.rs @@ -377,7 +377,7 @@ impl LocalParticipant { ..Default::default() }; - match self.inner.rtc_engine.publish_data(&data, DataPacketKind::Reliable).await { + match self.inner.rtc_engine.publish_data(data, DataPacketKind::Reliable).await { Ok(_) => Ok(ChatMessage::from(chat_message)), Err(e) => Err(Into::into(e)), } @@ -403,7 +403,7 @@ impl LocalParticipant { ..Default::default() }; - match self.inner.rtc_engine.publish_data(&data, DataPacketKind::Reliable).await { + match self.inner.rtc_engine.publish_data(data, DataPacketKind::Reliable).await { Ok(_) => Ok(ChatMessage::from(proto_msg)), Err(e) => Err(Into::into(e)), } @@ -447,7 +447,7 @@ impl LocalParticipant { true => DataPacketKind::Reliable, false => DataPacketKind::Lossy, }; - self.inner.rtc_engine.publish_data(&packet, kind).await.map_err(Into::into) + self.inner.rtc_engine.publish_data(packet, kind).await.map_err(Into::into) } pub async fn publish_data(&self, packet: DataPacket) -> RoomResult<()> { @@ -468,7 +468,26 @@ impl LocalParticipant { ..Default::default() }; - self.inner.rtc_engine.publish_data(&data, kind).await.map_err(Into::into) + self.inner.rtc_engine.publish_data(data, kind).await.map_err(Into::into) + } + + pub fn set_data_channel_buffered_amount_low_threshold( + &self, + threshold: u64, + kind: DataPacketKind, + ) -> RoomResult<()> { + self.inner + .rtc_engine + .session() + .set_data_channel_buffered_amount_low_threshold(threshold, kind); + Ok(()) + } + + pub fn data_channel_buffered_amount_low_threshold( + &self, + kind: DataPacketKind, + ) -> RoomResult { + Ok(self.inner.rtc_engine.session().data_channel_buffered_amount_low_threshold(kind)) } pub async fn publish_transcription(&self, packet: Transcription) -> RoomResult<()> { @@ -493,11 +512,7 @@ impl LocalParticipant { value: Some(proto::data_packet::Value::Transcription(transcription_packet)), ..Default::default() }; - self.inner - .rtc_engine - .publish_data(&data, DataPacketKind::Reliable) - .await - .map_err(Into::into) + self.inner.rtc_engine.publish_data(data, DataPacketKind::Reliable).await.map_err(Into::into) } pub async fn publish_dtmf(&self, dtmf: SipDTMF) -> RoomResult<()> { @@ -511,11 +526,7 @@ impl LocalParticipant { ..Default::default() }; - self.inner - .rtc_engine - .publish_data(&data, DataPacketKind::Reliable) - .await - .map_err(Into::into) + self.inner.rtc_engine.publish_data(data, DataPacketKind::Reliable).await.map_err(Into::into) } async fn publish_rpc_request(&self, rpc_request: RpcRequest) -> RoomResult<()> { @@ -535,11 +546,7 @@ impl LocalParticipant { ..Default::default() }; - self.inner - .rtc_engine - .publish_data(&data, DataPacketKind::Reliable) - .await - .map_err(Into::into) + self.inner.rtc_engine.publish_data(data, DataPacketKind::Reliable).await.map_err(Into::into) } async fn publish_rpc_response(&self, rpc_response: RpcResponse) -> RoomResult<()> { @@ -563,11 +570,7 @@ impl LocalParticipant { ..Default::default() }; - self.inner - .rtc_engine - .publish_data(&data, DataPacketKind::Reliable) - .await - .map_err(Into::into) + self.inner.rtc_engine.publish_data(data, DataPacketKind::Reliable).await.map_err(Into::into) } async fn publish_rpc_ack(&self, rpc_ack: RpcAck) -> RoomResult<()> { @@ -581,11 +584,7 @@ impl LocalParticipant { ..Default::default() }; - self.inner - .rtc_engine - .publish_data(&data, DataPacketKind::Reliable) - .await - .map_err(Into::into) + self.inner.rtc_engine.publish_data(data, DataPacketKind::Reliable).await.map_err(Into::into) } pub fn get_track_publication(&self, sid: &TrackSid) -> Option { diff --git a/livekit/src/rtc_engine/mod.rs b/livekit/src/rtc_engine/mod.rs index 0b2059095..5599861e9 100644 --- a/livekit/src/rtc_engine/mod.rs +++ b/livekit/src/rtc_engine/mod.rs @@ -25,7 +25,7 @@ use tokio::sync::{ RwLockReadGuard as AsyncRwLockReadGuard, }; -pub use self::rtc_session::SessionStats; +pub use self::rtc_session::{SessionStats, INITIAL_BUFFERED_AMOUNT_LOW_THRESHOLD}; use crate::prelude::ParticipantIdentity; use crate::{ id::ParticipantSid, @@ -171,6 +171,10 @@ pub enum EngineEvent { trailer: proto::data_stream::Trailer, participant_identity: String, }, + DataChannelBufferedAmountLowThresholdChanged { + kind: DataPacketKind, + threshold: u64, + }, } /// Represents a running RtcSession with the ability to close the session @@ -233,7 +237,7 @@ impl RtcEngine { pub async fn publish_data( &self, - data: &proto::DataPacket, + data: proto::DataPacket, kind: DataPacketKind, ) -> EngineResult<()> { let (session, _r_lock) = { @@ -551,6 +555,11 @@ impl EngineInner { .engine_tx .send(EngineEvent::DataStreamTrailer { trailer, participant_identity }); } + SessionEvent::DataChannelBufferedAmountLowThresholdChanged { kind, threshold } => { + let _ = self.engine_tx.send( + EngineEvent::DataChannelBufferedAmountLowThresholdChanged { kind, threshold }, + ); + } } Ok(()) } diff --git a/livekit/src/rtc_engine/rtc_events.rs b/livekit/src/rtc_engine/rtc_events.rs index 576b867d1..004fb2292 100644 --- a/livekit/src/rtc_engine/rtc_events.rs +++ b/livekit/src/rtc_engine/rtc_events.rs @@ -17,7 +17,7 @@ use livekit_protocol as proto; use tokio::sync::mpsc; use super::peer_transport::PeerTransport; -use crate::rtc_engine::peer_transport::OnOfferCreated; +use crate::{rtc_engine::peer_transport::OnOfferCreated, DataPacketKind}; pub type RtcEmitter = mpsc::UnboundedSender; pub type RtcEvents = mpsc::UnboundedReceiver; @@ -51,6 +51,11 @@ pub enum RtcEvent { data: Vec, binary: bool, }, + DataChannelBufferedAmountChange { + sent: u64, + amount: u64, + kind: DataPacketKind, + }, } /// Handlers used to forward events to a channel @@ -141,6 +146,18 @@ fn on_message(emitter: RtcEmitter) -> rtc::data_channel::OnMessage { }) } -pub fn forward_dc_events(dc: &mut DataChannel, rtc_emitter: RtcEmitter) { - dc.on_message(Some(on_message(rtc_emitter))); +fn on_buffered_amount_change( + emitter: RtcEmitter, + dc: DataChannel, + kind: DataPacketKind, +) -> rtc::data_channel::OnBufferedAmountChange { + Box::new(move |sent| { + let amount = dc.buffered_amount(); + let _ = emitter.send(RtcEvent::DataChannelBufferedAmountChange { sent, amount, kind }); + }) +} + +pub fn forward_dc_events(dc: &mut DataChannel, kind: DataPacketKind, rtc_emitter: RtcEmitter) { + dc.on_message(Some(on_message(rtc_emitter.clone()))); + dc.on_buffered_amount_change(Some(on_buffered_amount_change(rtc_emitter, dc.clone(), kind))); } diff --git a/livekit/src/rtc_engine/rtc_session.rs b/livekit/src/rtc_engine/rtc_session.rs index 8de9d9593..01e57cd85 100644 --- a/livekit/src/rtc_engine/rtc_session.rs +++ b/livekit/src/rtc_engine/rtc_session.rs @@ -13,12 +13,12 @@ // limitations under the License. use std::{ - collections::HashMap, + collections::{HashMap, VecDeque}, convert::TryInto, fmt::Debug, ops::Not, sync::{ - atomic::{AtomicBool, Ordering}, + atomic::{AtomicBool, AtomicU64, Ordering}, Arc, }, time::Duration, @@ -34,7 +34,7 @@ use proto::{ debouncer::{self, Debouncer}, SignalTarget, }; -use serde::{de::IntoDeserializer, Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use tokio::sync::{mpsc, oneshot, watch}; use super::{rtc_events, EngineError, EngineOptions, EngineResult, SimulateScenario}; @@ -58,6 +58,7 @@ pub const TRACK_PUBLISH_TIMEOUT: Duration = Duration::from_secs(10); pub const LOSSY_DC_LABEL: &str = "_lossy"; pub const RELIABLE_DC_LABEL: &str = "_reliable"; pub const PUBLISHER_NEGOTIATION_FREQUENCY: Duration = Duration::from_millis(150); +pub const INITIAL_BUFFERED_AMOUNT_LOW_THRESHOLD: u64 = 2 * 1024 * 1024; pub type SessionEmitter = mpsc::UnboundedSender; pub type SessionEvents = mpsc::UnboundedReceiver; @@ -147,6 +148,16 @@ pub enum SessionEvent { trailer: proto::data_stream::Trailer, participant_identity: String, }, + DataChannelBufferedAmountLowThresholdChanged { + kind: DataPacketKind, + threshold: u64, + }, +} + +#[derive(Debug)] +enum DataChannelEvent { + PublishData(proto::DataPacket, DataPacketKind, oneshot::Sender>), + BufferedAmountChange(u64, DataPacketKind), } #[derive(Serialize, Deserialize)] @@ -170,7 +181,10 @@ struct SessionInner { // Publisher data channels // used to send data to other participants (The SFU forwards the messages) lossy_dc: DataChannel, + lossy_dc_buffered_amount_low_threshold: AtomicU64, reliable_dc: DataChannel, + reliable_dc_buffered_amount_low_threshold: AtomicU64, + dc_emitter: mpsc::UnboundedSender, // Keep a strong reference to the subscriber datachannels, // so we can receive data from other participants @@ -205,6 +219,7 @@ struct SessionHandle { close_tx: watch::Sender, // false = is_running signal_task: JoinHandle<()>, rtc_task: JoinHandle<()>, + dc_task: JoinHandle<()>, } impl RtcSession { @@ -223,6 +238,8 @@ impl RtcSession { let (rtc_emitter, rtc_events) = mpsc::unbounded_channel(); let rtc_config = make_rtc_config_join(join_response.clone(), options.rtc_config.clone()); + let (dc_emitter, dc_events) = mpsc::unbounded_channel(); + let lk_runtime = LkRuntime::instance(); let mut publisher_pc = PeerTransport::new( lk_runtime.pc_factory().create_peer_connection(rtc_config.clone())?, @@ -251,8 +268,8 @@ impl RtcSession { // Forward events received inside the signaling thread to our rtc channel rtc_events::forward_pc_events(&mut publisher_pc, rtc_emitter.clone()); rtc_events::forward_pc_events(&mut subscriber_pc, rtc_emitter.clone()); - rtc_events::forward_dc_events(&mut lossy_dc, rtc_emitter.clone()); - rtc_events::forward_dc_events(&mut reliable_dc, rtc_emitter); + rtc_events::forward_dc_events(&mut lossy_dc, DataPacketKind::Lossy, rtc_emitter.clone()); + rtc_events::forward_dc_events(&mut reliable_dc, DataPacketKind::Reliable, rtc_emitter); let (close_tx, close_rx) = watch::channel(false); let inner = Arc::new(SessionInner { @@ -262,7 +279,14 @@ impl RtcSession { subscriber_pc, pending_tracks: Default::default(), lossy_dc, + lossy_dc_buffered_amount_low_threshold: AtomicU64::new( + INITIAL_BUFFERED_AMOUNT_LOW_THRESHOLD, + ), reliable_dc, + reliable_dc_buffered_amount_low_threshold: AtomicU64::new( + INITIAL_BUFFERED_AMOUNT_LOW_THRESHOLD, + ), + dc_emitter, sub_lossy_dc: Mutex::new(None), sub_reliable_dc: Mutex::new(None), closed: Default::default(), @@ -275,9 +299,11 @@ impl RtcSession { // Start session tasks let signal_task = livekit_runtime::spawn(inner.clone().signal_task(signal_events, close_rx.clone())); - let rtc_task = livekit_runtime::spawn(inner.clone().rtc_session_task(rtc_events, close_rx)); + let rtc_task = + livekit_runtime::spawn(inner.clone().rtc_session_task(rtc_events, close_rx.clone())); + let dc_task = livekit_runtime::spawn(inner.clone().data_channel_task(dc_events, close_rx)); - let handle = Mutex::new(Some(SessionHandle { close_tx, signal_task, rtc_task })); + let handle = Mutex::new(Some(SessionHandle { close_tx, signal_task, rtc_task, dc_task })); Ok((Self { inner, handle }, join_response, session_events)) } @@ -319,6 +345,7 @@ impl RtcSession { let _ = handle.close_tx.send(true); let _ = handle.rtc_task.await; let _ = handle.signal_task.await; + let _ = handle.dc_task.await; } // Close the PeerConnections after the task @@ -328,7 +355,7 @@ impl RtcSession { pub async fn publish_data( &self, - data: &proto::DataPacket, + data: proto::DataPacket, kind: DataPacketKind, ) -> Result<(), EngineError> { self.inner.publish_data(data, kind).await @@ -374,6 +401,38 @@ impl RtcSession { self.inner.data_channel(target, kind) } + pub fn data_channel_buffered_amount_low_threshold(&self, kind: DataPacketKind) -> u64 { + match kind { + DataPacketKind::Lossy => { + self.inner.lossy_dc_buffered_amount_low_threshold.load(Ordering::Relaxed) + } + DataPacketKind::Reliable => { + self.inner.reliable_dc_buffered_amount_low_threshold.load(Ordering::Relaxed) + } + } + } + + pub fn set_data_channel_buffered_amount_low_threshold( + &self, + threshold: u64, + kind: DataPacketKind, + ) { + match kind { + DataPacketKind::Lossy => self + .inner + .lossy_dc_buffered_amount_low_threshold + .store(threshold, Ordering::Relaxed), + DataPacketKind::Reliable => self + .inner + .reliable_dc_buffered_amount_low_threshold + .store(threshold, Ordering::Relaxed), + } + let _ = self + .inner + .emitter + .send(SessionEvent::DataChannelBufferedAmountLowThresholdChanged { kind, threshold }); + } + pub async fn get_response(&self, request_id: u32) -> proto::RequestResponse { self.inner.get_response(request_id).await } @@ -469,6 +528,101 @@ impl SessionInner { log::debug!("closing signal_task"); } + async fn data_channel_task( + self: Arc, + mut dc_events: mpsc::UnboundedReceiver, + mut close_rx: watch::Receiver, + ) { + let mut lossy_buffered_amount = 0; + let mut reliable_buffered_amount = 0; + let mut lossy_queue = VecDeque::new(); + let mut reliable_queue = VecDeque::new(); + + loop { + tokio::select! { + event = dc_events.recv() => { + let Some(event) = event else { + // tx closed + break; + }; + + match event { + DataChannelEvent::PublishData(packet, kind, tx) => { + let data = packet.encode_to_vec(); + match kind { + DataPacketKind::Lossy => { + lossy_queue.push_back((data, kind, tx)); + let threshold = self.lossy_dc_buffered_amount_low_threshold.load(Ordering::Relaxed); + self._send_until_threshold(threshold, &mut lossy_buffered_amount, &mut lossy_queue); + } + DataPacketKind::Reliable => { + reliable_queue.push_back((data, kind, tx)); + let threshold = self.reliable_dc_buffered_amount_low_threshold.load(Ordering::Relaxed); + self._send_until_threshold(threshold, &mut reliable_buffered_amount, &mut reliable_queue); + } + } + } + DataChannelEvent::BufferedAmountChange(sent, kind) => { + match kind { + DataPacketKind::Lossy => { + if lossy_buffered_amount < sent { + // I believe never reach here but adding logs just in case + log::error!("unexpected buffer size detected: lossy_buffered_amount={}, sent={}", lossy_buffered_amount, sent); + lossy_buffered_amount = 0; + } else { + lossy_buffered_amount -= sent; + } + let threshold = self.lossy_dc_buffered_amount_low_threshold.load(Ordering::Relaxed); + self._send_until_threshold(threshold, &mut lossy_buffered_amount, &mut lossy_queue); + } + DataPacketKind::Reliable => { + if reliable_buffered_amount < sent { + log::error!("unexpected buffer size detected: reliable_buffered_amount={}, sent={}", reliable_buffered_amount, sent); + reliable_buffered_amount = 0; + } else { + reliable_buffered_amount -= sent; + } + let threshold = self.reliable_dc_buffered_amount_low_threshold.load(Ordering::Relaxed); + self._send_until_threshold(threshold, &mut reliable_buffered_amount, &mut reliable_queue); + } + } + } + } + }, + + _ = close_rx.changed() => { + break; + }, + } + } + + log::debug!("closing data_channel_task"); + } + + fn _send_until_threshold( + self: &Arc, + threshold: u64, + buffered_amount: &mut u64, + queue: &mut VecDeque<(Vec, DataPacketKind, oneshot::Sender>)>, + ) { + while *buffered_amount <= threshold { + let Some((data, kind, tx)) = queue.pop_front() else { + break; + }; + + *buffered_amount += data.len() as u64; + let result = self + .data_channel(SignalTarget::Publisher, kind) + .unwrap() + .send(&data, true) + .map_err(|err| { + EngineError::Internal(format!("failed to send data packet: {:?}", err).into()) + }); + + let _ = tx.send(result); + } + } + async fn on_signal_event(&self, event: proto::signal_response::Message) -> EngineResult<()> { match event { proto::signal_response::Message::Answer(answer) => { @@ -757,6 +911,13 @@ impl SessionInner { } } } + RtcEvent::DataChannelBufferedAmountChange { sent, amount: _, kind } => { + if let Err(err) = + self.dc_emitter.send(DataChannelEvent::BufferedAmountChange(sent, kind)) + { + log::error!("failed to send dc_event buffer_amount_change: {:?}", err); + } + } } Ok(()) @@ -964,16 +1125,20 @@ impl SessionInner { async fn publish_data( self: &Arc, - data: &proto::DataPacket, + data: proto::DataPacket, kind: DataPacketKind, ) -> Result<(), EngineError> { self.ensure_publisher_connected(kind).await?; - self.data_channel(SignalTarget::Publisher, kind) - .unwrap() - .send(&data.encode_to_vec(), true) - .map_err(|err| { - EngineError::Internal(format!("failed to send data packet {:?}", err).into()) - }) + + let (tx, rx) = oneshot::channel(); + if let Err(err) = self.dc_emitter.send(DataChannelEvent::PublishData(data, kind, tx)) { + return Err(EngineError::Internal( + format!("failed to push data into queue: {:?}", err).into(), + )); + }; + rx.await.map_err(|e| { + EngineError::Internal(format!("failed to receive data from dc_task: {:?}", e).into()) + })? } /// This reconnection if more seemless compared to the full reconnection implemented in diff --git a/webrtc-sys/include/livekit/data_channel.h b/webrtc-sys/include/livekit/data_channel.h index f08e93ea1..33fea51b4 100644 --- a/webrtc-sys/include/livekit/data_channel.h +++ b/webrtc-sys/include/livekit/data_channel.h @@ -49,6 +49,7 @@ class DataChannel { rust::String label() const; DataState state() const; void close() const; + uint64_t buffered_amount() const; private: mutable webrtc::Mutex mutex_; diff --git a/webrtc-sys/src/data_channel.cpp b/webrtc-sys/src/data_channel.cpp index 9f2005f6a..bec5cef4e 100644 --- a/webrtc-sys/src/data_channel.cpp +++ b/webrtc-sys/src/data_channel.cpp @@ -92,6 +92,10 @@ void DataChannel::close() const { return data_channel_->Close(); } +uint64_t DataChannel::buffered_amount() const { + return data_channel_->buffered_amount(); +} + NativeDataChannelObserver::NativeDataChannelObserver( rust::Box observer, const DataChannel* dc) diff --git a/webrtc-sys/src/data_channel.rs b/webrtc-sys/src/data_channel.rs index 7073194de..1ae85bf7e 100644 --- a/webrtc-sys/src/data_channel.rs +++ b/webrtc-sys/src/data_channel.rs @@ -70,6 +70,7 @@ pub mod ffi { fn label(self: &DataChannel) -> String; fn state(self: &DataChannel) -> DataState; fn close(self: &DataChannel); + fn buffered_amount(self: &DataChannel) -> u64; fn _shared_data_channel() -> SharedPtr; // Ignore } From 3eb79a736ac190da29e78797482abf583be9d21a Mon Sep 17 00:00:00 2001 From: "ilo-nanpa[bot]" <174912985+ilo-nanpa[bot]@users.noreply.github.com> Date: Fri, 17 Jan 2025 17:10:10 +0000 Subject: [PATCH 128/274] nanpa: bump --- .nanpa/data-stream-trailer.kdl | 2 -- .nanpa/dc-buffered-amount-low-threshold.kdl | 6 ------ Cargo.toml | 8 ++++---- libwebrtc/.nanparc | 2 +- libwebrtc/CHANGELOG.md | 6 ++++++ libwebrtc/Cargo.toml | 2 +- livekit-api/Cargo.toml | 2 +- livekit-ffi/.nanparc | 2 +- livekit-ffi/CHANGELOG.md | 8 ++++++++ livekit-ffi/Cargo.toml | 2 +- livekit-protocol/.nanparc | 2 +- livekit-protocol/CHANGELOG.md | 7 +++++++ livekit-protocol/Cargo.toml | 2 +- livekit/.nanparc | 2 +- livekit/CHANGELOG.md | 11 +++++++++++ livekit/Cargo.toml | 2 +- 16 files changed, 45 insertions(+), 21 deletions(-) delete mode 100644 .nanpa/data-stream-trailer.kdl delete mode 100644 .nanpa/dc-buffered-amount-low-threshold.kdl create mode 100644 livekit-protocol/CHANGELOG.md diff --git a/.nanpa/data-stream-trailer.kdl b/.nanpa/data-stream-trailer.kdl deleted file mode 100644 index 84706c57a..000000000 --- a/.nanpa/data-stream-trailer.kdl +++ /dev/null @@ -1,2 +0,0 @@ -patch type="changed" package="livekit-protocol" "Update protocol version to v1.31.0" -patch type="added" package="livekit-ffi" "Add DataStream.Trailer support" diff --git a/.nanpa/dc-buffered-amount-low-threshold.kdl b/.nanpa/dc-buffered-amount-low-threshold.kdl deleted file mode 100644 index e5a46b931..000000000 --- a/.nanpa/dc-buffered-amount-low-threshold.kdl +++ /dev/null @@ -1,6 +0,0 @@ -patch type="added" package="libwebrtc" "Expose DataChannel.bufferedAmount property" -patch type="fixed" package="livekit" "Wait for the buffered amount to become low before sending data during publish_data for Reliable Data Channel" -patch type="added" package="livekit" "Add an API to set buffer_amount_low_threshold for DataChannel" -patch type="added" package="livekit" "Update RoomInfo to contain buffer_amount_low_threshold for DataChannel" -patch type="added" package="livekit-ffi" "Add an API to set buffer_amount_low_threshold for DataChannel" -patch type="added" package="livekit-ffi" "Update RoomInfo to contain buffer_amount_low_threshold for DataChannel" diff --git a/Cargo.toml b/Cargo.toml index 837cfaf64..d1acc9044 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,12 +17,12 @@ members = [ [workspace.dependencies] imgproc = { version = "0.3.12", path = "imgproc" } yuv-sys = { version = "0.3.7", path = "yuv-sys" } -libwebrtc = { version = "0.3.8", path = "libwebrtc" } +libwebrtc = { version = "0.3.9", path = "libwebrtc" } livekit-api = { version = "0.4.1", path = "livekit-api" } -livekit-ffi = { version = "0.12.6", path = "livekit-ffi" } -livekit-protocol = { version = "0.3.6", path = "livekit-protocol" } +livekit-ffi = { version = "0.12.7", path = "livekit-ffi" } +livekit-protocol = { version = "0.3.7", path = "livekit-protocol" } livekit-runtime = { version = "0.3.1", path = "livekit-runtime" } -livekit = { version = "0.7.2", path = "livekit" } +livekit = { version = "0.7.3", path = "livekit" } soxr-sys = { version = "0.1.0", path = "soxr-sys" } webrtc-sys-build = { version = "0.3.6", path = "webrtc-sys/build" } webrtc-sys = { version = "0.3.6", path = "webrtc-sys" } diff --git a/libwebrtc/.nanparc b/libwebrtc/.nanparc index 9ad0822e5..96df60bef 100644 --- a/libwebrtc/.nanparc +++ b/libwebrtc/.nanparc @@ -1,2 +1,2 @@ -version 0.3.8 +version 0.3.9 language rust diff --git a/libwebrtc/CHANGELOG.md b/libwebrtc/CHANGELOG.md index 04423d14f..1928bdee8 100644 --- a/libwebrtc/CHANGELOG.md +++ b/libwebrtc/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [0.3.9] - 2025-01-17 + +### Added + +- Expose DataChannel.bufferedAmount property + ## [0.3.8] - 2024-12-14 ### Added diff --git a/libwebrtc/Cargo.toml b/libwebrtc/Cargo.toml index 9cd679f3b..c139122ce 100644 --- a/libwebrtc/Cargo.toml +++ b/libwebrtc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libwebrtc" -version = "0.3.8" +version = "0.3.9" edition = "2021" homepage = "https://livekit.io" license = "Apache-2.0" diff --git a/livekit-api/Cargo.toml b/livekit-api/Cargo.toml index cca769851..7a843f78c 100644 --- a/livekit-api/Cargo.toml +++ b/livekit-api/Cargo.toml @@ -65,7 +65,7 @@ rustls-tls-webpki-roots = [ __rustls-tls = ["tokio-tungstenite?/__rustls-tls", "reqwest?/__rustls"] [dependencies] -livekit-protocol = { path = "../livekit-protocol", version = "0.3.6" } +livekit-protocol = { path = "../livekit-protocol", version = "0.3.7" } thiserror = "1.0" serde = { version = "1.0", features = ["derive"] } sha2 = "0.10" diff --git a/livekit-ffi/.nanparc b/livekit-ffi/.nanparc index 71a6ca890..ea03152fb 100644 --- a/livekit-ffi/.nanparc +++ b/livekit-ffi/.nanparc @@ -1,2 +1,2 @@ -version 0.12.6 +version 0.12.7 language rust diff --git a/livekit-ffi/CHANGELOG.md b/livekit-ffi/CHANGELOG.md index 4968dd87d..7db2c6d57 100644 --- a/livekit-ffi/CHANGELOG.md +++ b/livekit-ffi/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [0.12.7] - 2025-01-17 + +### Added + +- Add DataStream.Trailer support +- Add an API to set buffer_amount_low_threshold for DataChannel +- Update RoomInfo to contain buffer_amount_low_threshold for DataChannel + ## [0.12.6] - 2025-01-07 ### Fixed diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 634bf7d1d..3895227e8 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.12.6" +version = "0.12.7" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" diff --git a/livekit-protocol/.nanparc b/livekit-protocol/.nanparc index 10b95b2c7..5bf971c37 100644 --- a/livekit-protocol/.nanparc +++ b/livekit-protocol/.nanparc @@ -1,2 +1,2 @@ -version 0.3.6 +version 0.3.7 language rust diff --git a/livekit-protocol/CHANGELOG.md b/livekit-protocol/CHANGELOG.md new file mode 100644 index 000000000..29c8f009c --- /dev/null +++ b/livekit-protocol/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog + +## [0.3.7] - 2025-01-17 + +### Changed + +- Update protocol version to v1.31.0 diff --git a/livekit-protocol/Cargo.toml b/livekit-protocol/Cargo.toml index bc65d15d4..bfb935b10 100644 --- a/livekit-protocol/Cargo.toml +++ b/livekit-protocol/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-protocol" -version = "0.3.6" +version = "0.3.7" edition = "2021" license = "Apache-2.0" description = "Livekit protocol and utilities for the Rust SDK" diff --git a/livekit/.nanparc b/livekit/.nanparc index 4d2203e27..f705105b3 100644 --- a/livekit/.nanparc +++ b/livekit/.nanparc @@ -1,2 +1,2 @@ -version 0.7.2 +version 0.7.3 language rust diff --git a/livekit/CHANGELOG.md b/livekit/CHANGELOG.md index 36c57cfa4..52847231e 100644 --- a/livekit/CHANGELOG.md +++ b/livekit/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## [0.7.3] - 2025-01-17 + +### Added + +- Add an API to set buffer_amount_low_threshold for DataChannel +- Update RoomInfo to contain buffer_amount_low_threshold for DataChannel + +### Fixed + +- Wait for the buffered amount to become low before sending data during publish_data for Reliable Data Channel + ## [0.7.2] - 2025-01-04 ### Added diff --git a/livekit/Cargo.toml b/livekit/Cargo.toml index 176be38d2..2cdc746ed 100644 --- a/livekit/Cargo.toml +++ b/livekit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit" -version = "0.7.2" +version = "0.7.3" edition = "2021" license = "Apache-2.0" description = "Rust Client SDK for LiveKit" From ca39260da9454e50e5098fd22e40af588ab1f432 Mon Sep 17 00:00:00 2001 From: Daisuke Murase Date: Fri, 17 Jan 2025 11:00:19 -0800 Subject: [PATCH 129/274] Fix publish workflow (#550) * Trying to fix publish workflow * update the resync --- .github/workflows/publish.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f90b522d4..9670190c4 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -45,6 +45,9 @@ jobs: - uses: actions/checkout@v3 with: submodules: true + - name: Resync is needed after bump + run: | + git fetch --tags && git checkout origin/main - name: Publish crates run: | git tag --points-at HEAD | From e96fdcafb31f9800f917d3e79375367f5ac99ae5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Fri, 17 Jan 2025 20:32:38 +0100 Subject: [PATCH 130/274] nit comment (#551) --- livekit-ffi/protocol/video_frame.proto | 2 ++ 1 file changed, 2 insertions(+) diff --git a/livekit-ffi/protocol/video_frame.proto b/livekit-ffi/protocol/video_frame.proto index 6ce0a2bc8..528b14f43 100644 --- a/livekit-ffi/protocol/video_frame.proto +++ b/livekit-ffi/protocol/video_frame.proto @@ -98,6 +98,8 @@ enum VideoRotation { VIDEO_ROTATION_270 = 3; } +// Values of this enum must not be changed +// It is used to serialize a rtc.VideoFrame on Python enum VideoBufferType { RGBA = 0; ABGR = 1; From aabbd79c6d2c2a2ca1a31f8e60648f455a91457f Mon Sep 17 00:00:00 2001 From: lukasIO Date: Tue, 21 Jan 2025 18:13:10 +0100 Subject: [PATCH 131/274] Update DataStream naming (#555) * Update DataStream naming * remove unused import --- livekit-api/src/services/sip.rs | 8 +- livekit-ffi/protocol/room.proto | 12 +- livekit-ffi/src/conversion/room.rs | 20 +- livekit-ffi/src/livekit.proto.rs | 18 +- livekit-protocol/protocol | 2 +- livekit-protocol/src/livekit.rs | 32 +- livekit-protocol/src/livekit.serde.rs | 456 ++++++++++++++++++-------- 7 files changed, 380 insertions(+), 168 deletions(-) diff --git a/livekit-api/src/services/sip.rs b/livekit-api/src/services/sip.rs index d8a2b982d..773afd534 100644 --- a/livekit-api/src/services/sip.rs +++ b/livekit-api/src/services/sip.rs @@ -257,7 +257,10 @@ impl SIPClient { .request( SVC, "ListSIPTrunk", - proto::ListSipTrunkRequest {}, + proto::ListSipTrunkRequest { + // TODO support these attributes + page: Default::default(), + }, self.base.auth_header( Default::default(), Some(SIPGrants { admin: true, ..Default::default() }), @@ -279,6 +282,7 @@ impl SIPClient { "ListSIPInboundTrunk", proto::ListSipInboundTrunkRequest { // TODO: support these attributes + page: Default::default(), trunk_ids: Default::default(), numbers: Default::default(), }, @@ -303,6 +307,7 @@ impl SIPClient { "ListSIPOutboundTrunk", proto::ListSipOutboundTrunkRequest { // TODO: support these attributes + page: Default::default(), trunk_ids: Default::default(), numbers: Default::default(), }, @@ -373,6 +378,7 @@ impl SIPClient { "ListSIPDispatchRule", proto::ListSipDispatchRuleRequest { // TODO: support these attributes + page: Default::default(), dispatch_rule_ids: Default::default(), trunk_ids: Default::default(), }, diff --git a/livekit-ffi/protocol/room.proto b/livekit-ffi/protocol/room.proto index 87ad8fb58..1333f6d55 100644 --- a/livekit-ffi/protocol/room.proto +++ b/livekit-ffi/protocol/room.proto @@ -552,9 +552,9 @@ message DataStream { } - // header properties specific to file or image streams - message FileHeader { - required string file_name = 1; // name of the file + // header properties specific to byte or file streams + message ByteHeader { + required string name = 1; } // main DataStream.Header that contains a oneof for specific headers @@ -564,12 +564,12 @@ message DataStream { required string mime_type = 3; required string topic = 4; optional uint64 total_length = 5; // only populated for finite streams, if it's a stream of unknown size this stays empty - map extensions = 6; // user defined extensions map that can carry additional info + map attributes = 6; // user defined attributes map that can carry additional info // oneof to choose between specific header types oneof content_header { TextHeader text_header = 7; - FileHeader file_header = 8; + ByteHeader byte_header = 8; } } @@ -584,7 +584,7 @@ message DataStream { message Trailer { required string stream_id = 1; // unique identifier for this data stream required string reason = 2; // reason why the stream was closed (could contain "error" / "interrupted" / empty for expected end) - map extensions = 3; // finalizing updates for the stream, can also include additional insights for errors or endTime for transcription + map attributes = 3; // finalizing updates for the stream, can also include additional insights for errors or endTime for transcription } } diff --git a/livekit-ffi/src/conversion/room.rs b/livekit-ffi/src/conversion/room.rs index adf1bfce2..2662bf3b8 100644 --- a/livekit-ffi/src/conversion/room.rs +++ b/livekit-ffi/src/conversion/room.rs @@ -302,9 +302,9 @@ impl From for proto::data_stream::Header }, )) } - Some(livekit_protocol::data_stream::header::ContentHeader::FileHeader(file_header)) => { - Some(proto::data_stream::header::ContentHeader::FileHeader( - proto::data_stream::FileHeader { file_name: file_header.file_name }, + Some(livekit_protocol::data_stream::header::ContentHeader::ByteHeader(byte_header)) => { + Some(proto::data_stream::header::ContentHeader::ByteHeader( + proto::data_stream::ByteHeader { name: byte_header.name }, )) } None => None, @@ -316,7 +316,7 @@ impl From for proto::data_stream::Header topic: msg.topic, mime_type: msg.mime_type, total_length: msg.total_length, - extensions: msg.extensions, + attributes: msg.attributes, content_header, } } @@ -336,9 +336,9 @@ impl From for livekit_protocol::data_stream::Header }, )) } - Some(proto::data_stream::header::ContentHeader::FileHeader(file_header)) => { - Some(livekit_protocol::data_stream::header::ContentHeader::FileHeader( - livekit_protocol::data_stream::FileHeader { file_name: file_header.file_name }, + Some(proto::data_stream::header::ContentHeader::ByteHeader(byte_header)) => { + Some(livekit_protocol::data_stream::header::ContentHeader::ByteHeader( + livekit_protocol::data_stream::ByteHeader { name: byte_header.name }, )) } None => None, @@ -350,7 +350,7 @@ impl From for livekit_protocol::data_stream::Header topic: msg.topic, mime_type: msg.mime_type, total_length: msg.total_length, - extensions: msg.extensions, + attributes: msg.attributes, content_header, encryption_type: 0, } @@ -383,12 +383,12 @@ impl From for livekit_protocol::data_stream::Chunk { impl From for proto::data_stream::Trailer { fn from(msg: livekit_protocol::data_stream::Trailer) -> Self { - Self { stream_id: msg.stream_id, reason: msg.reason, extensions: msg.extensions } + Self { stream_id: msg.stream_id, reason: msg.reason, attributes: msg.attributes } } } impl From for livekit_protocol::data_stream::Trailer { fn from(msg: proto::data_stream::Trailer) -> Self { - Self { stream_id: msg.stream_id, reason: msg.reason, extensions: msg.extensions } + Self { stream_id: msg.stream_id, reason: msg.reason, attributes: msg.attributes } } } diff --git a/livekit-ffi/src/livekit.proto.rs b/livekit-ffi/src/livekit.proto.rs index a10af08d6..ec8a9d53c 100644 --- a/livekit-ffi/src/livekit.proto.rs +++ b/livekit-ffi/src/livekit.proto.rs @@ -1,4 +1,5 @@ // @generated +// This file is @generated by prost-build. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FrameCryptor { @@ -2007,6 +2008,8 @@ impl VideoRotation { } } } +/// Values of this enum must not be changed +/// It is used to serialize a rtc.VideoFrame on Python #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] pub enum VideoBufferType { @@ -3011,13 +3014,12 @@ pub mod data_stream { #[prost(bool, optional, tag="5")] pub generated: ::core::option::Option, } - /// header properties specific to file or image streams + /// header properties specific to byte or file streams #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] - pub struct FileHeader { - /// name of the file + pub struct ByteHeader { #[prost(string, required, tag="1")] - pub file_name: ::prost::alloc::string::String, + pub name: ::prost::alloc::string::String, } /// main DataStream.Header that contains a oneof for specific headers #[allow(clippy::derive_partial_eq_without_eq)] @@ -3036,9 +3038,9 @@ pub mod data_stream { /// only populated for finite streams, if it's a stream of unknown size this stays empty #[prost(uint64, optional, tag="5")] pub total_length: ::core::option::Option, - /// user defined extensions map that can carry additional info + /// user defined attributes map that can carry additional info #[prost(map="string, string", tag="6")] - pub extensions: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + pub attributes: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, /// oneof to choose between specific header types #[prost(oneof="header::ContentHeader", tags="7, 8")] pub content_header: ::core::option::Option, @@ -3052,7 +3054,7 @@ pub mod data_stream { #[prost(message, tag="7")] TextHeader(super::TextHeader), #[prost(message, tag="8")] - FileHeader(super::FileHeader), + ByteHeader(super::ByteHeader), } } #[allow(clippy::derive_partial_eq_without_eq)] @@ -3084,7 +3086,7 @@ pub mod data_stream { pub reason: ::prost::alloc::string::String, /// finalizing updates for the stream, can also include additional insights for errors or endTime for transcription #[prost(map="string, string", tag="3")] - pub extensions: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + pub attributes: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, } /// enum for operation types (specific to TextHeader) #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] diff --git a/livekit-protocol/protocol b/livekit-protocol/protocol index 553f87b84..afe463fe8 160000 --- a/livekit-protocol/protocol +++ b/livekit-protocol/protocol @@ -1 +1 @@ -Subproject commit 553f87b849effe55777f80cb93766509868fe4f5 +Subproject commit afe463fe84852471ee403efba3c32451162cbffd diff --git a/livekit-protocol/src/livekit.rs b/livekit-protocol/src/livekit.rs index 73f358d0a..9b6eaec82 100644 --- a/livekit-protocol/src/livekit.rs +++ b/livekit-protocol/src/livekit.rs @@ -192,6 +192,15 @@ impl MetricLabel { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct Pagination { + /// list entities which IDs are greater + #[prost(string, tag="1")] + pub after_id: ::prost::alloc::string::String, + #[prost(int32, tag="2")] + pub limit: i32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct Room { #[prost(string, tag="1")] pub sid: ::prost::alloc::string::String, @@ -1143,13 +1152,12 @@ pub mod data_stream { #[prost(bool, tag="5")] pub generated: bool, } - /// header properties specific to file or image streams + /// header properties specific to byte or file streams #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] - pub struct FileHeader { - /// name of the file + pub struct ByteHeader { #[prost(string, tag="1")] - pub file_name: ::prost::alloc::string::String, + pub name: ::prost::alloc::string::String, } /// main DataStream.Header that contains a oneof for specific headers #[allow(clippy::derive_partial_eq_without_eq)] @@ -1171,9 +1179,9 @@ pub mod data_stream { /// defaults to NONE #[prost(enumeration="super::encryption::Type", tag="7")] pub encryption_type: i32, - /// user defined extensions map that can carry additional info + /// user defined attributes map that can carry additional info #[prost(map="string, string", tag="8")] - pub extensions: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + pub attributes: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, /// oneof to choose between specific header types #[prost(oneof="header::ContentHeader", tags="9, 10")] pub content_header: ::core::option::Option, @@ -1187,7 +1195,7 @@ pub mod data_stream { #[prost(message, tag="9")] TextHeader(super::TextHeader), #[prost(message, tag="10")] - FileHeader(super::FileHeader), + ByteHeader(super::ByteHeader), } } #[allow(clippy::derive_partial_eq_without_eq)] @@ -1219,7 +1227,7 @@ pub mod data_stream { pub reason: ::prost::alloc::string::String, /// finalizing updates for the stream, can also include additional insights for errors or endTime for transcription #[prost(map="string, string", tag="3")] - pub extensions: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + pub attributes: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, } /// enum for operation types (specific to TextHeader) #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] @@ -4785,6 +4793,8 @@ pub struct GetSipOutboundTrunkResponse { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ListSipTrunkRequest { + #[prost(message, optional, tag="1")] + pub page: ::core::option::Option, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -4796,6 +4806,8 @@ pub struct ListSipTrunkResponse { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ListSipInboundTrunkRequest { + #[prost(message, optional, tag="3")] + pub page: ::core::option::Option, /// Trunk IDs to list. If this option is set, the response will contains trunks in the same order. /// If any of the trunks is missing, a nil item in that position will be sent in the response. #[prost(string, repeated, tag="1")] @@ -4814,6 +4826,8 @@ pub struct ListSipInboundTrunkResponse { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ListSipOutboundTrunkRequest { + #[prost(message, optional, tag="3")] + pub page: ::core::option::Option, /// Trunk IDs to list. If this option is set, the response will contains trunks in the same order. /// If any of the trunks is missing, a nil item in that position will be sent in the response. #[prost(string, repeated, tag="1")] @@ -4966,6 +4980,8 @@ pub struct SipDispatchRuleInfo { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ListSipDispatchRuleRequest { + #[prost(message, optional, tag="3")] + pub page: ::core::option::Option, /// Rule IDs to list. If this option is set, the response will contains rules in the same order. /// If any of the rules is missing, a nil item in that position will be sent in the response. #[prost(string, repeated, tag="1")] diff --git a/livekit-protocol/src/livekit.serde.rs b/livekit-protocol/src/livekit.serde.rs index ae08809a4..b908afb0c 100644 --- a/livekit-protocol/src/livekit.serde.rs +++ b/livekit-protocol/src/livekit.serde.rs @@ -5571,6 +5571,101 @@ impl<'de> serde::Deserialize<'de> for DataStream { deserializer.deserialize_struct("livekit.DataStream", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for data_stream::ByteHeader { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.name.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.DataStream.ByteHeader", len)?; + if !self.name.is_empty() { + struct_ser.serialize_field("name", &self.name)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for data_stream::ByteHeader { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "name", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Name, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "name" => Ok(GeneratedField::Name), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = data_stream::ByteHeader; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.DataStream.ByteHeader") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut name__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Name => { + if name__.is_some() { + return Err(serde::de::Error::duplicate_field("name")); + } + name__ = Some(map_.next_value()?); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(data_stream::ByteHeader { + name: name__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.DataStream.ByteHeader", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for data_stream::Chunk { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -5750,102 +5845,6 @@ impl<'de> serde::Deserialize<'de> for data_stream::Chunk { deserializer.deserialize_struct("livekit.DataStream.Chunk", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for data_stream::FileHeader { - #[allow(deprecated)] - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::Serializer, - { - use serde::ser::SerializeStruct; - let mut len = 0; - if !self.file_name.is_empty() { - len += 1; - } - let mut struct_ser = serializer.serialize_struct("livekit.DataStream.FileHeader", len)?; - if !self.file_name.is_empty() { - struct_ser.serialize_field("fileName", &self.file_name)?; - } - struct_ser.end() - } -} -impl<'de> serde::Deserialize<'de> for data_stream::FileHeader { - #[allow(deprecated)] - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - const FIELDS: &[&str] = &[ - "file_name", - "fileName", - ]; - - #[allow(clippy::enum_variant_names)] - enum GeneratedField { - FileName, - __SkipField__, - } - impl<'de> serde::Deserialize<'de> for GeneratedField { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - struct GeneratedVisitor; - - impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = GeneratedField; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(formatter, "expected one of: {:?}", &FIELDS) - } - - #[allow(unused_variables)] - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - match value { - "fileName" | "file_name" => Ok(GeneratedField::FileName), - _ => Ok(GeneratedField::__SkipField__), - } - } - } - deserializer.deserialize_identifier(GeneratedVisitor) - } - } - struct GeneratedVisitor; - impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = data_stream::FileHeader; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.DataStream.FileHeader") - } - - fn visit_map(self, mut map_: V) -> std::result::Result - where - V: serde::de::MapAccess<'de>, - { - let mut file_name__ = None; - while let Some(k) = map_.next_key()? { - match k { - GeneratedField::FileName => { - if file_name__.is_some() { - return Err(serde::de::Error::duplicate_field("fileName")); - } - file_name__ = Some(map_.next_value()?); - } - GeneratedField::__SkipField__ => { - let _ = map_.next_value::()?; - } - } - } - Ok(data_stream::FileHeader { - file_name: file_name__.unwrap_or_default(), - }) - } - } - deserializer.deserialize_struct("livekit.DataStream.FileHeader", FIELDS, GeneratedVisitor) - } -} impl serde::Serialize for data_stream::Header { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -5872,7 +5871,7 @@ impl serde::Serialize for data_stream::Header { if self.encryption_type != 0 { len += 1; } - if !self.extensions.is_empty() { + if !self.attributes.is_empty() { len += 1; } if self.content_header.is_some() { @@ -5903,16 +5902,16 @@ impl serde::Serialize for data_stream::Header { .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.encryption_type)))?; struct_ser.serialize_field("encryptionType", &v)?; } - if !self.extensions.is_empty() { - struct_ser.serialize_field("extensions", &self.extensions)?; + if !self.attributes.is_empty() { + struct_ser.serialize_field("attributes", &self.attributes)?; } if let Some(v) = self.content_header.as_ref() { match v { data_stream::header::ContentHeader::TextHeader(v) => { struct_ser.serialize_field("textHeader", v)?; } - data_stream::header::ContentHeader::FileHeader(v) => { - struct_ser.serialize_field("fileHeader", v)?; + data_stream::header::ContentHeader::ByteHeader(v) => { + struct_ser.serialize_field("byteHeader", v)?; } } } @@ -5936,11 +5935,11 @@ impl<'de> serde::Deserialize<'de> for data_stream::Header { "totalLength", "encryption_type", "encryptionType", - "extensions", + "attributes", "text_header", "textHeader", - "file_header", - "fileHeader", + "byte_header", + "byteHeader", ]; #[allow(clippy::enum_variant_names)] @@ -5951,9 +5950,9 @@ impl<'de> serde::Deserialize<'de> for data_stream::Header { MimeType, TotalLength, EncryptionType, - Extensions, + Attributes, TextHeader, - FileHeader, + ByteHeader, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -5982,9 +5981,9 @@ impl<'de> serde::Deserialize<'de> for data_stream::Header { "mimeType" | "mime_type" => Ok(GeneratedField::MimeType), "totalLength" | "total_length" => Ok(GeneratedField::TotalLength), "encryptionType" | "encryption_type" => Ok(GeneratedField::EncryptionType), - "extensions" => Ok(GeneratedField::Extensions), + "attributes" => Ok(GeneratedField::Attributes), "textHeader" | "text_header" => Ok(GeneratedField::TextHeader), - "fileHeader" | "file_header" => Ok(GeneratedField::FileHeader), + "byteHeader" | "byte_header" => Ok(GeneratedField::ByteHeader), _ => Ok(GeneratedField::__SkipField__), } } @@ -6010,7 +6009,7 @@ impl<'de> serde::Deserialize<'de> for data_stream::Header { let mut mime_type__ = None; let mut total_length__ = None; let mut encryption_type__ = None; - let mut extensions__ = None; + let mut attributes__ = None; let mut content_header__ = None; while let Some(k) = map_.next_key()? { match k { @@ -6054,11 +6053,11 @@ impl<'de> serde::Deserialize<'de> for data_stream::Header { } encryption_type__ = Some(map_.next_value::()? as i32); } - GeneratedField::Extensions => { - if extensions__.is_some() { - return Err(serde::de::Error::duplicate_field("extensions")); + GeneratedField::Attributes => { + if attributes__.is_some() { + return Err(serde::de::Error::duplicate_field("attributes")); } - extensions__ = Some( + attributes__ = Some( map_.next_value::>()? ); } @@ -6069,11 +6068,11 @@ impl<'de> serde::Deserialize<'de> for data_stream::Header { content_header__ = map_.next_value::<::std::option::Option<_>>()?.map(data_stream::header::ContentHeader::TextHeader) ; } - GeneratedField::FileHeader => { + GeneratedField::ByteHeader => { if content_header__.is_some() { - return Err(serde::de::Error::duplicate_field("fileHeader")); + return Err(serde::de::Error::duplicate_field("byteHeader")); } - content_header__ = map_.next_value::<::std::option::Option<_>>()?.map(data_stream::header::ContentHeader::FileHeader) + content_header__ = map_.next_value::<::std::option::Option<_>>()?.map(data_stream::header::ContentHeader::ByteHeader) ; } GeneratedField::__SkipField__ => { @@ -6088,7 +6087,7 @@ impl<'de> serde::Deserialize<'de> for data_stream::Header { mime_type: mime_type__.unwrap_or_default(), total_length: total_length__, encryption_type: encryption_type__.unwrap_or_default(), - extensions: extensions__.unwrap_or_default(), + attributes: attributes__.unwrap_or_default(), content_header: content_header__, }) } @@ -6357,7 +6356,7 @@ impl serde::Serialize for data_stream::Trailer { if !self.reason.is_empty() { len += 1; } - if !self.extensions.is_empty() { + if !self.attributes.is_empty() { len += 1; } let mut struct_ser = serializer.serialize_struct("livekit.DataStream.Trailer", len)?; @@ -6367,8 +6366,8 @@ impl serde::Serialize for data_stream::Trailer { if !self.reason.is_empty() { struct_ser.serialize_field("reason", &self.reason)?; } - if !self.extensions.is_empty() { - struct_ser.serialize_field("extensions", &self.extensions)?; + if !self.attributes.is_empty() { + struct_ser.serialize_field("attributes", &self.attributes)?; } struct_ser.end() } @@ -6383,14 +6382,14 @@ impl<'de> serde::Deserialize<'de> for data_stream::Trailer { "stream_id", "streamId", "reason", - "extensions", + "attributes", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { StreamId, Reason, - Extensions, + Attributes, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -6415,7 +6414,7 @@ impl<'de> serde::Deserialize<'de> for data_stream::Trailer { match value { "streamId" | "stream_id" => Ok(GeneratedField::StreamId), "reason" => Ok(GeneratedField::Reason), - "extensions" => Ok(GeneratedField::Extensions), + "attributes" => Ok(GeneratedField::Attributes), _ => Ok(GeneratedField::__SkipField__), } } @@ -6437,7 +6436,7 @@ impl<'de> serde::Deserialize<'de> for data_stream::Trailer { { let mut stream_id__ = None; let mut reason__ = None; - let mut extensions__ = None; + let mut attributes__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::StreamId => { @@ -6452,11 +6451,11 @@ impl<'de> serde::Deserialize<'de> for data_stream::Trailer { } reason__ = Some(map_.next_value()?); } - GeneratedField::Extensions => { - if extensions__.is_some() { - return Err(serde::de::Error::duplicate_field("extensions")); + GeneratedField::Attributes => { + if attributes__.is_some() { + return Err(serde::de::Error::duplicate_field("attributes")); } - extensions__ = Some( + attributes__ = Some( map_.next_value::>()? ); } @@ -6468,7 +6467,7 @@ impl<'de> serde::Deserialize<'de> for data_stream::Trailer { Ok(data_stream::Trailer { stream_id: stream_id__.unwrap_or_default(), reason: reason__.unwrap_or_default(), - extensions: extensions__.unwrap_or_default(), + attributes: attributes__.unwrap_or_default(), }) } } @@ -14883,6 +14882,9 @@ impl serde::Serialize for ListSipDispatchRuleRequest { { use serde::ser::SerializeStruct; let mut len = 0; + if self.page.is_some() { + len += 1; + } if !self.dispatch_rule_ids.is_empty() { len += 1; } @@ -14890,6 +14892,9 @@ impl serde::Serialize for ListSipDispatchRuleRequest { len += 1; } let mut struct_ser = serializer.serialize_struct("livekit.ListSIPDispatchRuleRequest", len)?; + if let Some(v) = self.page.as_ref() { + struct_ser.serialize_field("page", v)?; + } if !self.dispatch_rule_ids.is_empty() { struct_ser.serialize_field("dispatchRuleIds", &self.dispatch_rule_ids)?; } @@ -14906,6 +14911,7 @@ impl<'de> serde::Deserialize<'de> for ListSipDispatchRuleRequest { D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ + "page", "dispatch_rule_ids", "dispatchRuleIds", "trunk_ids", @@ -14914,6 +14920,7 @@ impl<'de> serde::Deserialize<'de> for ListSipDispatchRuleRequest { #[allow(clippy::enum_variant_names)] enum GeneratedField { + Page, DispatchRuleIds, TrunkIds, __SkipField__, @@ -14938,6 +14945,7 @@ impl<'de> serde::Deserialize<'de> for ListSipDispatchRuleRequest { E: serde::de::Error, { match value { + "page" => Ok(GeneratedField::Page), "dispatchRuleIds" | "dispatch_rule_ids" => Ok(GeneratedField::DispatchRuleIds), "trunkIds" | "trunk_ids" => Ok(GeneratedField::TrunkIds), _ => Ok(GeneratedField::__SkipField__), @@ -14959,10 +14967,17 @@ impl<'de> serde::Deserialize<'de> for ListSipDispatchRuleRequest { where V: serde::de::MapAccess<'de>, { + let mut page__ = None; let mut dispatch_rule_ids__ = None; let mut trunk_ids__ = None; while let Some(k) = map_.next_key()? { match k { + GeneratedField::Page => { + if page__.is_some() { + return Err(serde::de::Error::duplicate_field("page")); + } + page__ = map_.next_value()?; + } GeneratedField::DispatchRuleIds => { if dispatch_rule_ids__.is_some() { return Err(serde::de::Error::duplicate_field("dispatchRuleIds")); @@ -14981,6 +14996,7 @@ impl<'de> serde::Deserialize<'de> for ListSipDispatchRuleRequest { } } Ok(ListSipDispatchRuleRequest { + page: page__, dispatch_rule_ids: dispatch_rule_ids__.unwrap_or_default(), trunk_ids: trunk_ids__.unwrap_or_default(), }) @@ -15092,6 +15108,9 @@ impl serde::Serialize for ListSipInboundTrunkRequest { { use serde::ser::SerializeStruct; let mut len = 0; + if self.page.is_some() { + len += 1; + } if !self.trunk_ids.is_empty() { len += 1; } @@ -15099,6 +15118,9 @@ impl serde::Serialize for ListSipInboundTrunkRequest { len += 1; } let mut struct_ser = serializer.serialize_struct("livekit.ListSIPInboundTrunkRequest", len)?; + if let Some(v) = self.page.as_ref() { + struct_ser.serialize_field("page", v)?; + } if !self.trunk_ids.is_empty() { struct_ser.serialize_field("trunkIds", &self.trunk_ids)?; } @@ -15115,6 +15137,7 @@ impl<'de> serde::Deserialize<'de> for ListSipInboundTrunkRequest { D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ + "page", "trunk_ids", "trunkIds", "numbers", @@ -15122,6 +15145,7 @@ impl<'de> serde::Deserialize<'de> for ListSipInboundTrunkRequest { #[allow(clippy::enum_variant_names)] enum GeneratedField { + Page, TrunkIds, Numbers, __SkipField__, @@ -15146,6 +15170,7 @@ impl<'de> serde::Deserialize<'de> for ListSipInboundTrunkRequest { E: serde::de::Error, { match value { + "page" => Ok(GeneratedField::Page), "trunkIds" | "trunk_ids" => Ok(GeneratedField::TrunkIds), "numbers" => Ok(GeneratedField::Numbers), _ => Ok(GeneratedField::__SkipField__), @@ -15167,10 +15192,17 @@ impl<'de> serde::Deserialize<'de> for ListSipInboundTrunkRequest { where V: serde::de::MapAccess<'de>, { + let mut page__ = None; let mut trunk_ids__ = None; let mut numbers__ = None; while let Some(k) = map_.next_key()? { match k { + GeneratedField::Page => { + if page__.is_some() { + return Err(serde::de::Error::duplicate_field("page")); + } + page__ = map_.next_value()?; + } GeneratedField::TrunkIds => { if trunk_ids__.is_some() { return Err(serde::de::Error::duplicate_field("trunkIds")); @@ -15189,6 +15221,7 @@ impl<'de> serde::Deserialize<'de> for ListSipInboundTrunkRequest { } } Ok(ListSipInboundTrunkRequest { + page: page__, trunk_ids: trunk_ids__.unwrap_or_default(), numbers: numbers__.unwrap_or_default(), }) @@ -15300,6 +15333,9 @@ impl serde::Serialize for ListSipOutboundTrunkRequest { { use serde::ser::SerializeStruct; let mut len = 0; + if self.page.is_some() { + len += 1; + } if !self.trunk_ids.is_empty() { len += 1; } @@ -15307,6 +15343,9 @@ impl serde::Serialize for ListSipOutboundTrunkRequest { len += 1; } let mut struct_ser = serializer.serialize_struct("livekit.ListSIPOutboundTrunkRequest", len)?; + if let Some(v) = self.page.as_ref() { + struct_ser.serialize_field("page", v)?; + } if !self.trunk_ids.is_empty() { struct_ser.serialize_field("trunkIds", &self.trunk_ids)?; } @@ -15323,6 +15362,7 @@ impl<'de> serde::Deserialize<'de> for ListSipOutboundTrunkRequest { D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ + "page", "trunk_ids", "trunkIds", "numbers", @@ -15330,6 +15370,7 @@ impl<'de> serde::Deserialize<'de> for ListSipOutboundTrunkRequest { #[allow(clippy::enum_variant_names)] enum GeneratedField { + Page, TrunkIds, Numbers, __SkipField__, @@ -15354,6 +15395,7 @@ impl<'de> serde::Deserialize<'de> for ListSipOutboundTrunkRequest { E: serde::de::Error, { match value { + "page" => Ok(GeneratedField::Page), "trunkIds" | "trunk_ids" => Ok(GeneratedField::TrunkIds), "numbers" => Ok(GeneratedField::Numbers), _ => Ok(GeneratedField::__SkipField__), @@ -15375,10 +15417,17 @@ impl<'de> serde::Deserialize<'de> for ListSipOutboundTrunkRequest { where V: serde::de::MapAccess<'de>, { + let mut page__ = None; let mut trunk_ids__ = None; let mut numbers__ = None; while let Some(k) = map_.next_key()? { match k { + GeneratedField::Page => { + if page__.is_some() { + return Err(serde::de::Error::duplicate_field("page")); + } + page__ = map_.next_value()?; + } GeneratedField::TrunkIds => { if trunk_ids__.is_some() { return Err(serde::de::Error::duplicate_field("trunkIds")); @@ -15397,6 +15446,7 @@ impl<'de> serde::Deserialize<'de> for ListSipOutboundTrunkRequest { } } Ok(ListSipOutboundTrunkRequest { + page: page__, trunk_ids: trunk_ids__.unwrap_or_default(), numbers: numbers__.unwrap_or_default(), }) @@ -15507,8 +15557,14 @@ impl serde::Serialize for ListSipTrunkRequest { S: serde::Serializer, { use serde::ser::SerializeStruct; - let len = 0; - let struct_ser = serializer.serialize_struct("livekit.ListSIPTrunkRequest", len)?; + let mut len = 0; + if self.page.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.ListSIPTrunkRequest", len)?; + if let Some(v) = self.page.as_ref() { + struct_ser.serialize_field("page", v)?; + } struct_ser.end() } } @@ -15519,10 +15575,12 @@ impl<'de> serde::Deserialize<'de> for ListSipTrunkRequest { D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ + "page", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { + Page, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -15544,7 +15602,10 @@ impl<'de> serde::Deserialize<'de> for ListSipTrunkRequest { where E: serde::de::Error, { - Ok(GeneratedField::__SkipField__) + match value { + "page" => Ok(GeneratedField::Page), + _ => Ok(GeneratedField::__SkipField__), + } } } deserializer.deserialize_identifier(GeneratedVisitor) @@ -15562,10 +15623,22 @@ impl<'de> serde::Deserialize<'de> for ListSipTrunkRequest { where V: serde::de::MapAccess<'de>, { - while map_.next_key::()?.is_some() { - let _ = map_.next_value::()?; + let mut page__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Page => { + if page__.is_some() { + return Err(serde::de::Error::duplicate_field("page")); + } + page__ = map_.next_value()?; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } } Ok(ListSipTrunkRequest { + page: page__, }) } } @@ -16553,6 +16626,121 @@ impl<'de> serde::Deserialize<'de> for MuteTrackRequest { deserializer.deserialize_struct("livekit.MuteTrackRequest", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for Pagination { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.after_id.is_empty() { + len += 1; + } + if self.limit != 0 { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.Pagination", len)?; + if !self.after_id.is_empty() { + struct_ser.serialize_field("afterId", &self.after_id)?; + } + if self.limit != 0 { + struct_ser.serialize_field("limit", &self.limit)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for Pagination { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "after_id", + "afterId", + "limit", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + AfterId, + Limit, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "afterId" | "after_id" => Ok(GeneratedField::AfterId), + "limit" => Ok(GeneratedField::Limit), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = Pagination; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.Pagination") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut after_id__ = None; + let mut limit__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::AfterId => { + if after_id__.is_some() { + return Err(serde::de::Error::duplicate_field("afterId")); + } + after_id__ = Some(map_.next_value()?); + } + GeneratedField::Limit => { + if limit__.is_some() { + return Err(serde::de::Error::duplicate_field("limit")); + } + limit__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(Pagination { + after_id: after_id__.unwrap_or_default(), + limit: limit__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.Pagination", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for ParticipantEgressRequest { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result From 18a9fc8c16836c05df33c66896ae3c2c277b198b Mon Sep 17 00:00:00 2001 From: lukasIO Date: Thu, 23 Jan 2025 11:59:10 +0100 Subject: [PATCH 132/274] Add nanpa changeset for #555 (#557) * Add nanpa changeset * move --- livekit-ffi/.nanpa/data-stream-naming.kdl | 1 + 1 file changed, 1 insertion(+) create mode 100644 livekit-ffi/.nanpa/data-stream-naming.kdl diff --git a/livekit-ffi/.nanpa/data-stream-naming.kdl b/livekit-ffi/.nanpa/data-stream-naming.kdl new file mode 100644 index 000000000..10c1a770f --- /dev/null +++ b/livekit-ffi/.nanpa/data-stream-naming.kdl @@ -0,0 +1 @@ +patch type="changed" "Rename DataStream header properties" \ No newline at end of file From df4d753663bb6e4be4198833b4923cef3924278d Mon Sep 17 00:00:00 2001 From: "ilo-nanpa[bot]" <174912985+ilo-nanpa[bot]@users.noreply.github.com> Date: Thu, 23 Jan 2025 11:16:32 +0000 Subject: [PATCH 133/274] nanpa: bump --- Cargo.toml | 2 +- livekit-ffi/.nanpa/data-stream-naming.kdl | 1 - livekit-ffi/.nanparc | 2 +- livekit-ffi/CHANGELOG.md | 6 ++++++ livekit-ffi/Cargo.toml | 2 +- 5 files changed, 9 insertions(+), 4 deletions(-) delete mode 100644 livekit-ffi/.nanpa/data-stream-naming.kdl diff --git a/Cargo.toml b/Cargo.toml index d1acc9044..0d7531eb2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ imgproc = { version = "0.3.12", path = "imgproc" } yuv-sys = { version = "0.3.7", path = "yuv-sys" } libwebrtc = { version = "0.3.9", path = "libwebrtc" } livekit-api = { version = "0.4.1", path = "livekit-api" } -livekit-ffi = { version = "0.12.7", path = "livekit-ffi" } +livekit-ffi = { version = "0.12.8", path = "livekit-ffi" } livekit-protocol = { version = "0.3.7", path = "livekit-protocol" } livekit-runtime = { version = "0.3.1", path = "livekit-runtime" } livekit = { version = "0.7.3", path = "livekit" } diff --git a/livekit-ffi/.nanpa/data-stream-naming.kdl b/livekit-ffi/.nanpa/data-stream-naming.kdl deleted file mode 100644 index 10c1a770f..000000000 --- a/livekit-ffi/.nanpa/data-stream-naming.kdl +++ /dev/null @@ -1 +0,0 @@ -patch type="changed" "Rename DataStream header properties" \ No newline at end of file diff --git a/livekit-ffi/.nanparc b/livekit-ffi/.nanparc index ea03152fb..e40b0457b 100644 --- a/livekit-ffi/.nanparc +++ b/livekit-ffi/.nanparc @@ -1,2 +1,2 @@ -version 0.12.7 +version 0.12.8 language rust diff --git a/livekit-ffi/CHANGELOG.md b/livekit-ffi/CHANGELOG.md index 7db2c6d57..d95ff2186 100644 --- a/livekit-ffi/CHANGELOG.md +++ b/livekit-ffi/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [0.12.8] - 2025-01-23 + +### Changed + +- Rename DataStream header properties + ## [0.12.7] - 2025-01-17 ### Added diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 3895227e8..b50d56730 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.12.7" +version = "0.12.8" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" From 370d2ac9aae8fc8c43ae181da3512425fe9e730b Mon Sep 17 00:00:00 2001 From: lukasIO Date: Tue, 28 Jan 2025 13:53:12 +0100 Subject: [PATCH 134/274] Update protocol and add SendDataRequest nonce (#560) * Update protocol and add SendDataRequest nonce * changeset * typo --- .nanpa/send-data-nonce.kdl | 1 + Cargo.lock | 116 +++++++++++++++++++++---- livekit-api/Cargo.toml | 1 + livekit-api/src/services/room.rs | 4 + livekit-ffi/protocol/video_frame.proto | 2 +- livekit-protocol/protocol | 2 +- livekit-protocol/src/livekit.rs | 11 +++ livekit-protocol/src/livekit.serde.rs | 86 ++++++++++++++++++ 8 files changed, 204 insertions(+), 19 deletions(-) create mode 100644 .nanpa/send-data-nonce.kdl diff --git a/.nanpa/send-data-nonce.kdl b/.nanpa/send-data-nonce.kdl new file mode 100644 index 000000000..b4d2a4f0e --- /dev/null +++ b/.nanpa/send-data-nonce.kdl @@ -0,0 +1 @@ +patch package="livekit-api" type="added" "Update protocol and add SendDataRequest nonce" diff --git a/Cargo.lock b/Cargo.lock index b0532c222..37b881351 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1064,10 +1064,22 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets 0.52.0", +] + [[package]] name = "gimli" version = "0.28.1" @@ -1521,9 +1533,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.151" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libloading" @@ -1537,7 +1549,7 @@ dependencies = [ [[package]] name = "libwebrtc" -version = "0.3.8" +version = "0.3.9" dependencies = [ "cxx", "env_logger", @@ -1593,7 +1605,7 @@ checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "livekit" -version = "0.7.2" +version = "0.7.3" dependencies = [ "chrono", "futures-util", @@ -1628,6 +1640,7 @@ dependencies = [ "parking_lot", "pbjson-types", "prost 0.12.3", + "rand 0.9.0", "reqwest", "scopeguard", "serde", @@ -1641,7 +1654,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.12.6" +version = "0.12.8" dependencies = [ "console-subscriber", "dashmap", @@ -1666,7 +1679,7 @@ dependencies = [ [[package]] name = "livekit-protocol" -version = "0.3.6" +version = "0.3.7" dependencies = [ "futures-util", "livekit-runtime", @@ -1760,7 +1773,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.48.0", ] @@ -1925,7 +1938,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" dependencies = [ "base64ct", - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -2212,8 +2225,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.0", + "zerocopy", ] [[package]] @@ -2223,7 +2247,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.0", ] [[package]] @@ -2232,7 +2266,17 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.11", +] + +[[package]] +name = "rand_core" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff" +dependencies = [ + "getrandom 0.3.1", + "zerocopy", ] [[package]] @@ -2359,7 +2403,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" dependencies = [ "cc", - "getrandom", + "getrandom 0.2.11", "libc", "spin", "untrusted", @@ -2971,7 +3015,7 @@ dependencies = [ "indexmap 1.9.3", "pin-project", "pin-project-lite", - "rand", + "rand 0.8.5", "slab", "tokio", "tokio-util", @@ -3070,7 +3114,7 @@ dependencies = [ "httparse", "log", "native-tls", - "rand", + "rand 0.8.5", "rustls", "sha1", "thiserror", @@ -3091,7 +3135,7 @@ dependencies = [ "httparse", "log", "native-tls", - "rand", + "rand 0.8.5", "sha1", "thiserror", "url", @@ -3209,6 +3253,15 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasm-bindgen" version = "0.2.89" @@ -3576,6 +3629,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags 2.4.1", +] + [[package]] name = "yuv-sys" version = "0.3.7" @@ -3586,6 +3648,26 @@ dependencies = [ "regex", ] +[[package]] +name = "zerocopy" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a367f292d93d4eab890745e75a778da40909cab4d6ff8173693812f79c4a2468" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3931cb58c62c13adec22e38686b559c86a30565e16ad6e8510a337cedc611e1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.50", +] + [[package]] name = "zip" version = "0.6.6" diff --git a/livekit-api/Cargo.toml b/livekit-api/Cargo.toml index 7a843f78c..ec94be6d3 100644 --- a/livekit-api/Cargo.toml +++ b/livekit-api/Cargo.toml @@ -95,3 +95,4 @@ reqwest = { version = "0.11", default-features = false, features = [ "json" ], o isahc = { version = "1.7.2", default-features = false, features = [ "json", "text-decoding" ], optional = true } scopeguard = "1.2.0" +rand = "0.9.0" diff --git a/livekit-api/src/services/room.rs b/livekit-api/src/services/room.rs index 0bdae45b2..006e54ee4 100644 --- a/livekit-api/src/services/room.rs +++ b/livekit-api/src/services/room.rs @@ -17,6 +17,7 @@ use std::collections::HashMap; use super::{ServiceBase, ServiceResult, LIVEKIT_PACKAGE}; use crate::{access_token::VideoGrants, get_env_keys, services::twirp_client::TwirpClient}; +use rand::Rng; const SVC: &str = "RoomService"; @@ -288,6 +289,8 @@ impl RoomClient { data: Vec, options: SendDataOptions, ) -> ServiceResult<()> { + let mut rng = rand::rng(); + let nonce: Vec = (0..16).map(|_| rng.random::()).collect(); #[allow(deprecated)] self.client .request( @@ -300,6 +303,7 @@ impl RoomClient { topic: options.topic, kind: options.kind as i32, destination_identities: options.destination_identities, + nonce, }, self.base.auth_header( VideoGrants { room_admin: true, room: room.to_owned(), ..Default::default() }, diff --git a/livekit-ffi/protocol/video_frame.proto b/livekit-ffi/protocol/video_frame.proto index 528b14f43..6d41dc824 100644 --- a/livekit-ffi/protocol/video_frame.proto +++ b/livekit-ffi/protocol/video_frame.proto @@ -124,7 +124,7 @@ message VideoBufferInfo { required uint32 width = 2; required uint32 height = 3; required uint64 data_ptr = 4; - required uint32 stride = 6; // only for packed formats + optional uint32 stride = 6; // only for packed formats repeated ComponentInfo components = 7; } diff --git a/livekit-protocol/protocol b/livekit-protocol/protocol index afe463fe8..2e0a35efa 160000 --- a/livekit-protocol/protocol +++ b/livekit-protocol/protocol @@ -1 +1 @@ -Subproject commit afe463fe84852471ee403efba3c32451162cbffd +Subproject commit 2e0a35efa9382b8b3eabaaf6c867c3f487431dd5 diff --git a/livekit-protocol/src/livekit.rs b/livekit-protocol/src/livekit.rs index 9b6eaec82..c1f96e776 100644 --- a/livekit-protocol/src/livekit.rs +++ b/livekit-protocol/src/livekit.rs @@ -214,6 +214,8 @@ pub struct Room { pub max_participants: u32, #[prost(int64, tag="5")] pub creation_time: i64, + #[prost(int64, tag="15")] + pub creation_time_ms: i64, #[prost(string, tag="6")] pub turn_password: ::prost::alloc::string::String, #[prost(message, repeated, tag="7")] @@ -298,6 +300,9 @@ pub struct ParticipantInfo { /// timestamp when participant joined room, in seconds #[prost(int64, tag="6")] pub joined_at: i64, + /// timestamp when participant joined room, in milliseconds + #[prost(int64, tag="17")] + pub joined_at_ms: i64, #[prost(string, tag="9")] pub name: ::prost::alloc::string::String, #[prost(uint32, tag="10")] @@ -634,6 +639,9 @@ pub struct UserPacket { pub start_time: ::core::option::Option, #[prost(uint64, optional, tag="10")] pub end_time: ::core::option::Option, + /// added by SDK to enable de-duping of messages, for INTERNAL USE ONLY + #[prost(bytes="vec", tag="11")] + pub nonce: ::prost::alloc::vec::Vec, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -4044,6 +4052,9 @@ pub struct SendDataRequest { pub destination_identities: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, #[prost(string, optional, tag="5")] pub topic: ::core::option::Option<::prost::alloc::string::String>, + /// added by SDK to enable de-duping of messages, for INTERNAL USE ONLY + #[prost(bytes="vec", tag="7")] + pub nonce: ::prost::alloc::vec::Vec, } /// #[allow(clippy::derive_partial_eq_without_eq)] diff --git a/livekit-protocol/src/livekit.serde.rs b/livekit-protocol/src/livekit.serde.rs index b908afb0c..7dc86d65a 100644 --- a/livekit-protocol/src/livekit.serde.rs +++ b/livekit-protocol/src/livekit.serde.rs @@ -17006,6 +17006,9 @@ impl serde::Serialize for ParticipantInfo { if self.joined_at != 0 { len += 1; } + if self.joined_at_ms != 0 { + len += 1; + } if !self.name.is_empty() { len += 1; } @@ -17053,6 +17056,11 @@ impl serde::Serialize for ParticipantInfo { #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("joinedAt", ToString::to_string(&self.joined_at).as_str())?; } + if self.joined_at_ms != 0 { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("joinedAtMs", ToString::to_string(&self.joined_at_ms).as_str())?; + } if !self.name.is_empty() { struct_ser.serialize_field("name", &self.name)?; } @@ -17098,6 +17106,8 @@ impl<'de> serde::Deserialize<'de> for ParticipantInfo { "metadata", "joined_at", "joinedAt", + "joined_at_ms", + "joinedAtMs", "name", "version", "permission", @@ -17118,6 +17128,7 @@ impl<'de> serde::Deserialize<'de> for ParticipantInfo { Tracks, Metadata, JoinedAt, + JoinedAtMs, Name, Version, Permission, @@ -17154,6 +17165,7 @@ impl<'de> serde::Deserialize<'de> for ParticipantInfo { "tracks" => Ok(GeneratedField::Tracks), "metadata" => Ok(GeneratedField::Metadata), "joinedAt" | "joined_at" => Ok(GeneratedField::JoinedAt), + "joinedAtMs" | "joined_at_ms" => Ok(GeneratedField::JoinedAtMs), "name" => Ok(GeneratedField::Name), "version" => Ok(GeneratedField::Version), "permission" => Ok(GeneratedField::Permission), @@ -17187,6 +17199,7 @@ impl<'de> serde::Deserialize<'de> for ParticipantInfo { let mut tracks__ = None; let mut metadata__ = None; let mut joined_at__ = None; + let mut joined_at_ms__ = None; let mut name__ = None; let mut version__ = None; let mut permission__ = None; @@ -17235,6 +17248,14 @@ impl<'de> serde::Deserialize<'de> for ParticipantInfo { Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) ; } + GeneratedField::JoinedAtMs => { + if joined_at_ms__.is_some() { + return Err(serde::de::Error::duplicate_field("joinedAtMs")); + } + joined_at_ms__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } GeneratedField::Name => { if name__.is_some() { return Err(serde::de::Error::duplicate_field("name")); @@ -17299,6 +17320,7 @@ impl<'de> serde::Deserialize<'de> for ParticipantInfo { tracks: tracks__.unwrap_or_default(), metadata: metadata__.unwrap_or_default(), joined_at: joined_at__.unwrap_or_default(), + joined_at_ms: joined_at_ms__.unwrap_or_default(), name: name__.unwrap_or_default(), version: version__.unwrap_or_default(), permission: permission__, @@ -21346,6 +21368,9 @@ impl serde::Serialize for Room { if self.creation_time != 0 { len += 1; } + if self.creation_time_ms != 0 { + len += 1; + } if !self.turn_password.is_empty() { len += 1; } @@ -21388,6 +21413,11 @@ impl serde::Serialize for Room { #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("creationTime", ToString::to_string(&self.creation_time).as_str())?; } + if self.creation_time_ms != 0 { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("creationTimeMs", ToString::to_string(&self.creation_time_ms).as_str())?; + } if !self.turn_password.is_empty() { struct_ser.serialize_field("turnPassword", &self.turn_password)?; } @@ -21429,6 +21459,8 @@ impl<'de> serde::Deserialize<'de> for Room { "maxParticipants", "creation_time", "creationTime", + "creation_time_ms", + "creationTimeMs", "turn_password", "turnPassword", "enabled_codecs", @@ -21451,6 +21483,7 @@ impl<'de> serde::Deserialize<'de> for Room { DepartureTimeout, MaxParticipants, CreationTime, + CreationTimeMs, TurnPassword, EnabledCodecs, Metadata, @@ -21486,6 +21519,7 @@ impl<'de> serde::Deserialize<'de> for Room { "departureTimeout" | "departure_timeout" => Ok(GeneratedField::DepartureTimeout), "maxParticipants" | "max_participants" => Ok(GeneratedField::MaxParticipants), "creationTime" | "creation_time" => Ok(GeneratedField::CreationTime), + "creationTimeMs" | "creation_time_ms" => Ok(GeneratedField::CreationTimeMs), "turnPassword" | "turn_password" => Ok(GeneratedField::TurnPassword), "enabledCodecs" | "enabled_codecs" => Ok(GeneratedField::EnabledCodecs), "metadata" => Ok(GeneratedField::Metadata), @@ -21518,6 +21552,7 @@ impl<'de> serde::Deserialize<'de> for Room { let mut departure_timeout__ = None; let mut max_participants__ = None; let mut creation_time__ = None; + let mut creation_time_ms__ = None; let mut turn_password__ = None; let mut enabled_codecs__ = None; let mut metadata__ = None; @@ -21571,6 +21606,14 @@ impl<'de> serde::Deserialize<'de> for Room { Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) ; } + GeneratedField::CreationTimeMs => { + if creation_time_ms__.is_some() { + return Err(serde::de::Error::duplicate_field("creationTimeMs")); + } + creation_time_ms__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } GeneratedField::TurnPassword => { if turn_password__.is_some() { return Err(serde::de::Error::duplicate_field("turnPassword")); @@ -21629,6 +21672,7 @@ impl<'de> serde::Deserialize<'de> for Room { departure_timeout: departure_timeout__.unwrap_or_default(), max_participants: max_participants__.unwrap_or_default(), creation_time: creation_time__.unwrap_or_default(), + creation_time_ms: creation_time_ms__.unwrap_or_default(), turn_password: turn_password__.unwrap_or_default(), enabled_codecs: enabled_codecs__.unwrap_or_default(), metadata: metadata__.unwrap_or_default(), @@ -27257,6 +27301,9 @@ impl serde::Serialize for SendDataRequest { if self.topic.is_some() { len += 1; } + if !self.nonce.is_empty() { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.SendDataRequest", len)?; if !self.room.is_empty() { struct_ser.serialize_field("room", &self.room)?; @@ -27280,6 +27327,11 @@ impl serde::Serialize for SendDataRequest { if let Some(v) = self.topic.as_ref() { struct_ser.serialize_field("topic", v)?; } + if !self.nonce.is_empty() { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("nonce", pbjson::private::base64::encode(&self.nonce).as_str())?; + } struct_ser.end() } } @@ -27298,6 +27350,7 @@ impl<'de> serde::Deserialize<'de> for SendDataRequest { "destination_identities", "destinationIdentities", "topic", + "nonce", ]; #[allow(clippy::enum_variant_names)] @@ -27308,6 +27361,7 @@ impl<'de> serde::Deserialize<'de> for SendDataRequest { DestinationSids, DestinationIdentities, Topic, + Nonce, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -27336,6 +27390,7 @@ impl<'de> serde::Deserialize<'de> for SendDataRequest { "destinationSids" | "destination_sids" => Ok(GeneratedField::DestinationSids), "destinationIdentities" | "destination_identities" => Ok(GeneratedField::DestinationIdentities), "topic" => Ok(GeneratedField::Topic), + "nonce" => Ok(GeneratedField::Nonce), _ => Ok(GeneratedField::__SkipField__), } } @@ -27361,6 +27416,7 @@ impl<'de> serde::Deserialize<'de> for SendDataRequest { let mut destination_sids__ = None; let mut destination_identities__ = None; let mut topic__ = None; + let mut nonce__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::Room => { @@ -27401,6 +27457,14 @@ impl<'de> serde::Deserialize<'de> for SendDataRequest { } topic__ = map_.next_value()?; } + GeneratedField::Nonce => { + if nonce__.is_some() { + return Err(serde::de::Error::duplicate_field("nonce")); + } + nonce__ = + Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) + ; + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -27413,6 +27477,7 @@ impl<'de> serde::Deserialize<'de> for SendDataRequest { destination_sids: destination_sids__.unwrap_or_default(), destination_identities: destination_identities__.unwrap_or_default(), topic: topic__, + nonce: nonce__.unwrap_or_default(), }) } } @@ -36250,6 +36315,9 @@ impl serde::Serialize for UserPacket { if self.end_time.is_some() { len += 1; } + if !self.nonce.is_empty() { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.UserPacket", len)?; if !self.participant_sid.is_empty() { struct_ser.serialize_field("participantSid", &self.participant_sid)?; @@ -36284,6 +36352,11 @@ impl serde::Serialize for UserPacket { #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("endTime", ToString::to_string(&v).as_str())?; } + if !self.nonce.is_empty() { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("nonce", pbjson::private::base64::encode(&self.nonce).as_str())?; + } struct_ser.end() } } @@ -36309,6 +36382,7 @@ impl<'de> serde::Deserialize<'de> for UserPacket { "startTime", "end_time", "endTime", + "nonce", ]; #[allow(clippy::enum_variant_names)] @@ -36322,6 +36396,7 @@ impl<'de> serde::Deserialize<'de> for UserPacket { Id, StartTime, EndTime, + Nonce, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -36353,6 +36428,7 @@ impl<'de> serde::Deserialize<'de> for UserPacket { "id" => Ok(GeneratedField::Id), "startTime" | "start_time" => Ok(GeneratedField::StartTime), "endTime" | "end_time" => Ok(GeneratedField::EndTime), + "nonce" => Ok(GeneratedField::Nonce), _ => Ok(GeneratedField::__SkipField__), } } @@ -36381,6 +36457,7 @@ impl<'de> serde::Deserialize<'de> for UserPacket { let mut id__ = None; let mut start_time__ = None; let mut end_time__ = None; + let mut nonce__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::ParticipantSid => { @@ -36443,6 +36520,14 @@ impl<'de> serde::Deserialize<'de> for UserPacket { map_.next_value::<::std::option::Option<::pbjson::private::NumberDeserialize<_>>>()?.map(|x| x.0) ; } + GeneratedField::Nonce => { + if nonce__.is_some() { + return Err(serde::de::Error::duplicate_field("nonce")); + } + nonce__ = + Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) + ; + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -36458,6 +36543,7 @@ impl<'de> serde::Deserialize<'de> for UserPacket { id: id__, start_time: start_time__, end_time: end_time__, + nonce: nonce__.unwrap_or_default(), }) } } From e7e93c46ee0be22b4f5f3872eaa69145205b551e Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 28 Jan 2025 09:46:02 -0800 Subject: [PATCH 135/274] update readme (#562) --- .github/banner_dark.png | Bin 189438 -> 123952 bytes .github/banner_light.png | Bin 59295 -> 46138 bytes README.md | 7 ++++--- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/banner_dark.png b/.github/banner_dark.png index 002ec714ac56ff26a6b4bf571065b4fc31f50c5a..e9cb174e2dff1ac26c0949ffcc2776bcfe41bc0c 100644 GIT binary patch literal 123952 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 z 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

    #&dV#;Fm$; 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

    r%DTRYRj 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

    PNhtbCEuo5lM^u6OF|Tms@ugIx$L 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�$ProeuM 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

    {*gPqMoN0VJv2_lUB5^D#oIW~3(#_E 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 AnVn*h1={AyvlE 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

    doX%i&GYF^rQKzlN7ueoPhpM?;1y@ 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
  • *Uh{*P)zenwWRS^?NK#F`lM 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 E00Sev_5c6? diff --git a/README.md b/README.md index e305bd727..884333e13 100644 --- a/README.md +++ b/README.md @@ -188,9 +188,10 @@ We'll first use it as a basis for our Unity SDK (under development), but over ti
    - - - + + + + From 1d85bacb613bcb877df2c77e5e928158aaa81496 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Mon, 3 Feb 2025 09:47:30 -0800 Subject: [PATCH 136/274] implement track permission updates (#563) * implement track permission updates * fixed locks * fix build * formatting * changeset --- .nanpa/.keep | 0 .nanpa/track-permissions.kdl | 1 + livekit-ffi/protocol/ffi.proto | 11 +++-- livekit-ffi/protocol/track.proto | 18 ++++++++ livekit-ffi/src/conversion/track.rs | 30 ++++++++++++- livekit-ffi/src/livekit.proto.rs | 42 ++++++++++++++++--- livekit-ffi/src/server/colorcvt/cvtimpl.rs | 5 +++ livekit-ffi/src/server/colorcvt/mod.rs | 16 +++---- livekit-ffi/src/server/requests.rs | 15 +++++++ livekit-ffi/src/server/room.rs | 18 ++++++++ livekit/src/proto.rs | 25 +++++++++++ livekit/src/room/mod.rs | 10 +++++ .../src/room/participant/local_participant.rs | 38 ++++++++++++++++- livekit/src/room/participant/mod.rs | 7 ++++ 14 files changed, 217 insertions(+), 19 deletions(-) create mode 100644 .nanpa/.keep create mode 100644 .nanpa/track-permissions.kdl diff --git a/.nanpa/.keep b/.nanpa/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/.nanpa/track-permissions.kdl b/.nanpa/track-permissions.kdl new file mode 100644 index 000000000..004c675ae --- /dev/null +++ b/.nanpa/track-permissions.kdl @@ -0,0 +1 @@ +patch package="livekit-rtc" type="added" "Support for track subscription permissions" diff --git a/livekit-ffi/protocol/ffi.proto b/livekit-ffi/protocol/ffi.proto index 8ccd2d17a..584197348 100644 --- a/livekit-ffi/protocol/ffi.proto +++ b/livekit-ffi/protocol/ffi.proto @@ -46,7 +46,7 @@ import "rpc.proto"; // that it receives from the server. // // Therefore, the ffi client is easier to implement if there is less handles to manage. -// +// // - We are mainly using FfiHandle on info messages (e.g: RoomInfo, TrackInfo, etc...) // For this reason, info are only sent once, at creation (We're not using them for updates, we can infer them from // events on the client implementation). @@ -71,7 +71,6 @@ message FfiRequest { GetSessionStatsRequest get_session_stats = 12; PublishTranscriptionRequest publish_transcription = 13; PublishSipDtmfRequest publish_sip_dtmf = 14; - // Track CreateVideoTrackRequest create_video_track = 15; @@ -79,8 +78,9 @@ message FfiRequest { LocalTrackMuteRequest local_track_mute = 17; EnableRemoteTrackRequest enable_remote_track = 18; GetStatsRequest get_stats = 19; + SetTrackSubscriptionPermissionsRequest set_track_subscription_permissions = 48; -// Video + // Video NewVideoStreamRequest new_video_stream = 20; NewVideoSourceRequest new_video_source = 21; CaptureVideoFrameRequest capture_video_frame = 22; @@ -119,6 +119,8 @@ message FfiRequest { // Data Channel SetDataChannelBufferedAmountLowThresholdRequest set_data_channel_buffered_amount_low_threshold = 47; + + // NEXT_ID: 49 } } @@ -147,6 +149,7 @@ message FfiResponse { LocalTrackMuteResponse local_track_mute = 17; EnableRemoteTrackResponse enable_remote_track = 18; GetStatsResponse get_stats = 19; + SetTrackSubscriptionPermissionsResponse set_track_subscription_permissions = 47; // Video NewVideoStreamResponse new_video_stream = 20; @@ -184,6 +187,8 @@ message FfiResponse { // Data Channel SetDataChannelBufferedAmountLowThresholdResponse set_data_channel_buffered_amount_low_threshold = 46; + + // NEXT_ID: 48 } } diff --git a/livekit-ffi/protocol/track.proto b/livekit-ffi/protocol/track.proto index 4bebd399c..88a62c236 100644 --- a/livekit-ffi/protocol/track.proto +++ b/livekit-ffi/protocol/track.proto @@ -129,3 +129,21 @@ message EnableRemoteTrackRequest { message EnableRemoteTrackResponse { required bool enabled = 1; } + +message SetTrackSubscriptionPermissionsRequest { + required uint64 local_participant_handle = 1; + required bool all_participants_allowed = 2; + repeated ParticipantTrackPermission permissions = 3; +} + +message ParticipantTrackPermission { + // The participant identity this permission applies to. + required string participant_identity = 1; + // Grant permission to all all tracks. Takes precedence over allowedTrackSids. + optional bool allow_all = 2; + // List of track sids to grant permission to. + repeated string allowed_track_sids = 3; +} + +message SetTrackSubscriptionPermissionsResponse { +} diff --git a/livekit-ffi/src/conversion/track.rs b/livekit-ffi/src/conversion/track.rs index a839fa3f1..2e1d33f69 100644 --- a/livekit-ffi/src/conversion/track.rs +++ b/livekit-ffi/src/conversion/track.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use livekit::prelude::*; +use livekit::{participant::ParticipantTrackPermission, prelude::*}; use crate::{ proto, @@ -93,3 +93,31 @@ impl From for proto::TrackSource { } } } + +impl From for proto::ParticipantTrackPermission { + fn from(value: ParticipantTrackPermission) -> Self { + proto::ParticipantTrackPermission { + participant_identity: value.participant_identity.to_string(), + allow_all: Some(value.allow_all), + allowed_track_sids: value + .allowed_track_sids + .into_iter() + .map(|sid| sid.to_string()) + .collect(), + } + } +} + +impl From for ParticipantTrackPermission { + fn from(value: proto::ParticipantTrackPermission) -> Self { + Self { + participant_identity: value.participant_identity.into(), + allow_all: value.allow_all.unwrap_or(false), + allowed_track_sids: value + .allowed_track_sids + .into_iter() + .map(|sid| sid.try_into().unwrap()) + .collect(), + } + } +} diff --git a/livekit-ffi/src/livekit.proto.rs b/livekit-ffi/src/livekit.proto.rs index ec8a9d53c..0b93a7be8 100644 --- a/livekit-ffi/src/livekit.proto.rs +++ b/livekit-ffi/src/livekit.proto.rs @@ -1,5 +1,4 @@ // @generated -// This file is @generated by prost-build. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FrameCryptor { @@ -1455,6 +1454,33 @@ pub struct EnableRemoteTrackResponse { #[prost(bool, required, tag="1")] pub enabled: bool, } +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SetTrackSubscriptionPermissionsRequest { + #[prost(uint64, required, tag="1")] + pub local_participant_handle: u64, + #[prost(bool, required, tag="2")] + pub all_participants_allowed: bool, + #[prost(message, repeated, tag="3")] + pub permissions: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ParticipantTrackPermission { + /// The participant identity this permission applies to. + #[prost(string, required, tag="1")] + pub participant_identity: ::prost::alloc::string::String, + /// Grant permission to all all tracks. Takes precedence over allowedTrackSids. + #[prost(bool, optional, tag="2")] + pub allow_all: ::core::option::Option, + /// List of track sids to grant permission to. + #[prost(string, repeated, tag="3")] + pub allowed_track_sids: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SetTrackSubscriptionPermissionsResponse { +} #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] pub enum TrackKind { @@ -1844,8 +1870,8 @@ pub struct VideoBufferInfo { #[prost(uint64, required, tag="4")] pub data_ptr: u64, /// only for packed formats - #[prost(uint32, required, tag="6")] - pub stride: u32, + #[prost(uint32, optional, tag="6")] + pub stride: ::core::option::Option, #[prost(message, repeated, tag="7")] pub components: ::prost::alloc::vec::Vec, } @@ -4007,7 +4033,7 @@ pub struct RpcMethodInvocationEvent { // that it receives from the server. // // Therefore, the ffi client is easier to implement if there is less handles to manage. -// +// // - We are mainly using FfiHandle on info messages (e.g: RoomInfo, TrackInfo, etc...) // For this reason, info are only sent once, at creation (We're not using them for updates, we can infer them from // events on the client implementation). @@ -4018,7 +4044,7 @@ pub struct RpcMethodInvocationEvent { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiRequest { - #[prost(oneof="ffi_request::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47")] + #[prost(oneof="ffi_request::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 48, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47")] pub message: ::core::option::Option, } /// Nested message and enum types in `FfiRequest`. @@ -4064,6 +4090,8 @@ pub mod ffi_request { EnableRemoteTrack(super::EnableRemoteTrackRequest), #[prost(message, tag="19")] GetStats(super::GetStatsRequest), + #[prost(message, tag="48")] + SetTrackSubscriptionPermissions(super::SetTrackSubscriptionPermissionsRequest), /// Video #[prost(message, tag="20")] NewVideoStream(super::NewVideoStreamRequest), @@ -4132,7 +4160,7 @@ pub mod ffi_request { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiResponse { - #[prost(oneof="ffi_response::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46")] + #[prost(oneof="ffi_response::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 47, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46")] pub message: ::core::option::Option, } /// Nested message and enum types in `FfiResponse`. @@ -4178,6 +4206,8 @@ pub mod ffi_response { EnableRemoteTrack(super::EnableRemoteTrackResponse), #[prost(message, tag="19")] GetStats(super::GetStatsResponse), + #[prost(message, tag="47")] + SetTrackSubscriptionPermissions(super::SetTrackSubscriptionPermissionsResponse), /// Video #[prost(message, tag="20")] NewVideoStream(super::NewVideoStreamResponse), diff --git a/livekit-ffi/src/server/colorcvt/cvtimpl.rs b/livekit-ffi/src/server/colorcvt/cvtimpl.rs index b6ebcb631..5540190aa 100644 --- a/livekit-ffi/src/server/colorcvt/cvtimpl.rs +++ b/livekit-ffi/src/server/colorcvt/cvtimpl.rs @@ -30,6 +30,7 @@ pub unsafe fn cvt_rgba( ) -> FfiResult<(Box<[u8]>, proto::VideoBufferInfo)> { assert_eq!(buffer.r#type(), proto::VideoBufferType::Rgba); let proto::VideoBufferInfo { stride, width, height, data_ptr, .. } = buffer; + let stride = stride.unwrap_or(width * 4); let data_len = (stride * height) as usize; let data = unsafe { slice::from_raw_parts(data_ptr as *const u8, data_len as usize) }; @@ -85,6 +86,7 @@ pub unsafe fn cvt_abgr( ) -> FfiResult<(Box<[u8]>, proto::VideoBufferInfo)> { assert_eq!(buffer.r#type(), proto::VideoBufferType::Rgba); let proto::VideoBufferInfo { stride, width, height, data_ptr, .. } = buffer; + let stride = stride.unwrap_or(width * 4); let data_len = (stride * height) as usize; let data = unsafe { slice::from_raw_parts(data_ptr as *const u8, data_len as usize) }; @@ -131,6 +133,7 @@ pub unsafe fn cvt_argb( ) -> FfiResult<(Box<[u8]>, proto::VideoBufferInfo)> { assert_eq!(buffer.r#type(), proto::VideoBufferType::Argb); let proto::VideoBufferInfo { stride, width, height, data_ptr, .. } = buffer; + let stride = stride.unwrap_or(width * 4); let data_len = (stride * height) as usize; let data = unsafe { slice::from_raw_parts(data_ptr as *const u8, data_len as usize) }; @@ -176,6 +179,7 @@ pub unsafe fn cvt_bgra( ) -> FfiResult<(Box<[u8]>, proto::VideoBufferInfo)> { assert_eq!(buffer.r#type(), proto::VideoBufferType::Bgra); let proto::VideoBufferInfo { stride, width, height, data_ptr, .. } = buffer; + let stride = stride.unwrap_or(width * 4); let data_len = (stride * height) as usize; let data = unsafe { slice::from_raw_parts(data_ptr as *const u8, data_len as usize) }; @@ -231,6 +235,7 @@ pub unsafe fn cvt_rgb24( ) -> FfiResult<(Box<[u8]>, proto::VideoBufferInfo)> { assert_eq!(buffer.r#type(), proto::VideoBufferType::Rgb24); let proto::VideoBufferInfo { stride, width, height, data_ptr, .. } = buffer; + let stride = stride.unwrap_or(width * 3); let data_len = (stride * height) as usize; let data = unsafe { slice::from_raw_parts(data_ptr as *const u8, data_len as usize) }; diff --git a/livekit-ffi/src/server/colorcvt/mod.rs b/livekit-ffi/src/server/colorcvt/mod.rs index 0bb05349b..29ac09ff0 100644 --- a/livekit-ffi/src/server/colorcvt/mod.rs +++ b/livekit-ffi/src/server/colorcvt/mod.rs @@ -327,7 +327,7 @@ pub fn i420_info( r#type: proto::VideoBufferType::I420.into(), components, data_ptr: data_ptr as u64, - stride: 0, + stride: None, } } @@ -378,7 +378,7 @@ pub fn i420a_info( r#type: proto::VideoBufferType::I420a.into(), components, data_ptr: data_ptr as u64, - stride: 0, + stride: None, } } @@ -419,7 +419,7 @@ pub fn i422_info( r#type: proto::VideoBufferType::I422.into(), components, data_ptr: data_ptr as u64, - stride: 0, + stride: None, } } @@ -460,7 +460,7 @@ pub fn i444_info( r#type: proto::VideoBufferType::I444.into(), components, data_ptr: data_ptr as u64, - stride: 0, + stride: None, } } @@ -503,7 +503,7 @@ pub fn i010_info( r#type: proto::VideoBufferType::I010.into(), components, data_ptr: data_ptr as u64, - stride: 0, + stride: None, } } @@ -538,7 +538,7 @@ pub fn nv12_info( r#type: proto::VideoBufferType::Nv12.into(), components, data_ptr: data_ptr as u64, - stride: 0, + stride: None, } } @@ -554,7 +554,7 @@ pub fn rgba_info( r#type: r#type.into(), components: Vec::default(), data_ptr: data_ptr as u64, - stride: width * 4, + stride: Some(width * 4), } } @@ -570,6 +570,6 @@ pub fn rgb_info( r#type: r#type.into(), components: Vec::default(), data_ptr: data_ptr as u64, - stride: width * 3, + stride: Some(width * 3), } } diff --git a/livekit-ffi/src/server/requests.rs b/livekit-ffi/src/server/requests.rs index 8a8b5be80..a421f67f6 100644 --- a/livekit-ffi/src/server/requests.rs +++ b/livekit-ffi/src/server/requests.rs @@ -919,6 +919,16 @@ fn on_set_data_channel_buffered_amount_low_threshold( )) } +fn on_set_track_subscription_permissions( + server: &'static FfiServer, + set_permissions: proto::SetTrackSubscriptionPermissionsRequest, +) -> FfiResult { + let ffi_participant = + server.retrieve_handle::(set_permissions.local_participant_handle)?.clone(); + + Ok(ffi_participant.room.set_track_subscription_permissions(server, set_permissions)) +} + #[allow(clippy::field_reassign_with_default)] // Avoid uggly format pub fn handle_request( server: &'static FfiServer, @@ -1097,6 +1107,11 @@ pub fn handle_request( on_set_data_channel_buffered_amount_low_threshold(server, request)?, ) } + proto::ffi_request::Message::SetTrackSubscriptionPermissions(request) => { + proto::ffi_response::Message::SetTrackSubscriptionPermissions( + on_set_track_subscription_permissions(server, request)?, + ) + } }); Ok(res) diff --git a/livekit-ffi/src/server/room.rs b/livekit-ffi/src/server/room.rs index e3eea0d96..7d9c797cf 100644 --- a/livekit-ffi/src/server/room.rs +++ b/livekit-ffi/src/server/room.rs @@ -785,6 +785,24 @@ impl RoomInner { ); proto::SetDataChannelBufferedAmountLowThresholdResponse {} } + + pub fn set_track_subscription_permissions( + self: &Arc, + server: &'static FfiServer, + request: proto::SetTrackSubscriptionPermissionsRequest, + ) -> proto::SetTrackSubscriptionPermissionsResponse { + let inner = self.clone(); + let permissions = request.permissions.into_iter().map(|p| p.into()).collect(); + let handle = server.async_runtime.spawn(async move { + let _ = inner + .room + .local_participant() + .set_track_subscription_permissions(request.all_participants_allowed, permissions) + .await; + }); + server.watch_panic(handle); + proto::SetTrackSubscriptionPermissionsResponse {} + } } // Task used to publish data without blocking the client thread diff --git a/livekit/src/proto.rs b/livekit/src/proto.rs index b630feb1a..2b3442255 100644 --- a/livekit/src/proto.rs +++ b/livekit/src/proto.rs @@ -171,3 +171,28 @@ impl From for ChatMessage { } } } + +impl From for TrackPermission { + fn from(perm: participant::ParticipantTrackPermission) -> Self { + TrackPermission { + participant_identity: perm.participant_identity.to_string(), + participant_sid: String::new(), + all_tracks: perm.allow_all, + track_sids: perm.allowed_track_sids.iter().map(|sid| sid.to_string()).collect(), + } + } +} + +impl From for participant::ParticipantTrackPermission { + fn from(perm: TrackPermission) -> Self { + participant::ParticipantTrackPermission { + participant_identity: perm.participant_identity.into(), + allow_all: perm.all_tracks, + allowed_track_sids: perm + .track_sids + .into_iter() + .map(|sid| sid.try_into().unwrap()) + .collect(), + } + } +} diff --git a/livekit/src/room/mod.rs b/livekit/src/room/mod.rs index 762bec1f2..2c25b653b 100644 --- a/livekit/src/room/mod.rs +++ b/livekit/src/room/mod.rs @@ -1078,6 +1078,11 @@ impl RoomSession { self.dispatcher.dispatch(&RoomEvent::Reconnected); let _ = tx.send(()); + + let local_participant = self.local_participant.clone(); + livekit_runtime::spawn(async move { + local_participant.update_track_subscription_permissions().await; + }); } fn handle_signal_resumed( @@ -1118,6 +1123,9 @@ impl RoomSession { // At this time we know that the RtcSession is successfully restarted let published_tracks = self.local_participant.track_publications(); + // we need to update the track subscription permissions after reconnection + let local_participant = self.local_participant.clone(); + // Spawining a new task because we need to wait for the RtcEngine to close the reconnection // lock. livekit_runtime::spawn({ @@ -1153,6 +1161,8 @@ impl RoomSession { // Wait for the tracks to be republished before sending the Connect event while set.join_next().await.is_some() {} + local_participant.update_track_subscription_permissions().await; + session.update_connection_state(ConnectionState::Connected); session.dispatcher.dispatch(&RoomEvent::Reconnected); } diff --git a/livekit/src/room/participant/local_participant.rs b/livekit/src/room/participant/local_participant.rs index ad0db760c..67605ba29 100644 --- a/livekit/src/room/participant/local_participant.rs +++ b/livekit/src/room/participant/local_participant.rs @@ -14,7 +14,7 @@ use std::{collections::HashMap, fmt::Debug, pin::Pin, sync::Arc, time::Duration}; -use super::{ConnectionQuality, ParticipantInner, ParticipantKind}; +use super::{ConnectionQuality, ParticipantInner, ParticipantKind, ParticipantTrackPermission}; use crate::{ e2ee::EncryptionType, options::{self, compute_video_encodings, video_layers_from_encodings, TrackPublishOptions}, @@ -71,6 +71,8 @@ struct LocalInfo { events: LocalEvents, encryption_type: EncryptionType, rpc_state: Mutex, + all_participants_allowed: Mutex, + track_permissions: Mutex>, } #[derive(Clone)] @@ -106,6 +108,8 @@ impl LocalParticipant { events: LocalEvents::default(), encryption_type, rpc_state: Mutex::new(RpcState::new()), + all_participants_allowed: Mutex::new(true), + track_permissions: Mutex::new(vec![]), }), } } @@ -490,6 +494,17 @@ impl LocalParticipant { Ok(self.inner.rtc_engine.session().data_channel_buffered_amount_low_threshold(kind)) } + pub async fn set_track_subscription_permissions( + &self, + all_participants_allowed: bool, + permissions: Vec, + ) -> RoomResult<()> { + *self.local.track_permissions.lock() = permissions; + *self.local.all_participants_allowed.lock() = all_participants_allowed; + self.update_track_subscription_permissions().await; + Ok(()) + } + pub async fn publish_transcription(&self, packet: Transcription) -> RoomResult<()> { let segments: Vec = packet .segments @@ -587,6 +602,27 @@ impl LocalParticipant { self.inner.rtc_engine.publish_data(data, DataPacketKind::Reliable).await.map_err(Into::into) } + pub(crate) async fn update_track_subscription_permissions(&self) { + let all_participants_allowed = *self.local.all_participants_allowed.lock(); + let track_permissions = self + .local + .track_permissions + .lock() + .iter() + .map(|p| proto::TrackPermission::from(p.clone())) + .collect(); + + self.inner + .rtc_engine + .send_request(proto::signal_request::Message::SubscriptionPermission( + proto::SubscriptionPermission { + all_participants: all_participants_allowed, + track_permissions, + }, + )) + .await; + } + pub fn get_track_publication(&self, sid: &TrackSid) -> Option { self.inner.track_publications.read().get(sid).map(|track| { if let TrackPublication::Local(local) = track { diff --git a/livekit/src/room/participant/mod.rs b/livekit/src/room/participant/mod.rs index 164c3eddf..30cf0544c 100644 --- a/livekit/src/room/participant/mod.rs +++ b/livekit/src/room/participant/mod.rs @@ -137,6 +137,13 @@ pub(super) struct ParticipantInner { events: Arc, } +#[derive(Clone)] +pub struct ParticipantTrackPermission { + pub participant_identity: ParticipantIdentity, + pub allow_all: bool, + pub allowed_track_sids: Vec, +} + pub(super) fn new_inner( rtc_engine: Arc, sid: ParticipantSid, From e1d709309844d5cc6135f6c044fc4ce604abceed Mon Sep 17 00:00:00 2001 From: David Zhao Date: Mon, 3 Feb 2025 12:21:44 -0800 Subject: [PATCH 137/274] fix package references in nanpa changeset (#564) --- .nanpa/track-permissions.kdl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.nanpa/track-permissions.kdl b/.nanpa/track-permissions.kdl index 004c675ae..798d113ab 100644 --- a/.nanpa/track-permissions.kdl +++ b/.nanpa/track-permissions.kdl @@ -1 +1,2 @@ -patch package="livekit-rtc" type="added" "Support for track subscription permissions" +patch package="livekit-ffi" type="added" "Support for track subscription permissions" +patch package="livekit" type="added" "Support for track subscription permissions" \ No newline at end of file From 660f70e1f18809df65f3faac5d6bb0d4bde730c4 Mon Sep 17 00:00:00 2001 From: "ilo-nanpa[bot]" <174912985+ilo-nanpa[bot]@users.noreply.github.com> Date: Mon, 3 Feb 2025 20:23:53 +0000 Subject: [PATCH 138/274] nanpa: bump --- .nanpa/send-data-nonce.kdl | 1 - .nanpa/track-permissions.kdl | 2 -- Cargo.toml | 6 +++--- livekit-api/.nanparc | 2 +- livekit-api/CHANGELOG.md | 7 +++++++ livekit-api/Cargo.toml | 2 +- livekit-ffi/.nanparc | 2 +- livekit-ffi/CHANGELOG.md | 6 ++++++ livekit-ffi/Cargo.toml | 2 +- livekit/.nanparc | 2 +- livekit/CHANGELOG.md | 6 ++++++ livekit/Cargo.toml | 2 +- 12 files changed, 28 insertions(+), 12 deletions(-) delete mode 100644 .nanpa/send-data-nonce.kdl delete mode 100644 .nanpa/track-permissions.kdl create mode 100644 livekit-api/CHANGELOG.md diff --git a/.nanpa/send-data-nonce.kdl b/.nanpa/send-data-nonce.kdl deleted file mode 100644 index b4d2a4f0e..000000000 --- a/.nanpa/send-data-nonce.kdl +++ /dev/null @@ -1 +0,0 @@ -patch package="livekit-api" type="added" "Update protocol and add SendDataRequest nonce" diff --git a/.nanpa/track-permissions.kdl b/.nanpa/track-permissions.kdl deleted file mode 100644 index 798d113ab..000000000 --- a/.nanpa/track-permissions.kdl +++ /dev/null @@ -1,2 +0,0 @@ -patch package="livekit-ffi" type="added" "Support for track subscription permissions" -patch package="livekit" type="added" "Support for track subscription permissions" \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 0d7531eb2..5cfa6d869 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,11 +18,11 @@ members = [ imgproc = { version = "0.3.12", path = "imgproc" } yuv-sys = { version = "0.3.7", path = "yuv-sys" } libwebrtc = { version = "0.3.9", path = "libwebrtc" } -livekit-api = { version = "0.4.1", path = "livekit-api" } -livekit-ffi = { version = "0.12.8", path = "livekit-ffi" } +livekit-api = { version = "0.4.2", path = "livekit-api" } +livekit-ffi = { version = "0.12.9", path = "livekit-ffi" } livekit-protocol = { version = "0.3.7", path = "livekit-protocol" } livekit-runtime = { version = "0.3.1", path = "livekit-runtime" } -livekit = { version = "0.7.3", path = "livekit" } +livekit = { version = "0.7.4", path = "livekit" } soxr-sys = { version = "0.1.0", path = "soxr-sys" } webrtc-sys-build = { version = "0.3.6", path = "webrtc-sys/build" } webrtc-sys = { version = "0.3.6", path = "webrtc-sys" } diff --git a/livekit-api/.nanparc b/livekit-api/.nanparc index 0dacc3fcf..d3108f6b1 100644 --- a/livekit-api/.nanparc +++ b/livekit-api/.nanparc @@ -1,2 +1,2 @@ -version 0.4.1 +version 0.4.2 language rust diff --git a/livekit-api/CHANGELOG.md b/livekit-api/CHANGELOG.md new file mode 100644 index 000000000..9d8d3994b --- /dev/null +++ b/livekit-api/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog + +## [0.4.2] - 2025-02-03 + +### Added + +- Update protocol and add SendDataRequest nonce diff --git a/livekit-api/Cargo.toml b/livekit-api/Cargo.toml index ec94be6d3..fd9a163e1 100644 --- a/livekit-api/Cargo.toml +++ b/livekit-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-api" -version = "0.4.1" +version = "0.4.2" license = "Apache-2.0" description = "Rust Server SDK for LiveKit" edition = "2021" diff --git a/livekit-ffi/.nanparc b/livekit-ffi/.nanparc index e40b0457b..d8353fee4 100644 --- a/livekit-ffi/.nanparc +++ b/livekit-ffi/.nanparc @@ -1,2 +1,2 @@ -version 0.12.8 +version 0.12.9 language rust diff --git a/livekit-ffi/CHANGELOG.md b/livekit-ffi/CHANGELOG.md index d95ff2186..aaa312783 100644 --- a/livekit-ffi/CHANGELOG.md +++ b/livekit-ffi/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [0.12.9] - 2025-02-03 + +### Added + +- Support for track subscription permissions + ## [0.12.8] - 2025-01-23 ### Changed diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index b50d56730..6d5c8992e 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.12.8" +version = "0.12.9" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" diff --git a/livekit/.nanparc b/livekit/.nanparc index f705105b3..5a180ea43 100644 --- a/livekit/.nanparc +++ b/livekit/.nanparc @@ -1,2 +1,2 @@ -version 0.7.3 +version 0.7.4 language rust diff --git a/livekit/CHANGELOG.md b/livekit/CHANGELOG.md index 52847231e..9ddea9a10 100644 --- a/livekit/CHANGELOG.md +++ b/livekit/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [0.7.4] - 2025-02-03 + +### Added + +- Support for track subscription permissions + ## [0.7.3] - 2025-01-17 ### Added diff --git a/livekit/Cargo.toml b/livekit/Cargo.toml index 2cdc746ed..80311654c 100644 --- a/livekit/Cargo.toml +++ b/livekit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit" -version = "0.7.3" +version = "0.7.4" edition = "2021" license = "Apache-2.0" description = "Rust Client SDK for LiveKit" From 7da4bc758def69f77391161cae6da7d58ba31c39 Mon Sep 17 00:00:00 2001 From: Ben Cherry Date: Tue, 4 Feb 2025 13:57:35 -0800 Subject: [PATCH 139/274] Fix RPC invocation race condition (#565) * Fix race condition in rpc invocation * nanpa * fix * nanpa * cgo * sm --- .nanpa/rpc-race.kdl | 1 + livekit-ffi/src/server/participant.rs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 .nanpa/rpc-race.kdl diff --git a/.nanpa/rpc-race.kdl b/.nanpa/rpc-race.kdl new file mode 100644 index 000000000..6a9e3fe50 --- /dev/null +++ b/.nanpa/rpc-race.kdl @@ -0,0 +1 @@ +patch package="livekit-ffi" type="fixed" "Fix RPC invocation race bug" \ No newline at end of file diff --git a/livekit-ffi/src/server/participant.rs b/livekit-ffi/src/server/participant.rs index afa4bade2..e6d789876 100644 --- a/livekit-ffi/src/server/participant.rs +++ b/livekit-ffi/src/server/participant.rs @@ -140,6 +140,8 @@ async fn forward_rpc_method_invocation( let (tx, rx) = oneshot::channel(); let invocation_id = server.next_id(); + room.store_rpc_method_invocation_waiter(invocation_id, tx); + let _ = server.send_event(proto::ffi_event::Message::RpcMethodInvocation( proto::RpcMethodInvocationEvent { local_participant_handle: local_participant_handle as u64, @@ -152,8 +154,6 @@ async fn forward_rpc_method_invocation( }, )); - room.store_rpc_method_invocation_waiter(invocation_id, tx); - rx.await.unwrap_or_else(|_| { Err(RpcError { code: RpcErrorCode::ApplicationError as u32, From fabd8ed8e126eb35c9f0817470036137d83afd7e Mon Sep 17 00:00:00 2001 From: "ilo-nanpa[bot]" <174912985+ilo-nanpa[bot]@users.noreply.github.com> Date: Tue, 4 Feb 2025 22:14:06 +0000 Subject: [PATCH 140/274] nanpa: bump --- .nanpa/rpc-race.kdl | 1 - Cargo.toml | 2 +- livekit-ffi/.nanparc | 2 +- livekit-ffi/CHANGELOG.md | 6 ++++++ livekit-ffi/Cargo.toml | 2 +- 5 files changed, 9 insertions(+), 4 deletions(-) delete mode 100644 .nanpa/rpc-race.kdl diff --git a/.nanpa/rpc-race.kdl b/.nanpa/rpc-race.kdl deleted file mode 100644 index 6a9e3fe50..000000000 --- a/.nanpa/rpc-race.kdl +++ /dev/null @@ -1 +0,0 @@ -patch package="livekit-ffi" type="fixed" "Fix RPC invocation race bug" \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 5cfa6d869..585c02107 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ imgproc = { version = "0.3.12", path = "imgproc" } yuv-sys = { version = "0.3.7", path = "yuv-sys" } libwebrtc = { version = "0.3.9", path = "libwebrtc" } livekit-api = { version = "0.4.2", path = "livekit-api" } -livekit-ffi = { version = "0.12.9", path = "livekit-ffi" } +livekit-ffi = { version = "0.12.10", path = "livekit-ffi" } livekit-protocol = { version = "0.3.7", path = "livekit-protocol" } livekit-runtime = { version = "0.3.1", path = "livekit-runtime" } livekit = { version = "0.7.4", path = "livekit" } diff --git a/livekit-ffi/.nanparc b/livekit-ffi/.nanparc index d8353fee4..ec339f25e 100644 --- a/livekit-ffi/.nanparc +++ b/livekit-ffi/.nanparc @@ -1,2 +1,2 @@ -version 0.12.9 +version 0.12.10 language rust diff --git a/livekit-ffi/CHANGELOG.md b/livekit-ffi/CHANGELOG.md index aaa312783..c3ebe7a82 100644 --- a/livekit-ffi/CHANGELOG.md +++ b/livekit-ffi/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [0.12.10] - 2025-02-04 + +### Fixed + +- Fix RPC invocation race bug + ## [0.12.9] - 2025-02-03 ### Added diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 6d5c8992e..316d25616 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.12.9" +version = "0.12.10" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" From 4865c6cdc3aa23c02568628f710e0ef182326180 Mon Sep 17 00:00:00 2001 From: Daisuke Murase Date: Wed, 5 Feb 2025 10:50:31 -0800 Subject: [PATCH 141/274] Fix build issues (#567) * set to workspace * fix livekit-runtime --- .nanpa/fix-runtime-metadata.kdl | 1 + imgproc/Cargo.toml | 2 +- livekit-runtime/Cargo.toml | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 .nanpa/fix-runtime-metadata.kdl diff --git a/.nanpa/fix-runtime-metadata.kdl b/.nanpa/fix-runtime-metadata.kdl new file mode 100644 index 000000000..746492de1 --- /dev/null +++ b/.nanpa/fix-runtime-metadata.kdl @@ -0,0 +1 @@ +minor type="fixed" package="livekit-runtime" "Fix package metadata issue" diff --git a/imgproc/Cargo.toml b/imgproc/Cargo.toml index 8356ec8fe..844147d9e 100644 --- a/imgproc/Cargo.toml +++ b/imgproc/Cargo.toml @@ -7,4 +7,4 @@ license = "MIT OR Apache-2.0" description = "image processing library" [dependencies] -yuv-sys = { path = "../yuv-sys" } +yuv-sys = { workspace = true } diff --git a/livekit-runtime/Cargo.toml b/livekit-runtime/Cargo.toml index 2ec822ae4..dc25788dd 100644 --- a/livekit-runtime/Cargo.toml +++ b/livekit-runtime/Cargo.toml @@ -21,6 +21,8 @@ dispatcher = ["dep:futures", "dep:async-io", "dep:async-std", "dep:async-task"] tokio = { version = "1", default-features = false, optional = true, features = [ "rt", "rt-multi-thread", + "net", + "time", ] } tokio-stream = { version = "0.1.14", optional = true } From 6b90ca7f810b3191cf696fa2a1c3bc1ec6c9be4b Mon Sep 17 00:00:00 2001 From: "ilo-nanpa[bot]" <174912985+ilo-nanpa[bot]@users.noreply.github.com> Date: Wed, 5 Feb 2025 18:52:03 +0000 Subject: [PATCH 142/274] nanpa: bump --- .nanpa/fix-runtime-metadata.kdl | 1 - Cargo.toml | 2 +- livekit-api/Cargo.toml | 2 +- livekit-runtime/.nanparc | 2 +- livekit-runtime/CHANGELOG.md | 7 +++++++ livekit-runtime/Cargo.toml | 2 +- 6 files changed, 11 insertions(+), 5 deletions(-) delete mode 100644 .nanpa/fix-runtime-metadata.kdl create mode 100644 livekit-runtime/CHANGELOG.md diff --git a/.nanpa/fix-runtime-metadata.kdl b/.nanpa/fix-runtime-metadata.kdl deleted file mode 100644 index 746492de1..000000000 --- a/.nanpa/fix-runtime-metadata.kdl +++ /dev/null @@ -1 +0,0 @@ -minor type="fixed" package="livekit-runtime" "Fix package metadata issue" diff --git a/Cargo.toml b/Cargo.toml index 585c02107..9386580d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ libwebrtc = { version = "0.3.9", path = "libwebrtc" } livekit-api = { version = "0.4.2", path = "livekit-api" } livekit-ffi = { version = "0.12.10", path = "livekit-ffi" } livekit-protocol = { version = "0.3.7", path = "livekit-protocol" } -livekit-runtime = { version = "0.3.1", path = "livekit-runtime" } +livekit-runtime = { version = "0.4.0", path = "livekit-runtime" } livekit = { version = "0.7.4", path = "livekit" } soxr-sys = { version = "0.1.0", path = "soxr-sys" } webrtc-sys-build = { version = "0.3.6", path = "webrtc-sys/build" } diff --git a/livekit-api/Cargo.toml b/livekit-api/Cargo.toml index fd9a163e1..b803e61d0 100644 --- a/livekit-api/Cargo.toml +++ b/livekit-api/Cargo.toml @@ -83,7 +83,7 @@ base64 = { version = "0.21", optional = true } jsonwebtoken = { version = "9", default-features = false, optional = true } # signal_client -livekit-runtime = { path = "../livekit-runtime", version = "0.3.0", optional = true} +livekit-runtime = { path = "../livekit-runtime", version = "0.4.0", optional = true} tokio-tungstenite = { version = "0.20", optional = true } async-tungstenite = { version = "0.25.0", features = [ "async-std-runtime", "async-native-tls"], optional = true } tokio = { version = "1", default-features = false, features = ["sync", "macros", "signal"], optional = true } diff --git a/livekit-runtime/.nanparc b/livekit-runtime/.nanparc index 4efcdcec5..f3e92f54e 100644 --- a/livekit-runtime/.nanparc +++ b/livekit-runtime/.nanparc @@ -1,2 +1,2 @@ -version 0.3.1 +version 0.4.0 language rust diff --git a/livekit-runtime/CHANGELOG.md b/livekit-runtime/CHANGELOG.md new file mode 100644 index 000000000..9d0b76fe3 --- /dev/null +++ b/livekit-runtime/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog + +## [0.4.0] - 2025-02-05 + +### Fixed + +- Fix package metadata issue diff --git a/livekit-runtime/Cargo.toml b/livekit-runtime/Cargo.toml index dc25788dd..83a72be03 100644 --- a/livekit-runtime/Cargo.toml +++ b/livekit-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-runtime" -version = "0.3.1" +version = "0.4.0" license = "Apache-2.0" description = "Async runtime compatibility layer for LiveKit" edition = "2021" From 5b264ccf3058d02c4655ea5c2a6596507b2a96fa Mon Sep 17 00:00:00 2001 From: Daisuke Murase Date: Wed, 5 Feb 2025 13:07:41 -0800 Subject: [PATCH 143/274] webrtc-sys needs to be updated (#569) --- .nanpa/bump-webrtc-sys.kdl | 2 ++ Cargo.lock | 8 ++++---- libwebrtc/Cargo.toml | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) create mode 100644 .nanpa/bump-webrtc-sys.kdl diff --git a/.nanpa/bump-webrtc-sys.kdl b/.nanpa/bump-webrtc-sys.kdl new file mode 100644 index 000000000..862673dde --- /dev/null +++ b/.nanpa/bump-webrtc-sys.kdl @@ -0,0 +1,2 @@ +patch type="added" package="webrtc-sys" "Expose DataChannel.bufferedAmount property" +patch type="fixed" package="libwebrtc" "Fix build issue" diff --git a/Cargo.lock b/Cargo.lock index 37b881351..acc5b45a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1605,7 +1605,7 @@ checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "livekit" -version = "0.7.3" +version = "0.7.4" dependencies = [ "chrono", "futures-util", @@ -1626,7 +1626,7 @@ dependencies = [ [[package]] name = "livekit-api" -version = "0.4.1" +version = "0.4.2" dependencies = [ "async-tungstenite", "base64", @@ -1654,7 +1654,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.12.8" +version = "0.12.10" dependencies = [ "console-subscriber", "dashmap", @@ -1695,7 +1695,7 @@ dependencies = [ [[package]] name = "livekit-runtime" -version = "0.3.1" +version = "0.4.0" dependencies = [ "async-io 2.3.1", "async-std", diff --git a/libwebrtc/Cargo.toml b/libwebrtc/Cargo.toml index c139122ce..4984deffc 100644 --- a/libwebrtc/Cargo.toml +++ b/libwebrtc/Cargo.toml @@ -18,8 +18,8 @@ thiserror = "1.0" jni = "0.21" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -webrtc-sys = { path = "../webrtc-sys", version = "0.3.5" } -livekit-runtime = { path = "../livekit-runtime", version = "0.3.0" } +webrtc-sys = { workspace = true } +livekit-runtime = { workspace = true } lazy_static = "1.4" parking_lot = { version = "0.12" } tokio = { version = "1", default-features = false, features = ["sync", "macros"] } From afecf6f25e77fdfa8c7eb5047990f5983a766f87 Mon Sep 17 00:00:00 2001 From: "ilo-nanpa[bot]" <174912985+ilo-nanpa[bot]@users.noreply.github.com> Date: Wed, 5 Feb 2025 21:08:45 +0000 Subject: [PATCH 144/274] nanpa: bump --- .nanpa/bump-webrtc-sys.kdl | 2 -- Cargo.toml | 4 ++-- libwebrtc/.nanparc | 2 +- libwebrtc/CHANGELOG.md | 6 ++++++ libwebrtc/Cargo.toml | 2 +- webrtc-sys/.nanparc | 2 +- webrtc-sys/CHANGELOG.md | 6 ++++++ webrtc-sys/Cargo.toml | 2 +- 8 files changed, 18 insertions(+), 8 deletions(-) delete mode 100644 .nanpa/bump-webrtc-sys.kdl diff --git a/.nanpa/bump-webrtc-sys.kdl b/.nanpa/bump-webrtc-sys.kdl deleted file mode 100644 index 862673dde..000000000 --- a/.nanpa/bump-webrtc-sys.kdl +++ /dev/null @@ -1,2 +0,0 @@ -patch type="added" package="webrtc-sys" "Expose DataChannel.bufferedAmount property" -patch type="fixed" package="libwebrtc" "Fix build issue" diff --git a/Cargo.toml b/Cargo.toml index 9386580d6..6c8489369 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ members = [ [workspace.dependencies] imgproc = { version = "0.3.12", path = "imgproc" } yuv-sys = { version = "0.3.7", path = "yuv-sys" } -libwebrtc = { version = "0.3.9", path = "libwebrtc" } +libwebrtc = { version = "0.3.10", path = "libwebrtc" } livekit-api = { version = "0.4.2", path = "livekit-api" } livekit-ffi = { version = "0.12.10", path = "livekit-ffi" } livekit-protocol = { version = "0.3.7", path = "livekit-protocol" } @@ -25,4 +25,4 @@ livekit-runtime = { version = "0.4.0", path = "livekit-runtime" } livekit = { version = "0.7.4", path = "livekit" } soxr-sys = { version = "0.1.0", path = "soxr-sys" } webrtc-sys-build = { version = "0.3.6", path = "webrtc-sys/build" } -webrtc-sys = { version = "0.3.6", path = "webrtc-sys" } +webrtc-sys = { version = "0.3.7", path = "webrtc-sys" } diff --git a/libwebrtc/.nanparc b/libwebrtc/.nanparc index 96df60bef..6f8134801 100644 --- a/libwebrtc/.nanparc +++ b/libwebrtc/.nanparc @@ -1,2 +1,2 @@ -version 0.3.9 +version 0.3.10 language rust diff --git a/libwebrtc/CHANGELOG.md b/libwebrtc/CHANGELOG.md index 1928bdee8..78433a7e8 100644 --- a/libwebrtc/CHANGELOG.md +++ b/libwebrtc/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [0.3.10] - 2025-02-05 + +### Fixed + +- Fix build issue + ## [0.3.9] - 2025-01-17 ### Added diff --git a/libwebrtc/Cargo.toml b/libwebrtc/Cargo.toml index 4984deffc..fa3f44412 100644 --- a/libwebrtc/Cargo.toml +++ b/libwebrtc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libwebrtc" -version = "0.3.9" +version = "0.3.10" edition = "2021" homepage = "https://livekit.io" license = "Apache-2.0" diff --git a/webrtc-sys/.nanparc b/webrtc-sys/.nanparc index 10b95b2c7..5bf971c37 100644 --- a/webrtc-sys/.nanparc +++ b/webrtc-sys/.nanparc @@ -1,2 +1,2 @@ -version 0.3.6 +version 0.3.7 language rust diff --git a/webrtc-sys/CHANGELOG.md b/webrtc-sys/CHANGELOG.md index 215126331..6e1609449 100644 --- a/webrtc-sys/CHANGELOG.md +++ b/webrtc-sys/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [0.3.7] - 2025-02-05 + +### Added + +- Expose DataChannel.bufferedAmount property + ## [0.3.6] - 2024-12-14 ### Added diff --git a/webrtc-sys/Cargo.toml b/webrtc-sys/Cargo.toml index dfc6c7c6e..7882a0616 100644 --- a/webrtc-sys/Cargo.toml +++ b/webrtc-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "webrtc-sys" -version = "0.3.6" +version = "0.3.7" edition = "2021" homepage = "https://livekit.io" license = "Apache-2.0" From 81078e86e5e7b808bf504b802d921610f3e4e6ed Mon Sep 17 00:00:00 2001 From: Daisuke Murase Date: Wed, 5 Feb 2025 15:16:49 -0800 Subject: [PATCH 145/274] bump livekit-protocol (#571) --- .nanpa/bump-protocol.kdl | 1 + Cargo.lock | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 .nanpa/bump-protocol.kdl diff --git a/.nanpa/bump-protocol.kdl b/.nanpa/bump-protocol.kdl new file mode 100644 index 000000000..a16bc109f --- /dev/null +++ b/.nanpa/bump-protocol.kdl @@ -0,0 +1 @@ +patch type="fixed" package="livekit-protocol" "Fixed a dependency issue" \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index acc5b45a0..b764e2232 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1549,7 +1549,7 @@ dependencies = [ [[package]] name = "libwebrtc" -version = "0.3.9" +version = "0.3.10" dependencies = [ "cxx", "env_logger", @@ -3346,7 +3346,7 @@ checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" [[package]] name = "webrtc-sys" -version = "0.3.6" +version = "0.3.7" dependencies = [ "cc", "cxx", From 579ad436f892d32d954a13941e1b2a85edcd4d1b Mon Sep 17 00:00:00 2001 From: "ilo-nanpa[bot]" <174912985+ilo-nanpa[bot]@users.noreply.github.com> Date: Wed, 5 Feb 2025 23:19:08 +0000 Subject: [PATCH 146/274] nanpa: bump --- .nanpa/bump-protocol.kdl | 1 - Cargo.toml | 2 +- livekit-api/Cargo.toml | 2 +- livekit-protocol/.nanparc | 2 +- livekit-protocol/CHANGELOG.md | 6 ++++++ livekit-protocol/Cargo.toml | 2 +- 6 files changed, 10 insertions(+), 5 deletions(-) delete mode 100644 .nanpa/bump-protocol.kdl diff --git a/.nanpa/bump-protocol.kdl b/.nanpa/bump-protocol.kdl deleted file mode 100644 index a16bc109f..000000000 --- a/.nanpa/bump-protocol.kdl +++ /dev/null @@ -1 +0,0 @@ -patch type="fixed" package="livekit-protocol" "Fixed a dependency issue" \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 6c8489369..41a91d4d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ yuv-sys = { version = "0.3.7", path = "yuv-sys" } libwebrtc = { version = "0.3.10", path = "libwebrtc" } livekit-api = { version = "0.4.2", path = "livekit-api" } livekit-ffi = { version = "0.12.10", path = "livekit-ffi" } -livekit-protocol = { version = "0.3.7", path = "livekit-protocol" } +livekit-protocol = { version = "0.3.8", path = "livekit-protocol" } livekit-runtime = { version = "0.4.0", path = "livekit-runtime" } livekit = { version = "0.7.4", path = "livekit" } soxr-sys = { version = "0.1.0", path = "soxr-sys" } diff --git a/livekit-api/Cargo.toml b/livekit-api/Cargo.toml index b803e61d0..0545801c1 100644 --- a/livekit-api/Cargo.toml +++ b/livekit-api/Cargo.toml @@ -65,7 +65,7 @@ rustls-tls-webpki-roots = [ __rustls-tls = ["tokio-tungstenite?/__rustls-tls", "reqwest?/__rustls"] [dependencies] -livekit-protocol = { path = "../livekit-protocol", version = "0.3.7" } +livekit-protocol = { path = "../livekit-protocol", version = "0.3.8" } thiserror = "1.0" serde = { version = "1.0", features = ["derive"] } sha2 = "0.10" diff --git a/livekit-protocol/.nanparc b/livekit-protocol/.nanparc index 5bf971c37..9ad0822e5 100644 --- a/livekit-protocol/.nanparc +++ b/livekit-protocol/.nanparc @@ -1,2 +1,2 @@ -version 0.3.7 +version 0.3.8 language rust diff --git a/livekit-protocol/CHANGELOG.md b/livekit-protocol/CHANGELOG.md index 29c8f009c..223d1bc42 100644 --- a/livekit-protocol/CHANGELOG.md +++ b/livekit-protocol/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [0.3.8] - 2025-02-05 + +### Fixed + +- Fixed a dependency issue + ## [0.3.7] - 2025-01-17 ### Changed diff --git a/livekit-protocol/Cargo.toml b/livekit-protocol/Cargo.toml index bfb935b10..27e9d1351 100644 --- a/livekit-protocol/Cargo.toml +++ b/livekit-protocol/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-protocol" -version = "0.3.7" +version = "0.3.8" edition = "2021" license = "Apache-2.0" description = "Livekit protocol and utilities for the Rust SDK" From 3a0d67bf2e2cb48096feb0407a8f7bfff97b44f7 Mon Sep 17 00:00:00 2001 From: Daisuke Murase Date: Thu, 6 Feb 2025 11:13:47 -0800 Subject: [PATCH 147/274] add a changeset for livekit (#573) --- .nanpa/bump-livekit.kdl | 1 + 1 file changed, 1 insertion(+) create mode 100644 .nanpa/bump-livekit.kdl diff --git a/.nanpa/bump-livekit.kdl b/.nanpa/bump-livekit.kdl new file mode 100644 index 000000000..e279cd4e6 --- /dev/null +++ b/.nanpa/bump-livekit.kdl @@ -0,0 +1 @@ +patch type="fixed" package="livekit" "Fix a dependency issue with an older version of the libwebrtc crate" From 8b1bc052aa8bc2bfaa29d60f6c7fc011dea87d3b Mon Sep 17 00:00:00 2001 From: "ilo-nanpa[bot]" <174912985+ilo-nanpa[bot]@users.noreply.github.com> Date: Thu, 6 Feb 2025 19:14:44 +0000 Subject: [PATCH 148/274] nanpa: bump --- .nanpa/bump-livekit.kdl | 1 - Cargo.toml | 2 +- livekit/.nanparc | 2 +- livekit/CHANGELOG.md | 6 ++++++ livekit/Cargo.toml | 2 +- 5 files changed, 9 insertions(+), 4 deletions(-) delete mode 100644 .nanpa/bump-livekit.kdl diff --git a/.nanpa/bump-livekit.kdl b/.nanpa/bump-livekit.kdl deleted file mode 100644 index e279cd4e6..000000000 --- a/.nanpa/bump-livekit.kdl +++ /dev/null @@ -1 +0,0 @@ -patch type="fixed" package="livekit" "Fix a dependency issue with an older version of the libwebrtc crate" diff --git a/Cargo.toml b/Cargo.toml index 41a91d4d4..a25445ffd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ livekit-api = { version = "0.4.2", path = "livekit-api" } livekit-ffi = { version = "0.12.10", path = "livekit-ffi" } livekit-protocol = { version = "0.3.8", path = "livekit-protocol" } livekit-runtime = { version = "0.4.0", path = "livekit-runtime" } -livekit = { version = "0.7.4", path = "livekit" } +livekit = { version = "0.7.5", path = "livekit" } soxr-sys = { version = "0.1.0", path = "soxr-sys" } webrtc-sys-build = { version = "0.3.6", path = "webrtc-sys/build" } webrtc-sys = { version = "0.3.7", path = "webrtc-sys" } diff --git a/livekit/.nanparc b/livekit/.nanparc index 5a180ea43..ba2d28ba5 100644 --- a/livekit/.nanparc +++ b/livekit/.nanparc @@ -1,2 +1,2 @@ -version 0.7.4 +version 0.7.5 language rust diff --git a/livekit/CHANGELOG.md b/livekit/CHANGELOG.md index 9ddea9a10..468cfc22f 100644 --- a/livekit/CHANGELOG.md +++ b/livekit/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [0.7.5] - 2025-02-06 + +### Fixed + +- Fix a dependency issue with an older version of the libwebrtc crate + ## [0.7.4] - 2025-02-03 ### Added diff --git a/livekit/Cargo.toml b/livekit/Cargo.toml index 80311654c..46ea7e486 100644 --- a/livekit/Cargo.toml +++ b/livekit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit" -version = "0.7.4" +version = "0.7.5" edition = "2021" license = "Apache-2.0" description = "Rust Client SDK for LiveKit" From 3507dc0a07fd2179012040020b92b4b8272b9aa9 Mon Sep 17 00:00:00 2001 From: Mathew Kamkar <578302+matkam@users.noreply.github.com> Date: Wed, 19 Feb 2025 11:24:57 -0800 Subject: [PATCH 149/274] update builder runner os (#578) --- .github/workflows/builds.yml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index 4940634da..99acf50c4 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -35,30 +35,30 @@ jobs: fail-fast: false matrix: include: - - os: windows-2019 + - os: windows-latest target: x86_64-pc-windows-msvc - - os: windows-2019 + - os: windows-latest target: aarch64-pc-windows-msvc #extraargs: --exclude livekit-api --exclude livekit-ffi # waiting for v0.17 of ring - - os: macos-13 + - os: macos-latest target: x86_64-apple-darwin - - os: macos-13 + - os: macos-latest target: aarch64-apple-darwin - - os: macos-13 + - os: macos-latest target: aarch64-apple-ios - - os: macos-13 + - os: macos-latest target: aarch64-apple-ios-sim - - os: ubuntu-20.04 + - os: ubuntu-latest target: x86_64-unknown-linux-gnu - - os: buildjet-4vcpu-ubuntu-2204-arm + - os: ubuntu-24.04-arm target: aarch64-unknown-linux-gnu - - os: ubuntu-20.04 + - os: ubuntu-latest target: aarch64-linux-android ndk_arch: aarch64-unknown-linux-musl - - os: ubuntu-20.04 + - os: ubuntu-latest target: armv7-linux-androideabi ndk_arch: arm-unknown-linux-musleabihf - - os: ubuntu-20.04 + - os: ubuntu-latest target: x86_64-linux-android ndk_arch: x86_64-unknown-linux-musl @@ -70,7 +70,7 @@ jobs: submodules: true - name: Install linux dependencies - if: ${{ matrix.os == 'ubuntu-20.04' || matrix.os == 'buildjet-4vcpu-ubuntu-2204-arm'}} + if: ${{ matrix.os == 'ubuntu-latest' || matrix.os == 'ubuntu-24.04-arm'}} run: | sudo apt update -y sudo apt install -y libssl-dev libx11-dev libgl1-mesa-dev libxext-dev From e41ccedd6f2a04c628cd61a6d981f3263b72ae3d Mon Sep 17 00:00:00 2001 From: Mathew Kamkar <578302+matkam@users.noreply.github.com> Date: Wed, 19 Feb 2025 16:29:16 -0800 Subject: [PATCH 150/274] update more gh runner images (#579) --- .github/workflows/ffi-builds.yml | 28 ++++++++++++------------- .github/workflows/tests.yml | 8 ++++---- .github/workflows/webrtc-builds.yml | 32 ++++++++++++++--------------- 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/.github/workflows/ffi-builds.yml b/.github/workflows/ffi-builds.yml index 3b31ec8c4..491a721cc 100644 --- a/.github/workflows/ffi-builds.yml +++ b/.github/workflows/ffi-builds.yml @@ -29,56 +29,56 @@ jobs: fail-fast: false matrix: include: - - os: windows-2019 + - os: windows-latest platform: windows dylib: livekit_ffi.dll target: x86_64-pc-windows-msvc name: ffi-windows-x86_64 - - os: windows-2019 + - os: windows-latest platform: windows dylib: livekit_ffi.dll buildargs: --no-default-features --features "native-tls" # ring 0.16 is incompatible with win aarch64 target: aarch64-pc-windows-msvc name: ffi-windows-arm64 - - os: macos-13 + - os: macos-latest platform: macos dylib: liblivekit_ffi.dylib target: x86_64-apple-darwin macosx_deployment_target: "10.15" name: ffi-macos-x86_64 - - os: macos-13 + - os: macos-latest platform: macos dylib: liblivekit_ffi.dylib target: aarch64-apple-darwin macosx_deployment_target: "11.0" # aarch64 requires 11 name: ffi-macos-arm64 - - os: macos-13 + - os: macos-latest platform: ios dylib: liblivekit_ffi.a target: aarch64-apple-ios iphoneos_deployment_target: "13.0" name: ffi-ios-arm64 buildargs: --no-default-features --features "rustls-tls-webpki-roots" - - os: macos-13 + - os: macos-latest platform: ios dylib: liblivekit_ffi.a target: aarch64-apple-ios-sim iphoneos_deployment_target: "13.0" name: ffi-ios-sim-arm64 buildargs: --no-default-features --features "rustls-tls-webpki-roots" - - os: ubuntu-20.04 + - os: ubuntu-latest platform: linux build_image: quay.io/pypa/manylinux_2_28_x86_64 dylib: liblivekit_ffi.so target: x86_64-unknown-linux-gnu name: ffi-linux-x86_64 - - os: buildjet-4vcpu-ubuntu-2204-arm + - os: ubuntu-24.04-arm platform: linux build_image: quay.io/pypa/manylinux_2_28_aarch64 dylib: liblivekit_ffi.so target: aarch64-unknown-linux-gnu name: ffi-linux-arm64 - - os: ubuntu-20.04 + - os: ubuntu-latest platform: android dylib: liblivekit_ffi.so jar: libwebrtc.jar @@ -86,7 +86,7 @@ jobs: ndk_arch: aarch64-unknown-linux-musl name: ffi-android-arm64 buildargs: --no-default-features --features "rustls-tls-webpki-roots" - - os: ubuntu-20.04 + - os: ubuntu-latest platform: android dylib: liblivekit_ffi.so jar: libwebrtc.jar @@ -94,7 +94,7 @@ jobs: ndk_arch: arm-unknown-linux-musleabihf name: ffi-android-armv7 buildargs: --no-default-features --features "rustls-tls-webpki-roots" - - os: ubuntu-20.04 + - os: ubuntu-latest platform: android dylib: liblivekit_ffi.so jar: libwebrtc.jar @@ -175,21 +175,21 @@ jobs: # zip the files - name: Zip artifact (Unix) - if: ${{ matrix.os != 'windows-2019' && matrix.platform != 'android'}} + if: ${{ matrix.os != 'windows-latest' && matrix.platform != 'android'}} run: | cp livekit-ffi/include/livekit_ffi.h target/${{ matrix.target }}/release/ cd target/${{ matrix.target }}/release/ zip ${{ github.workspace }}/${{ matrix.name }}.zip ${{ matrix.dylib }} livekit_ffi.h LICENSE.md - name: Zip artifact (Unix for Android) - if: ${{ matrix.os != 'windows-2019' && matrix.platform == 'android'}} + if: ${{ matrix.os != 'windows-latest' && matrix.platform == 'android'}} run: | cp livekit-ffi/include/livekit_ffi.h target/${{ matrix.target }}/release/ cd target/${{ matrix.target }}/release/ zip ${{ github.workspace }}/${{ matrix.name }}.zip ${{ matrix.dylib }} ${{ matrix.jar }} livekit_ffi.h LICENSE.md - name: Zip artifact (Windows) - if: ${{ matrix.os == 'windows-2019' }} + if: ${{ matrix.os == 'windows-latest' }} run: | cp livekit-ffi/include/livekit_ffi.h target/${{ matrix.target }}/release/ cd target/${{ matrix.target }}/release/ diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2b159c66f..d0ded4622 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -31,11 +31,11 @@ jobs: matrix: include: # Platform supports is limited for tests (no aarch64) - - os: windows-2019 + - os: windows-latest target: x86_64-pc-windows-msvc - - os: macos-13 + - os: macos-latest target: x86_64-apple-darwin - - os: ubuntu-20.04 + - os: ubuntu-latest target: x86_64-unknown-linux-gnu name: Test (${{ matrix.target }}) @@ -47,7 +47,7 @@ jobs: rustup target add ${{ matrix.target }} --toolchain nightly - name: Install linux dependencies - if: ${{ matrix.os == 'ubuntu-20.04' }} + if: ${{ matrix.os == 'ubuntu-latest' }} run: | sudo apt update -y sudo apt install -y libssl-dev libx11-dev libgl1-mesa-dev libxext-dev diff --git a/.github/workflows/webrtc-builds.yml b/.github/workflows/webrtc-builds.yml index 60ebcd68a..13f4c307f 100644 --- a/.github/workflows/webrtc-builds.yml +++ b/.github/workflows/webrtc-builds.yml @@ -26,59 +26,59 @@ jobs: matrix: target: - name: win - os: windows-2019 + os: windows-latest cmd: .\build_windows.cmd arch: x64 - name: win - os: windows-2019 + os: windows-latest cmd: .\build_windows.cmd arch: arm64 - name: mac - os: macos-13 + os: macos-latest cmd: ./build_macos.sh arch: x64 - name: mac - os: macos-13 + os: macos-latest cmd: ./build_macos.sh arch: arm64 - name: linux - os: buildjet-8vcpu-ubuntu-2004 + os: ubuntu-latest cmd: ./build_linux.sh arch: x64 - name: linux - os: buildjet-8vcpu-ubuntu-2004 + os: ubuntu-24.04-arm cmd: ./build_linux.sh arch: arm64 - name: android - os: buildjet-8vcpu-ubuntu-2004 + os: ubuntu-24.04-arm cmd: ./build_android.sh arch: arm64 - name: android - os: buildjet-8vcpu-ubuntu-2004 + os: ubuntu-24.04-arm cmd: ./build_android.sh arch: arm - name: android - os: buildjet-8vcpu-ubuntu-2004 + os: ubuntu-latest cmd: ./build_android.sh arch: x64 - name: ios out: ios-device-arm64 - os: macos-13 + os: macos-latest cmd: ./build_ios.sh arch: arm64 - name: ios out: ios-simulator-arm64 - os: macos-13 + os: macos-latest cmd: ./build_ios.sh arch: arm64 buildargs: --environment simulator @@ -112,17 +112,17 @@ jobs: - run: pip3 install setuptools # pkg_resources is sometimes not found? - name: Install linux dependencies - if: ${{ matrix.target.os == 'ubuntu-20.04' || matrix.target.os == 'ubuntu-latest' || matrix.target.os == 'buildjet-8vcpu-ubuntu-2004' }} + if: ${{ matrix.target.os == 'ubuntu-latest' }} run: | sudo apt update -y sudo apt install -y ninja-build pkg-config openjdk-11-jdk - name: Install macos dependencies - if: ${{ matrix.target.os == 'macos-13' }} + if: ${{ matrix.target.os == 'macos-latest' }} run: brew install ninja - name: Install windows dependencies - if: ${{ matrix.target.os == 'windows-2019' }} + if: ${{ matrix.target.os == 'windows-latest' }} run: | Invoke-WebRequest -Uri "https://github.com/ninja-build/ninja/releases/latest/download/ninja-win.zip" -OutFile ninja.zip Expand-Archive -Path ninja.zip -DestinationPath ${{ github.workspace }}\ninja @@ -141,13 +141,13 @@ jobs: working-directory: webrtc-sys/libwebrtc - name: Zip artifact (Unix) - if: ${{ matrix.target.os != 'windows-2019' }} + if: ${{ matrix.target.os != 'windows-latest' }} run: | cd webrtc-sys/libwebrtc zip ${{ github.workspace }}/${{ steps.setup.outputs.ZIP }} ${{ steps.setup.outputs.OUT }} -r - name: Zip artifact (Windows) - if: ${{ matrix.target.os == 'windows-2019' }} + if: ${{ matrix.target.os == 'windows-latest' }} run: Compress-Archive -Path .\webrtc-sys\libwebrtc\${{ steps.setup.outputs.OUT }} -DestinationPath ${{ steps.setup.outputs.ZIP }} - name: Upload artifacts From 2c627f50796f55be159d4f405126a7e832e7f22e Mon Sep 17 00:00:00 2001 From: David Zhao Date: Mon, 24 Feb 2025 09:23:14 -0800 Subject: [PATCH 151/274] fix: pass disconnect reason explicitly (#581) * fix: pass disconnect reason explicitly * format * change --- .nanpa/pass-disconnect.kdl | 1 + Cargo.lock | 4 ++-- livekit-ffi/protocol/room.proto | 7 ++++--- livekit-ffi/src/livekit.proto.rs | 2 ++ livekit-ffi/src/server/room.rs | 4 ++++ livekit/src/room/mod.rs | 1 + 6 files changed, 14 insertions(+), 5 deletions(-) create mode 100644 .nanpa/pass-disconnect.kdl diff --git a/.nanpa/pass-disconnect.kdl b/.nanpa/pass-disconnect.kdl new file mode 100644 index 000000000..8f9f7304b --- /dev/null +++ b/.nanpa/pass-disconnect.kdl @@ -0,0 +1 @@ +patch type="changed" package="livekit-ffi" "fixed passing disconnect reason on ParticipantDisconnected events" diff --git a/Cargo.lock b/Cargo.lock index b764e2232..78202bd3c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1605,7 +1605,7 @@ checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "livekit" -version = "0.7.4" +version = "0.7.5" dependencies = [ "chrono", "futures-util", @@ -1679,7 +1679,7 @@ dependencies = [ [[package]] name = "livekit-protocol" -version = "0.3.7" +version = "0.3.8" dependencies = [ "futures-util", "livekit-runtime", diff --git a/livekit-ffi/protocol/room.proto b/livekit-ffi/protocol/room.proto index 1333f6d55..051a94e5c 100644 --- a/livekit-ffi/protocol/room.proto +++ b/livekit-ffi/protocol/room.proto @@ -391,6 +391,7 @@ message ParticipantConnected { required OwnedParticipant info = 1; } message ParticipantDisconnected { required string participant_identity = 1; + required DisconnectReason disconnect_reason = 2; } message LocalTrackPublished { @@ -541,7 +542,7 @@ message DataStream { DELETE = 2; REACTION = 3; } - + // header properties specific to text streams message TextHeader { required OperationType operation_type = 1; @@ -551,7 +552,7 @@ message DataStream { optional bool generated = 5; // true if the text has been generated by an agent from a participant's audio transcription } - + // header properties specific to byte or file streams message ByteHeader { required string name = 1; @@ -565,7 +566,7 @@ message DataStream { required string topic = 4; optional uint64 total_length = 5; // only populated for finite streams, if it's a stream of unknown size this stays empty map attributes = 6; // user defined attributes map that can carry additional info - + // oneof to choose between specific header types oneof content_header { TextHeader text_header = 7; diff --git a/livekit-ffi/src/livekit.proto.rs b/livekit-ffi/src/livekit.proto.rs index 0b93a7be8..7a8aab12f 100644 --- a/livekit-ffi/src/livekit.proto.rs +++ b/livekit-ffi/src/livekit.proto.rs @@ -2772,6 +2772,8 @@ pub struct ParticipantConnected { pub struct ParticipantDisconnected { #[prost(string, required, tag="1")] pub participant_identity: ::prost::alloc::string::String, + #[prost(enumeration="DisconnectReason", required, tag="2")] + pub disconnect_reason: i32, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] diff --git a/livekit-ffi/src/server/room.rs b/livekit-ffi/src/server/room.rs index 7d9c797cf..382f224e5 100644 --- a/livekit-ffi/src/server/room.rs +++ b/livekit-ffi/src/server/room.rs @@ -983,6 +983,10 @@ async fn forward_event( let _ = send_event(proto::room_event::Message::ParticipantDisconnected( proto::ParticipantDisconnected { participant_identity: participant.identity().into(), + disconnect_reason: proto::DisconnectReason::from( + participant.disconnect_reason(), + ) + .into(), }, )); } diff --git a/livekit/src/room/mod.rs b/livekit/src/room/mod.rs index 2c25b653b..938743f09 100644 --- a/livekit/src/room/mod.rs +++ b/livekit/src/room/mod.rs @@ -833,6 +833,7 @@ impl RoomSession { let remote_participant = self.get_participant_by_sid(&participant_sid); if pi.state == proto::participant_info::State::Disconnected as i32 { if let Some(remote_participant) = remote_participant { + // need to update to get the correct disconnect reason remote_participant.update_info(pi.clone()); self.clone().handle_participant_disconnect(remote_participant) } else { From cbf07356fb987db03259f0549c0869679a0e41e8 Mon Sep 17 00:00:00 2001 From: "ilo-nanpa[bot]" <174912985+ilo-nanpa[bot]@users.noreply.github.com> Date: Mon, 24 Feb 2025 19:06:00 +0000 Subject: [PATCH 152/274] nanpa: bump --- .nanpa/pass-disconnect.kdl | 1 - Cargo.toml | 2 +- livekit-ffi/.nanparc | 2 +- livekit-ffi/CHANGELOG.md | 6 ++++++ livekit-ffi/Cargo.toml | 2 +- 5 files changed, 9 insertions(+), 4 deletions(-) delete mode 100644 .nanpa/pass-disconnect.kdl diff --git a/.nanpa/pass-disconnect.kdl b/.nanpa/pass-disconnect.kdl deleted file mode 100644 index 8f9f7304b..000000000 --- a/.nanpa/pass-disconnect.kdl +++ /dev/null @@ -1 +0,0 @@ -patch type="changed" package="livekit-ffi" "fixed passing disconnect reason on ParticipantDisconnected events" diff --git a/Cargo.toml b/Cargo.toml index a25445ffd..05da62b53 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ imgproc = { version = "0.3.12", path = "imgproc" } yuv-sys = { version = "0.3.7", path = "yuv-sys" } libwebrtc = { version = "0.3.10", path = "libwebrtc" } livekit-api = { version = "0.4.2", path = "livekit-api" } -livekit-ffi = { version = "0.12.10", path = "livekit-ffi" } +livekit-ffi = { version = "0.12.11", path = "livekit-ffi" } livekit-protocol = { version = "0.3.8", path = "livekit-protocol" } livekit-runtime = { version = "0.4.0", path = "livekit-runtime" } livekit = { version = "0.7.5", path = "livekit" } diff --git a/livekit-ffi/.nanparc b/livekit-ffi/.nanparc index ec339f25e..eb88c51a6 100644 --- a/livekit-ffi/.nanparc +++ b/livekit-ffi/.nanparc @@ -1,2 +1,2 @@ -version 0.12.10 +version 0.12.11 language rust diff --git a/livekit-ffi/CHANGELOG.md b/livekit-ffi/CHANGELOG.md index c3ebe7a82..aea4b629a 100644 --- a/livekit-ffi/CHANGELOG.md +++ b/livekit-ffi/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [0.12.11] - 2025-02-24 + +### Changed + +- fixed passing disconnect reason on ParticipantDisconnected events + ## [0.12.10] - 2025-02-04 ### Fixed diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 316d25616..48f7f2297 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.12.10" +version = "0.12.11" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" From 1d6308c84b6473533d4787afee30308f2ea6b0fb Mon Sep 17 00:00:00 2001 From: Daisuke Murase Date: Thu, 27 Feb 2025 16:11:32 -0800 Subject: [PATCH 153/274] feat: Audio Filter plugin (#559) * test impl * move AudioFilterAudioStream to livekit crate * rename * remove audio source additions * update proto * update proto * update proto * update proto * dependency library support * make on_load separated function * Move room and RemoteTrack association from foreign language to FFI * introduce the idea of module_id so that we can reuse filter handle that is stored in FfiRoom * check if audio session fails to initialize * second * refactore * can be initialize (register) audio_filter plugin inside Rust * forgot to remove this * change order * fmt * remove unused import * add changeset --- .nanpa/vice-maker-gore.kdl | 2 + Cargo.lock | 7 +- examples/play_from_disk/Cargo.lock | 106 ++++++++- livekit-ffi/protocol/audio_frame.proto | 15 ++ livekit-ffi/protocol/ffi.proto | 6 +- livekit-ffi/src/livekit.proto.rs | 39 +++- livekit-ffi/src/server/audio_plugin.rs | 30 +++ livekit-ffi/src/server/audio_stream.rs | 116 +++++++++- livekit-ffi/src/server/mod.rs | 1 + livekit-ffi/src/server/requests.rs | 26 ++- livekit-ffi/src/server/room.rs | 46 +++- livekit/Cargo.toml | 1 + livekit/src/lib.rs | 3 + livekit/src/plugin.rs | 286 +++++++++++++++++++++++++ 14 files changed, 657 insertions(+), 27 deletions(-) create mode 100644 .nanpa/vice-maker-gore.kdl create mode 100644 livekit-ffi/src/server/audio_plugin.rs create mode 100644 livekit/src/plugin.rs diff --git a/.nanpa/vice-maker-gore.kdl b/.nanpa/vice-maker-gore.kdl new file mode 100644 index 000000000..856568b83 --- /dev/null +++ b/.nanpa/vice-maker-gore.kdl @@ -0,0 +1,2 @@ +patch package="livekit-ffi" type="added" "Add audio filter support" +patch package="livekit" type="added" "Add audio filter support" diff --git a/Cargo.lock b/Cargo.lock index 78202bd3c..781cc6473 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1539,12 +1539,12 @@ checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libloading" -version = "0.8.1" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-sys 0.48.0", + "windows-targets 0.52.0", ] [[package]] @@ -1610,6 +1610,7 @@ dependencies = [ "chrono", "futures-util", "lazy_static", + "libloading", "libwebrtc", "livekit-api", "livekit-protocol", diff --git a/examples/play_from_disk/Cargo.lock b/examples/play_from_disk/Cargo.lock index 0b8ceba95..f06220cd3 100644 --- a/examples/play_from_disk/Cargo.lock +++ b/examples/play_from_disk/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -37,6 +37,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anyhow" version = "1.0.80" @@ -160,11 +175,16 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.34" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", "num-traits", + "wasm-bindgen", + "windows-targets 0.52.0", ] [[package]] @@ -492,8 +512,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -641,6 +663,29 @@ dependencies = [ "tokio-rustls", ] +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "idna" version = "0.5.0" @@ -742,6 +787,19 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jsonwebtoken" +version = "9.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ae10193d25051e74945f1ea2d0b42e03cc3b890f7e4cc5faa44997d808193f" +dependencies = [ + "base64", + "js-sys", + "ring", + "serde", + "serde_json", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -754,9 +812,19 @@ version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +[[package]] +name = "libloading" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +dependencies = [ + "cfg-if", + "windows-targets 0.52.0", +] + [[package]] name = "libwebrtc" -version = "0.3.0" +version = "0.3.9" dependencies = [ "cxx", "jni", @@ -770,7 +838,6 @@ dependencies = [ "serde_json", "thiserror", "tokio", - "tokio-stream", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -794,10 +861,12 @@ checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "livekit" -version = "0.3.0" +version = "0.7.3" dependencies = [ + "chrono", "futures-util", "lazy_static", + "libloading", "libwebrtc", "livekit-api", "livekit-protocol", @@ -805,6 +874,7 @@ dependencies = [ "log", "parking_lot", "prost", + "semver", "serde", "serde_json", "thiserror", @@ -813,18 +883,22 @@ dependencies = [ [[package]] name = "livekit-api" -version = "0.3.0" +version = "0.4.1" dependencies = [ + "base64", "futures-util", "http", + "jsonwebtoken", "livekit-protocol", "livekit-runtime", "log", "parking_lot", + "pbjson-types", "prost", "reqwest", "scopeguard", "serde", + "serde_json", "sha2", "thiserror", "tokio", @@ -834,7 +908,7 @@ dependencies = [ [[package]] name = "livekit-protocol" -version = "0.3.0" +version = "0.3.7" dependencies = [ "futures-util", "livekit-runtime", @@ -850,9 +924,10 @@ dependencies = [ [[package]] name = "livekit-runtime" -version = "0.3.0" +version = "0.3.1" dependencies = [ "tokio", + "tokio-stream", ] [[package]] @@ -1954,7 +2029,7 @@ dependencies = [ [[package]] name = "webrtc-sys" -version = "0.3.0" +version = "0.3.6" dependencies = [ "cc", "cxx", @@ -1966,7 +2041,7 @@ dependencies = [ [[package]] name = "webrtc-sys-build" -version = "0.3.0" +version = "0.3.6" dependencies = [ "fs2", "regex", @@ -2019,6 +2094,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.0", +] + [[package]] name = "windows-sys" version = "0.45.0" diff --git a/livekit-ffi/protocol/audio_frame.proto b/livekit-ffi/protocol/audio_frame.proto index 4d70c310f..3dec06fe3 100644 --- a/livekit-ffi/protocol/audio_frame.proto +++ b/livekit-ffi/protocol/audio_frame.proto @@ -27,6 +27,8 @@ message NewAudioStreamRequest { required AudioStreamType type = 2; optional uint32 sample_rate = 3; optional uint32 num_channels = 4; + optional string audio_filter_module_id = 5; // Unique identifier passed in LoadAudioFilterPluginRequest + optional string audio_filter_options = 6; } message NewAudioStreamResponse { required OwnedAudioStream stream = 1; } @@ -36,6 +38,8 @@ message AudioStreamFromParticipantRequest { optional TrackSource track_source = 3; optional uint32 sample_rate = 5; optional uint32 num_channels = 6; + optional string audio_filter_module_id = 7; + optional string audio_filter_options = 8; } message AudioStreamFromParticipantResponse { required OwnedAudioStream stream = 1; } @@ -249,3 +253,14 @@ message OwnedSoxResampler { required FfiOwnedHandle handle = 1; required SoxResamplerInfo info = 2; } + +// Audio Filter Plugin +message LoadAudioFilterPluginRequest { + required string plugin_path = 1; // path for ffi audio filter plugin + repeated string dependencies = 2; // Optional: paths for dependency dylibs + required string module_id = 3; // Unique identifier of the plugin +} + +message LoadAudioFilterPluginResponse { + optional string error = 1; +} diff --git a/livekit-ffi/protocol/ffi.proto b/livekit-ffi/protocol/ffi.proto index 584197348..d94ae263b 100644 --- a/livekit-ffi/protocol/ffi.proto +++ b/livekit-ffi/protocol/ffi.proto @@ -120,7 +120,8 @@ message FfiRequest { // Data Channel SetDataChannelBufferedAmountLowThresholdRequest set_data_channel_buffered_amount_low_threshold = 47; - // NEXT_ID: 49 + // Audio Filter Plugin + LoadAudioFilterPluginRequest load_audio_filter_plugin = 49; } } @@ -188,7 +189,8 @@ message FfiResponse { // Data Channel SetDataChannelBufferedAmountLowThresholdResponse set_data_channel_buffered_amount_low_threshold = 46; - // NEXT_ID: 48 + // Audio Filter Plugin + LoadAudioFilterPluginResponse load_audio_filter_plugin = 48; } } diff --git a/livekit-ffi/src/livekit.proto.rs b/livekit-ffi/src/livekit.proto.rs index 7a8aab12f..650f7e3f5 100644 --- a/livekit-ffi/src/livekit.proto.rs +++ b/livekit-ffi/src/livekit.proto.rs @@ -3433,6 +3433,11 @@ pub struct NewAudioStreamRequest { pub sample_rate: ::core::option::Option, #[prost(uint32, optional, tag="4")] pub num_channels: ::core::option::Option, + /// Unique identifier passed in LoadAudioFilterPluginRequest + #[prost(string, optional, tag="5")] + pub audio_filter_module_id: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="6")] + pub audio_filter_options: ::core::option::Option<::prost::alloc::string::String>, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -3453,6 +3458,10 @@ pub struct AudioStreamFromParticipantRequest { pub sample_rate: ::core::option::Option, #[prost(uint32, optional, tag="6")] pub num_channels: ::core::option::Option, + #[prost(string, optional, tag="7")] + pub audio_filter_module_id: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="8")] + pub audio_filter_options: ::core::option::Option<::prost::alloc::string::String>, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -3752,6 +3761,26 @@ pub struct OwnedSoxResampler { #[prost(message, required, tag="2")] pub info: SoxResamplerInfo, } +/// Audio Filter Plugin +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct LoadAudioFilterPluginRequest { + /// path for ffi audio filter plugin + #[prost(string, required, tag="1")] + pub plugin_path: ::prost::alloc::string::String, + /// Optional: paths for dependency dylibs + #[prost(string, repeated, tag="2")] + pub dependencies: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + /// Unique identifier of the plugin + #[prost(string, required, tag="3")] + pub module_id: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct LoadAudioFilterPluginResponse { + #[prost(string, optional, tag="1")] + pub error: ::core::option::Option<::prost::alloc::string::String>, +} #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] pub enum SoxResamplerDataType { @@ -4046,7 +4075,7 @@ pub struct RpcMethodInvocationEvent { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiRequest { - #[prost(oneof="ffi_request::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 48, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47")] + #[prost(oneof="ffi_request::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 48, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 49")] pub message: ::core::option::Option, } /// Nested message and enum types in `FfiRequest`. @@ -4156,13 +4185,16 @@ pub mod ffi_request { /// Data Channel #[prost(message, tag="47")] SetDataChannelBufferedAmountLowThreshold(super::SetDataChannelBufferedAmountLowThresholdRequest), + /// Audio Filter Plugin + #[prost(message, tag="49")] + LoadAudioFilterPlugin(super::LoadAudioFilterPluginRequest), } } /// This is the output of livekit_ffi_request function. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiResponse { - #[prost(oneof="ffi_response::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 47, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46")] + #[prost(oneof="ffi_response::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 47, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 48")] pub message: ::core::option::Option, } /// Nested message and enum types in `FfiResponse`. @@ -4270,6 +4302,9 @@ pub mod ffi_response { /// Data Channel #[prost(message, tag="46")] SetDataChannelBufferedAmountLowThreshold(super::SetDataChannelBufferedAmountLowThresholdResponse), + /// Audio Filter Plugin + #[prost(message, tag="48")] + LoadAudioFilterPlugin(super::LoadAudioFilterPluginResponse), } } /// To minimize complexity, participant events are not included in the protocol. diff --git a/livekit-ffi/src/server/audio_plugin.rs b/livekit-ffi/src/server/audio_plugin.rs new file mode 100644 index 000000000..bfa1bf6c2 --- /dev/null +++ b/livekit-ffi/src/server/audio_plugin.rs @@ -0,0 +1,30 @@ +use std::{ + pin::Pin, + task::{Context, Poll}, +}; + +use futures_util::Stream; +use livekit::{ + webrtc::{audio_stream::native::NativeAudioStream, prelude::AudioFrame}, + AudioFilterAudioStream, +}; + +pub trait AudioStream: Stream> + Send + Sync + Unpin { + fn close(&mut self); +} + +pub enum AudioStreamKind { + Native(NativeAudioStream), + Filtered(AudioFilterAudioStream), +} + +impl Stream for AudioStreamKind { + type Item = AudioFrame<'static>; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + match self.get_mut() { + AudioStreamKind::Native(native_stream) => Pin::new(native_stream).poll_next(cx), + AudioStreamKind::Filtered(duration_stream) => Pin::new(duration_stream).poll_next(cx), + } + } +} diff --git a/livekit-ffi/src/server/audio_stream.rs b/livekit-ffi/src/server/audio_stream.rs index 782969613..8cb6843b4 100644 --- a/livekit-ffi/src/server/audio_stream.rs +++ b/livekit-ffi/src/server/audio_stream.rs @@ -12,11 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::time::Duration; + use futures_util::StreamExt; use livekit::track::Track; use livekit::webrtc::{audio_stream::native::NativeAudioStream, prelude::*}; +use livekit::{registered_audio_filter_plugin, AudioFilterAudioStream, AudioFilterStreamInfo}; use tokio::sync::{broadcast, mpsc, oneshot}; +use super::audio_plugin::AudioStreamKind; +use super::room::FfiRoom; use super::{room::FfiTrack, FfiHandle}; use crate::server::utils; use crate::{proto, server, FfiError, FfiHandleId, FfiResult}; @@ -52,6 +57,37 @@ impl FfiAudioStream { return Err(FfiError::InvalidRequest("not an audio track".into())); }; + let (audio_filter, stream_info) = match &new_stream.audio_filter_module_id { + Some(module_id) => { + let Some(room_handle) = ffi_track.room_handle else { + return Err(FfiError::InvalidRequest( + "this track has no room information".into(), + )); + }; + let room = server.retrieve_handle::(room_handle)?.clone(); + let Some(filter) = registered_audio_filter_plugin(module_id) else { + return Err(FfiError::InvalidRequest("the audio filter is not found".into())); + }; + + let stream_info = AudioFilterStreamInfo { + url: room.inner.url(), + room_id: room + .inner + .room + .maybe_sid() + .map(|sid| sid.to_string()) + .unwrap_or("".into()), + room_name: room.inner.room.name(), + participant_identity: room.inner.room.local_participant().identity().into(), + participant_id: room.inner.room.local_participant().name(), + track_id: rtc_track.id(), + }; + + (Some(filter), stream_info) + } + None => (None, AudioFilterStreamInfo::default()), + }; + let stream_type = new_stream.r#type(); let handle_id = server.next_id(); let audio_stream = match stream_type { @@ -63,10 +99,33 @@ impl FfiAudioStream { let native_stream = NativeAudioStream::new(rtc_track, sample_rate as i32, num_channels as i32); + + let stream = if let Some(audio_filter) = &audio_filter { + let Some(session) = audio_filter.clone().new_session( + sample_rate, + new_stream.audio_filter_options.unwrap_or("".into()), + stream_info, + ) else { + return Err(FfiError::InvalidRequest( + "audio filter is not initialized".into(), + )); + }; + let stream = AudioFilterAudioStream::new( + native_stream, + session, + Duration::from_millis(10), + sample_rate, + num_channels, + ); + AudioStreamKind::Filtered(stream) + } else { + AudioStreamKind::Native(native_stream) + }; + let handle = server.async_runtime.spawn(Self::native_audio_stream_task( server, handle_id, - native_stream, + stream, self_dropped_rx, server.watch_handle_dropped(new_stream.track_handle), true, @@ -91,6 +150,7 @@ impl FfiAudioStream { let (self_dropped_tx, self_dropped_rx) = oneshot::channel(); let handle_id = server.next_id(); let stream_type = request.r#type(); + let audio_stream = match stream_type { #[cfg(not(target_arch = "wasm32"))] proto::AudioStreamType::AudioStreamNative => { @@ -135,13 +195,23 @@ impl FfiAudioStream { let (track_tx, mut track_rx) = mpsc::channel::(1); let (track_finished_tx, _) = broadcast::channel::(1); server.async_runtime.spawn(utils::track_changed_trigger( - ffi_participant, + ffi_participant.clone(), track_source.into(), track_tx, track_finished_tx.clone(), )); // track_tx is no longer held, so the track_rx will be closed when track_changed_trigger is done + let url = ffi_participant.room.url(); + let room_sid = ffi_participant.room.room.sid().await; + let room_name = ffi_participant.room.room.name(); + let participant_identity = ffi_participant.participant.identity(); + let participant_id = ffi_participant.participant.sid(); + let filter = match &request.audio_filter_module_id { + Some(module_id) => registered_audio_filter_plugin(module_id), + None => None, + }; + loop { let track = track_rx.recv().await; if let Some(track) = track { @@ -149,11 +219,13 @@ impl FfiAudioStream { let MediaStreamTrack::Audio(rtc_track) = rtc_track else { continue; }; + let (c_tx, c_rx) = oneshot::channel::<()>(); let (handle_dropped_tx, handle_dropped_rx) = oneshot::channel::<()>(); let (done_tx, mut done_rx) = oneshot::channel::<()>(); let sample_rate = request.sample_rate.unwrap_or(48000) as i32; let num_channels = request.num_channels.unwrap_or(1) as i32; + let track_sid = track.sid(); let mut track_finished_rx = track_finished_tx.subscribe(); server.async_runtime.spawn(async move { @@ -162,7 +234,7 @@ impl FfiAudioStream { let Ok(t) = t else { return }; - if t.sid() == track.sid() { + if t.sid() == track_sid { handle_dropped_tx.send(()).ok(); return } @@ -170,11 +242,45 @@ impl FfiAudioStream { } }); + let mut audio_filter_session = match &filter { + Some(filter) => match &request.audio_filter_options { + Some(options) => { + let stream_info = AudioFilterStreamInfo { + url: url.clone(), + room_id: room_sid.clone().into(), + room_name: room_name.clone(), + participant_identity: participant_identity.clone().into(), + participant_id: participant_id.clone().into(), + track_id: track.sid().into(), + }; + + filter.clone().new_session(sample_rate as u32, &options, stream_info) + } + None => None, + }, + None => None, + }; + + let native_stream = NativeAudioStream::new(rtc_track, sample_rate, num_channels); + + let stream = if let Some(session) = audio_filter_session.take() { + let stream = AudioFilterAudioStream::new( + native_stream, + session, + Duration::from_millis(10), + sample_rate as u32, + num_channels as u32, + ); + AudioStreamKind::Filtered(stream) + } else { + AudioStreamKind::Native(native_stream) + }; + server.async_runtime.spawn(async move { Self::native_audio_stream_task( server, stream_handle, - NativeAudioStream::new(rtc_track, sample_rate, num_channels), + stream, c_rx, handle_dropped_rx, false, @@ -209,7 +315,7 @@ impl FfiAudioStream { async fn native_audio_stream_task( server: &'static server::FfiServer, stream_handle_id: FfiHandleId, - mut native_stream: NativeAudioStream, + mut native_stream: AudioStreamKind, mut self_dropped_rx: oneshot::Receiver<()>, mut handle_dropped_rx: oneshot::Receiver<()>, send_eos: bool, diff --git a/livekit-ffi/src/server/mod.rs b/livekit-ffi/src/server/mod.rs index 9c0f7dc82..ae39473ac 100644 --- a/livekit-ffi/src/server/mod.rs +++ b/livekit-ffi/src/server/mod.rs @@ -30,6 +30,7 @@ use tokio::{sync::oneshot, task::JoinHandle}; use crate::{proto, proto::FfiEvent, FfiError, FfiHandleId, FfiResult, INVALID_HANDLE}; +pub mod audio_plugin; pub mod audio_source; pub mod audio_stream; pub mod colorcvt; diff --git a/livekit-ffi/src/server/requests.rs b/livekit-ffi/src/server/requests.rs index a421f67f6..83f41c01c 100644 --- a/livekit-ffi/src/server/requests.rs +++ b/livekit-ffi/src/server/requests.rs @@ -17,7 +17,9 @@ use std::{slice, sync::Arc}; use colorcvt::cvtimpl; use livekit::{ prelude::*, + register_audio_filter_plugin, webrtc::{native::audio_resampler, prelude::*}, + AudioFilterPlugin, }; use parking_lot::Mutex; @@ -280,7 +282,8 @@ fn on_create_video_track( let handle_id = server.next_id(); let video_track = LocalVideoTrack::create_video_track(&create.name, source); - let ffi_track = FfiTrack { handle: handle_id, track: Track::LocalVideo(video_track) }; + let ffi_track = + FfiTrack { handle: handle_id, track: Track::LocalVideo(video_track), room_handle: None }; let track_info = proto::TrackInfo::from(&ffi_track); server.store_handle(handle_id, ffi_track); @@ -305,7 +308,8 @@ fn on_create_audio_track( let handle_id = server.next_id(); let audio_track = LocalAudioTrack::create_audio_track(&create.name, source); - let ffi_track = FfiTrack { handle: handle_id, track: Track::LocalAudio(audio_track) }; + let ffi_track = + FfiTrack { handle: handle_id, track: Track::LocalAudio(audio_track), room_handle: None }; let track_info = proto::TrackInfo::from(&ffi_track); server.store_handle(handle_id, ffi_track); @@ -919,6 +923,19 @@ fn on_set_data_channel_buffered_amount_low_threshold( )) } +fn on_load_audio_filter_plugin( + _server: &'static FfiServer, + request: proto::LoadAudioFilterPluginRequest, +) -> FfiResult { + let deps: Vec<_> = request.dependencies.iter().map(|d| d).collect(); + let plugin = AudioFilterPlugin::new_with_dependencies(&request.plugin_path, deps) + .map_err(|e| FfiError::InvalidRequest(format!("plugin error: {}", e).into()))?; + + register_audio_filter_plugin(request.module_id, plugin); + + Ok(proto::LoadAudioFilterPluginResponse { error: None }) +} + fn on_set_track_subscription_permissions( server: &'static FfiServer, set_permissions: proto::SetTrackSubscriptionPermissionsRequest, @@ -1107,6 +1124,11 @@ pub fn handle_request( on_set_data_channel_buffered_amount_low_threshold(server, request)?, ) } + proto::ffi_request::Message::LoadAudioFilterPlugin(request) => { + proto::ffi_response::Message::LoadAudioFilterPlugin(on_load_audio_filter_plugin( + server, request, + )?) + } proto::ffi_request::Message::SetTrackSubscriptionPermissions(request) => { proto::ffi_response::Message::SetTrackSubscriptionPermissions( on_set_track_subscription_permissions(server, request)?, diff --git a/livekit-ffi/src/server/room.rs b/livekit-ffi/src/server/room.rs index 382f224e5..13913d01d 100644 --- a/livekit-ffi/src/server/room.rs +++ b/livekit-ffi/src/server/room.rs @@ -16,8 +16,8 @@ use std::collections::HashMap; use std::time::Duration; use std::{collections::HashSet, slice, sync::Arc}; -use livekit::prelude::*; use livekit::ChatMessage; +use livekit::{prelude::*, registered_audio_filter_plugins}; use livekit_protocol as lk_proto; use parking_lot::Mutex; use tokio::sync::{broadcast, mpsc, oneshot, Mutex as AsyncMutex}; @@ -41,6 +41,7 @@ pub struct FfiPublication { pub struct FfiTrack { pub handle: FfiHandleId, pub track: Track, + pub room_handle: Option, } impl FfiHandle for FfiTrack {} @@ -70,6 +71,9 @@ pub struct RoomInner { // Used to forward RPC method invocation to the FfiClient and collect their results rpc_method_invocation_waiters: Mutex>>>, + + // ws url associated with this room + url: String, } struct Handle { @@ -113,6 +117,7 @@ impl FfiRoom { ) -> proto::ConnectResponse { let async_id = server.next_id(); + let req = connect.clone(); let mut options: RoomOptions = connect.options.into(); { @@ -126,6 +131,34 @@ impl FfiRoom { let connect = async move { match Room::connect(&connect.url, &connect.token, options.clone()).await { Ok((room, mut events)) => { + // initialize audio filters + let result = server + .async_runtime + .spawn_blocking(move || { + for filter in registered_audio_filter_plugins().into_iter() { + filter.on_load(&req.url, &req.token).map_err(|e| e.to_string())?; + } + Ok::<(), String>(()) + }) + .await + .map_err(|e| e.to_string()); + match result { + Err(e) | Ok(Err(e)) => { + log::error!("error while initializing audio filter: {}", e); + let _ = server.send_event(proto::ffi_event::Message::Connect( + proto::ConnectCallback { + async_id, + message: Some(proto::connect_callback::Message::Error( + e.to_string(), + )), + ..Default::default() + }, + )); + return; + } + Ok(Ok(_)) => (), + }; + // Successfully connected to the room // Forward the initial state for the FfiClient let Some(RoomEvent::Connected { participants_with_tracks }) = @@ -150,6 +183,7 @@ impl FfiRoom { pending_unpublished_tracks: Default::default(), track_handle_lookup: Default::default(), rpc_method_invocation_waiters: Default::default(), + url: connect.url, }); let (local_info, remote_infos) = @@ -803,6 +837,10 @@ impl RoomInner { server.watch_panic(handle); proto::SetTrackSubscriptionPermissionsResponse {} } + + pub fn url(&self) -> String { + self.url.clone() + } } // Task used to publish data without blocking the client thread @@ -1058,7 +1096,11 @@ async fn forward_event( RoomEvent::TrackSubscribed { track, publication: _, participant } => { let handle_id = server.next_id(); let track_sid = track.sid(); - let ffi_track = FfiTrack { handle: handle_id, track: track.into() }; + let ffi_track = FfiTrack { + handle: handle_id, + track: track.into(), + room_handle: Some(inner.handle_id), + }; let track_info = proto::TrackInfo::from(&ffi_track); server.store_handle(ffi_track.handle, ffi_track); diff --git a/livekit/Cargo.toml b/livekit/Cargo.toml index 46ea7e486..6040b010f 100644 --- a/livekit/Cargo.toml +++ b/livekit/Cargo.toml @@ -42,3 +42,4 @@ lazy_static = "1.4" log = "0.4" chrono = "0.4.38" semver = "1.0" +libloading = { version = "0.8.6" } diff --git a/livekit/src/lib.rs b/livekit/src/lib.rs index b63d77abc..8171af92b 100644 --- a/livekit/src/lib.rs +++ b/livekit/src/lib.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +mod plugin; pub mod proto; mod room; mod rtc_engine; @@ -31,3 +32,5 @@ pub mod dispatcher { pub use livekit_runtime::Dispatcher; pub use livekit_runtime::Runnable; } + +pub use plugin::*; diff --git a/livekit/src/plugin.rs b/livekit/src/plugin.rs new file mode 100644 index 000000000..23af1c057 --- /dev/null +++ b/livekit/src/plugin.rs @@ -0,0 +1,286 @@ +use std::{ + collections::HashMap, + ffi::{c_char, c_void, CString}, + pin::Pin, + sync::{Arc, LazyLock}, + task::{Context, Poll}, + time::Duration, +}; + +use futures_util::Stream; +use libloading::{Library, Symbol}; +use libwebrtc::{audio_stream::native::NativeAudioStream, prelude::AudioFrame}; +use parking_lot::RwLock; +use serde::Serialize; +use serde_json::json; + +#[derive(Debug, thiserror::Error)] +pub enum PluginError { + #[error("dylib error: {0}")] + Library(#[from] libloading::Error), + #[error("dylib error: {0}")] + NotImplemented(String), + #[error("on_load failed with error: {0}")] + OnLoad(i32), +} + +type OnLoadFn = unsafe extern "C" fn(options: *const c_char) -> i32; +type CreateFn = unsafe extern "C" fn( + sampling_rate: u32, + options: *const c_char, + stream_info: *const c_char, +) -> *mut c_void; +type DestroyFn = unsafe extern "C" fn(*const c_void); +type ProcessI16Fn = unsafe extern "C" fn(*const c_void, usize, *const i16, *mut i16); +type ProcessF32Fn = unsafe extern "C" fn(*const c_void, usize, *const f32, *mut f32); + +static REGISTERED_PLUGINS: LazyLock>>> = + LazyLock::new(|| RwLock::new(HashMap::new())); + +pub fn register_audio_filter_plugin(id: String, plugin: Arc) { + REGISTERED_PLUGINS.write().insert(id, plugin); +} + +pub fn registered_audio_filter_plugin(id: &str) -> Option> { + REGISTERED_PLUGINS.read().get(id).cloned() +} + +pub fn registered_audio_filter_plugins() -> Vec> { + REGISTERED_PLUGINS.read().values().map(|v| v.clone()).collect() +} + +pub struct AudioFilterPlugin { + lib: Library, + dependencies: Vec, + on_load_fn_ptr: *const c_void, + create_fn_ptr: *const c_void, + destroy_fn_ptr: *const c_void, + process_i16_fn_ptr: *const c_void, + process_f32_fn_ptr: *const c_void, +} + +impl AudioFilterPlugin { + pub fn new>(path: P) -> Result, PluginError> { + Ok(Arc::new(Self::_new(path)?)) + } + + pub fn new_with_dependencies>( + path: P, + dependencies: Vec

    , + ) -> Result, PluginError> { + let mut libs = vec![]; + for path in dependencies { + let lib = unsafe { Library::new(path.as_ref()) }?; + libs.push(lib); + } + let mut this = Self::_new(path)?; + this.dependencies = libs; + Ok(Arc::new(this)) + } + + fn _new>(path: P) -> Result { + let lib = unsafe { Library::new(path.as_ref()) }?; + + let on_load_fn_ptr = unsafe { + lib.get::>(b"audio_filter_on_load")?.try_as_raw_ptr().unwrap() + }; + + let create_fn_ptr = unsafe { + lib.get::>(b"audio_filter_create")?.try_as_raw_ptr().unwrap() + }; + if create_fn_ptr.is_null() { + return Err(PluginError::NotImplemented( + "audio_filter_create is not implemented".into(), + )); + } + let destroy_fn_ptr = unsafe { + lib.get::>(b"audio_filter_destroy")?.try_as_raw_ptr().unwrap() + }; + if destroy_fn_ptr.is_null() { + return Err(PluginError::NotImplemented( + "audio_filter_destroy is not implemented".into(), + )); + } + let process_i16_fn_ptr = unsafe { + lib.get::>(b"audio_filter_process_int16")? + .try_as_raw_ptr() + .unwrap() + }; + if process_i16_fn_ptr.is_null() { + return Err(PluginError::NotImplemented( + "audio_filter_process_int16 is not implemented".into(), + )); + } + let process_f32_fn_ptr = unsafe { + lib.get::>(b"audio_filter_process_float")? + .try_as_raw_ptr() + .unwrap() + }; + + Ok(Self { + lib, + dependencies: Default::default(), + on_load_fn_ptr, + create_fn_ptr, + destroy_fn_ptr, + process_i16_fn_ptr, + process_f32_fn_ptr, + }) + } + + pub fn on_load>(&self, url: S, token: S) -> Result<(), PluginError> { + if self.on_load_fn_ptr.is_null() { + // on_load is optional function + return Ok(()); + } + + let options_json = json!({ + "url": url.as_ref().to_string(), + "token": token.as_ref().to_string(), + }); + let options = serde_json::to_string(&options_json).map_err(|e| { + eprintln!("failed to serialize option: {}", e); + PluginError::OnLoad(-1) + })?; + + let options = CString::new(options).unwrap_or(CString::new("").unwrap()); + let on_load_fn: OnLoadFn = unsafe { std::mem::transmute(self.on_load_fn_ptr) }; + + let res = unsafe { on_load_fn(options.as_ptr()) }; + if res == 0 { + Ok(()) + } else { + Err(PluginError::OnLoad(res)) + } + } + + pub fn new_session>( + self: Arc, + sampling_rate: u32, + options: S, + stream_info: AudioFilterStreamInfo, + ) -> Option { + let create_fn: CreateFn = unsafe { std::mem::transmute(self.create_fn_ptr) }; + + let options = CString::new(options.as_ref()).unwrap_or(CString::new("").unwrap()); + + let stream_info = serde_json::to_string(&stream_info).unwrap(); + let stream_info = CString::new(stream_info).unwrap_or(CString::new("").unwrap()); + + let ptr = unsafe { create_fn(sampling_rate, options.as_ptr(), stream_info.as_ptr()) }; + if ptr.is_null() { + return None; + } + + Some(AudioFilterSession { plugin: self.clone(), ptr }) + } +} + +pub struct AudioFilterSession { + plugin: Arc, + ptr: *const c_void, +} + +impl AudioFilterSession { + pub fn destroy(&self) { + let destroy: DestroyFn = unsafe { std::mem::transmute(self.plugin.destroy_fn_ptr) }; + unsafe { destroy(self.ptr) }; + } + + pub fn process_i16(&self, num_samples: usize, input: &[i16], output: &mut [i16]) { + let process: ProcessI16Fn = unsafe { std::mem::transmute(self.plugin.process_i16_fn_ptr) }; + unsafe { process(self.ptr, num_samples, input.as_ptr(), output.as_mut_ptr()) }; + } + + pub fn process_f32(&self, num_samples: usize, input: &[f32], output: &mut [f32]) { + let process: ProcessF32Fn = unsafe { std::mem::transmute(self.plugin.process_f32_fn_ptr) }; + unsafe { process(self.ptr, num_samples, input.as_ptr(), output.as_mut_ptr()) }; + } +} + +impl Drop for AudioFilterSession { + fn drop(&mut self) { + if !self.ptr.is_null() { + self.destroy(); + } + } +} + +pub struct AudioFilterAudioStream { + inner: NativeAudioStream, + session: AudioFilterSession, + buffer: Vec, + sample_rate: u32, + num_channels: u32, + frame_size: usize, +} + +impl AudioFilterAudioStream { + pub fn new( + inner: NativeAudioStream, + session: AudioFilterSession, + duration: Duration, + sample_rate: u32, + num_channels: u32, + ) -> Self { + let frame_size = + ((sample_rate as f64) * duration.as_secs_f64() * num_channels as f64) as usize; + Self { + inner, + session, + buffer: Vec::with_capacity(frame_size), + sample_rate, + num_channels, + frame_size, + } + } +} + +impl Stream for AudioFilterAudioStream { + type Item = AudioFrame<'static>; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + let this = self.get_mut(); + + while let Poll::Ready(frame) = Pin::new(&mut this.inner).poll_next(cx) { + let Some(frame) = frame else { + return Poll::Ready(None); + }; + this.buffer.extend_from_slice(&frame.data); + + if this.buffer.len() >= this.frame_size { + let data = this.buffer.drain(..this.frame_size).collect::>(); + let mut out: Vec = Vec::with_capacity(this.frame_size); + + this.session.process_i16(this.frame_size, &data, &mut out); + + return Poll::Ready(Some(AudioFrame { + data: out.into(), + sample_rate: this.sample_rate, + num_channels: this.num_channels, + samples_per_channel: (this.frame_size / this.num_channels as usize) as u32, + })); + } + } + + Poll::Pending + } +} + +#[derive(Debug, Serialize, Default)] +#[serde(rename_all = "camelCase")] +pub struct AudioFilterStreamInfo { + pub url: String, + pub room_id: String, + pub room_name: String, + pub participant_identity: String, + pub participant_id: String, + pub track_id: String, +} + +// The function pointers in this struct are initialized only once during construction +// and remain read-only throughout the lifetime of the struct, ensuring thread safety. +unsafe impl Send for AudioFilterPlugin {} +unsafe impl Sync for AudioFilterPlugin {} +unsafe impl Send for AudioFilterSession {} +unsafe impl Sync for AudioFilterSession {} From c833df8e31532b72d6f738e9a8fd1beb11e9b321 Mon Sep 17 00:00:00 2001 From: "ilo-nanpa[bot]" <174912985+ilo-nanpa[bot]@users.noreply.github.com> Date: Fri, 28 Feb 2025 00:12:30 +0000 Subject: [PATCH 154/274] nanpa: bump --- .nanpa/vice-maker-gore.kdl | 2 -- Cargo.toml | 4 ++-- livekit-ffi/.nanparc | 2 +- livekit-ffi/CHANGELOG.md | 6 ++++++ livekit-ffi/Cargo.toml | 2 +- livekit/.nanparc | 2 +- livekit/CHANGELOG.md | 6 ++++++ livekit/Cargo.toml | 2 +- 8 files changed, 18 insertions(+), 8 deletions(-) delete mode 100644 .nanpa/vice-maker-gore.kdl diff --git a/.nanpa/vice-maker-gore.kdl b/.nanpa/vice-maker-gore.kdl deleted file mode 100644 index 856568b83..000000000 --- a/.nanpa/vice-maker-gore.kdl +++ /dev/null @@ -1,2 +0,0 @@ -patch package="livekit-ffi" type="added" "Add audio filter support" -patch package="livekit" type="added" "Add audio filter support" diff --git a/Cargo.toml b/Cargo.toml index 05da62b53..49096a9bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,10 +19,10 @@ imgproc = { version = "0.3.12", path = "imgproc" } yuv-sys = { version = "0.3.7", path = "yuv-sys" } libwebrtc = { version = "0.3.10", path = "libwebrtc" } livekit-api = { version = "0.4.2", path = "livekit-api" } -livekit-ffi = { version = "0.12.11", path = "livekit-ffi" } +livekit-ffi = { version = "0.12.12", path = "livekit-ffi" } livekit-protocol = { version = "0.3.8", path = "livekit-protocol" } livekit-runtime = { version = "0.4.0", path = "livekit-runtime" } -livekit = { version = "0.7.5", path = "livekit" } +livekit = { version = "0.7.6", path = "livekit" } soxr-sys = { version = "0.1.0", path = "soxr-sys" } webrtc-sys-build = { version = "0.3.6", path = "webrtc-sys/build" } webrtc-sys = { version = "0.3.7", path = "webrtc-sys" } diff --git a/livekit-ffi/.nanparc b/livekit-ffi/.nanparc index eb88c51a6..b88996568 100644 --- a/livekit-ffi/.nanparc +++ b/livekit-ffi/.nanparc @@ -1,2 +1,2 @@ -version 0.12.11 +version 0.12.12 language rust diff --git a/livekit-ffi/CHANGELOG.md b/livekit-ffi/CHANGELOG.md index aea4b629a..eadb6e648 100644 --- a/livekit-ffi/CHANGELOG.md +++ b/livekit-ffi/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [0.12.12] - 2025-02-28 + +### Added + +- Add audio filter support + ## [0.12.11] - 2025-02-24 ### Changed diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 48f7f2297..7be90e9d1 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.12.11" +version = "0.12.12" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" diff --git a/livekit/.nanparc b/livekit/.nanparc index ba2d28ba5..4f0ae7374 100644 --- a/livekit/.nanparc +++ b/livekit/.nanparc @@ -1,2 +1,2 @@ -version 0.7.5 +version 0.7.6 language rust diff --git a/livekit/CHANGELOG.md b/livekit/CHANGELOG.md index 468cfc22f..fa844ed40 100644 --- a/livekit/CHANGELOG.md +++ b/livekit/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [0.7.6] - 2025-02-28 + +### Added + +- Add audio filter support + ## [0.7.5] - 2025-02-06 ### Fixed diff --git a/livekit/Cargo.toml b/livekit/Cargo.toml index 6040b010f..d3f612d19 100644 --- a/livekit/Cargo.toml +++ b/livekit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit" -version = "0.7.5" +version = "0.7.6" edition = "2021" license = "Apache-2.0" description = "Rust Client SDK for LiveKit" From 3e51cdad4b4133bbdd80774374505705855648b6 Mon Sep 17 00:00:00 2001 From: Daisuke Murase Date: Mon, 3 Mar 2025 15:49:40 -0800 Subject: [PATCH 155/274] Fix panic in Promise::try_result (#585) * fix crash on try_result * there was still the cases where panics occur --- Cargo.lock | 4 ++-- livekit-protocol/src/promise.rs | 22 ++++++++++++++++------ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 781cc6473..490040bd7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1605,7 +1605,7 @@ checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "livekit" -version = "0.7.5" +version = "0.7.6" dependencies = [ "chrono", "futures-util", @@ -1655,7 +1655,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.12.10" +version = "0.12.12" dependencies = [ "console-subscriber", "dashmap", diff --git a/livekit-protocol/src/promise.rs b/livekit-protocol/src/promise.rs index 475f32bc2..1736715da 100644 --- a/livekit-protocol/src/promise.rs +++ b/livekit-protocol/src/promise.rs @@ -12,12 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -use tokio::sync::{oneshot, Mutex}; +use tokio::sync::{oneshot, Mutex, RwLock}; pub struct Promise { tx: Mutex>>, rx: Mutex>>, - result: Mutex>, + result: RwLock>, } impl Promise { @@ -37,14 +37,24 @@ impl Promise { } pub async fn result(&self) -> T { + { + let result_read = self.result.read().await; + if let Some(result) = result_read.clone() { + return result; + } + } + let mut rx = self.rx.lock().await; - if rx.is_some() { - self.result.lock().await.replace(rx.take().unwrap().await.unwrap()); + if let Some(rx) = rx.take() { + let result = rx.await.unwrap(); + *self.result.write().await = Some(result.clone()); + result + } else { + self.result.read().await.clone().unwrap() } - self.result.lock().await.clone().unwrap() } pub fn try_result(&self) -> Option { - self.result.try_lock().unwrap().clone() + self.result.try_read().ok().and_then(|result| result.clone()) } } From 4e9a275de50e7cc5ca3b6ef491ed234f56d8dcb4 Mon Sep 17 00:00:00 2001 From: Daisuke Murase Date: Tue, 4 Mar 2025 09:29:26 -0800 Subject: [PATCH 156/274] add changesets (#587) --- .nanpa/plant-batch-dense.kdl | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .nanpa/plant-batch-dense.kdl diff --git a/.nanpa/plant-batch-dense.kdl b/.nanpa/plant-batch-dense.kdl new file mode 100644 index 000000000..e8e06f000 --- /dev/null +++ b/.nanpa/plant-batch-dense.kdl @@ -0,0 +1,2 @@ +patch type="fixed" package="livekit-protocol" "Fixed the crashing issue in Promise::try_result" +patch type="fixed" package="livekit-ffi" "Depends the latest version of livekit-protocol" From de91ada2e0f26f14ebf95414953cef110c5288fe Mon Sep 17 00:00:00 2001 From: "ilo-nanpa[bot]" <174912985+ilo-nanpa[bot]@users.noreply.github.com> Date: Tue, 4 Mar 2025 17:30:17 +0000 Subject: [PATCH 157/274] nanpa: bump --- .nanpa/plant-batch-dense.kdl | 2 -- Cargo.toml | 4 ++-- livekit-api/Cargo.toml | 2 +- livekit-ffi/.nanparc | 2 +- livekit-ffi/CHANGELOG.md | 6 ++++++ livekit-ffi/Cargo.toml | 2 +- livekit-protocol/.nanparc | 2 +- livekit-protocol/CHANGELOG.md | 6 ++++++ livekit-protocol/Cargo.toml | 2 +- 9 files changed, 19 insertions(+), 9 deletions(-) delete mode 100644 .nanpa/plant-batch-dense.kdl diff --git a/.nanpa/plant-batch-dense.kdl b/.nanpa/plant-batch-dense.kdl deleted file mode 100644 index e8e06f000..000000000 --- a/.nanpa/plant-batch-dense.kdl +++ /dev/null @@ -1,2 +0,0 @@ -patch type="fixed" package="livekit-protocol" "Fixed the crashing issue in Promise::try_result" -patch type="fixed" package="livekit-ffi" "Depends the latest version of livekit-protocol" diff --git a/Cargo.toml b/Cargo.toml index 49096a9bc..a3597e666 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,8 +19,8 @@ imgproc = { version = "0.3.12", path = "imgproc" } yuv-sys = { version = "0.3.7", path = "yuv-sys" } libwebrtc = { version = "0.3.10", path = "libwebrtc" } livekit-api = { version = "0.4.2", path = "livekit-api" } -livekit-ffi = { version = "0.12.12", path = "livekit-ffi" } -livekit-protocol = { version = "0.3.8", path = "livekit-protocol" } +livekit-ffi = { version = "0.12.13", path = "livekit-ffi" } +livekit-protocol = { version = "0.3.9", path = "livekit-protocol" } livekit-runtime = { version = "0.4.0", path = "livekit-runtime" } livekit = { version = "0.7.6", path = "livekit" } soxr-sys = { version = "0.1.0", path = "soxr-sys" } diff --git a/livekit-api/Cargo.toml b/livekit-api/Cargo.toml index 0545801c1..877e9ef05 100644 --- a/livekit-api/Cargo.toml +++ b/livekit-api/Cargo.toml @@ -65,7 +65,7 @@ rustls-tls-webpki-roots = [ __rustls-tls = ["tokio-tungstenite?/__rustls-tls", "reqwest?/__rustls"] [dependencies] -livekit-protocol = { path = "../livekit-protocol", version = "0.3.8" } +livekit-protocol = { path = "../livekit-protocol", version = "0.3.9" } thiserror = "1.0" serde = { version = "1.0", features = ["derive"] } sha2 = "0.10" diff --git a/livekit-ffi/.nanparc b/livekit-ffi/.nanparc index b88996568..2cbc25150 100644 --- a/livekit-ffi/.nanparc +++ b/livekit-ffi/.nanparc @@ -1,2 +1,2 @@ -version 0.12.12 +version 0.12.13 language rust diff --git a/livekit-ffi/CHANGELOG.md b/livekit-ffi/CHANGELOG.md index eadb6e648..d3c272d09 100644 --- a/livekit-ffi/CHANGELOG.md +++ b/livekit-ffi/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [0.12.13] - 2025-03-04 + +### Fixed + +- Depends the latest version of livekit-protocol + ## [0.12.12] - 2025-02-28 ### Added diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 7be90e9d1..bf5ce63b0 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.12.12" +version = "0.12.13" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" diff --git a/livekit-protocol/.nanparc b/livekit-protocol/.nanparc index 9ad0822e5..96df60bef 100644 --- a/livekit-protocol/.nanparc +++ b/livekit-protocol/.nanparc @@ -1,2 +1,2 @@ -version 0.3.8 +version 0.3.9 language rust diff --git a/livekit-protocol/CHANGELOG.md b/livekit-protocol/CHANGELOG.md index 223d1bc42..e532bf5df 100644 --- a/livekit-protocol/CHANGELOG.md +++ b/livekit-protocol/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [0.3.9] - 2025-03-04 + +### Fixed + +- Fixed the crashing issue in Promise::try_result + ## [0.3.8] - 2025-02-05 ### Fixed diff --git a/livekit-protocol/Cargo.toml b/livekit-protocol/Cargo.toml index 27e9d1351..875c28846 100644 --- a/livekit-protocol/Cargo.toml +++ b/livekit-protocol/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-protocol" -version = "0.3.8" +version = "0.3.9" edition = "2021" license = "Apache-2.0" description = "Livekit protocol and utilities for the Rust SDK" From 8e3e1b12d1d6fab47ce519fe447e73ea2f4c9017 Mon Sep 17 00:00:00 2001 From: Daisuke Murase Date: Tue, 4 Mar 2025 10:34:21 -0800 Subject: [PATCH 158/274] Fix packaging issue in livekit-ffi (#589) * fix a packaging issue of livekit-ffi * add a changeset --- Cargo.lock | 4 ++-- livekit-ffi/.nanpa/rival-fang-bride.kdl | 1 + livekit-ffi/Cargo.toml | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 livekit-ffi/.nanpa/rival-fang-bride.kdl diff --git a/Cargo.lock b/Cargo.lock index 490040bd7..8fe183c44 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1655,7 +1655,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.12.12" +version = "0.12.13" dependencies = [ "console-subscriber", "dashmap", @@ -1680,7 +1680,7 @@ dependencies = [ [[package]] name = "livekit-protocol" -version = "0.3.8" +version = "0.3.9" dependencies = [ "futures-util", "livekit-runtime", diff --git a/livekit-ffi/.nanpa/rival-fang-bride.kdl b/livekit-ffi/.nanpa/rival-fang-bride.kdl new file mode 100644 index 000000000..86dac442f --- /dev/null +++ b/livekit-ffi/.nanpa/rival-fang-bride.kdl @@ -0,0 +1 @@ +patch type="fixed" package="livekit-ffi" "Fixed a packaging issue" diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index bf5ce63b0..411a533dd 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -19,8 +19,8 @@ tracing = ["tokio/tracing", "console-subscriber"] [dependencies] livekit = { workspace = true } -soxr-sys = { path = "../soxr-sys" } -imgproc = { path = "../imgproc" } +soxr-sys = { workspace = true } +imgproc = { workspace = true } livekit-protocol = { workspace = true } tokio = { version = "1", features = ["full", "parking_lot"] } futures-util = { version = "0.3", default-features = false, features = ["sink"] } From 7e51858058add73e023071cd2a0993e915626452 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Tue, 4 Mar 2025 20:34:49 +0100 Subject: [PATCH 159/274] add AudioProcessingModule (#580) --- libwebrtc/src/lib.rs | 2 +- libwebrtc/src/native/apm.rs | 102 ++ libwebrtc/src/native/mod.rs | 1 + livekit-api/src/services/egress.rs | 1 + livekit-api/src/services/sip.rs | 1 + livekit-ffi/protocol/audio_frame.proto | 43 + livekit-ffi/protocol/ffi.proto | 12 + livekit-ffi/src/livekit.proto.rs | 91 +- livekit-ffi/src/server/mod.rs | 3 +- livekit-ffi/src/server/requests.rs | 125 ++- livekit-protocol/protocol | 2 +- livekit-protocol/src/livekit.rs | 312 +++++- livekit-protocol/src/livekit.serde.rs | 1432 ++++++++++++++++++++---- webrtc-sys/build.rs | 3 + webrtc-sys/compile_flags.txt | 1 + webrtc-sys/include/livekit/apm.h | 73 ++ webrtc-sys/src/apm.cpp | 49 + webrtc-sys/src/apm.rs | 53 + webrtc-sys/src/lib.rs | 1 + 19 files changed, 2032 insertions(+), 275 deletions(-) create mode 100644 libwebrtc/src/native/apm.rs create mode 100644 webrtc-sys/include/livekit/apm.h create mode 100644 webrtc-sys/src/apm.cpp create mode 100644 webrtc-sys/src/apm.rs diff --git a/libwebrtc/src/lib.rs b/libwebrtc/src/lib.rs index 919da36ff..f13be654d 100644 --- a/libwebrtc/src/lib.rs +++ b/libwebrtc/src/lib.rs @@ -66,7 +66,7 @@ pub mod video_track; pub mod native { pub use webrtc_sys::webrtc::ffi::create_random_uuid; - pub use crate::imp::{audio_resampler, frame_cryptor, yuv_helper}; + pub use crate::imp::{apm, audio_resampler, frame_cryptor, yuv_helper}; } #[cfg(target_os = "android")] diff --git a/libwebrtc/src/native/apm.rs b/libwebrtc/src/native/apm.rs new file mode 100644 index 000000000..5315210e9 --- /dev/null +++ b/libwebrtc/src/native/apm.rs @@ -0,0 +1,102 @@ +// Copyright 2023 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use cxx::UniquePtr; +use webrtc_sys::apm::ffi as sys_apm; + +use crate::{RtcError, RtcErrorType}; + +pub struct AudioProcessingModule { + sys_handle: UniquePtr, +} + +impl AudioProcessingModule { + pub fn new( + echo_canceller_enabled: bool, + gain_controller_enabled: bool, + high_pass_filter_enabled: bool, + noise_suppression_enabled: bool, + ) -> Self { + Self { + sys_handle: unsafe { + sys_apm::create_apm( + echo_canceller_enabled, + gain_controller_enabled, + high_pass_filter_enabled, + noise_suppression_enabled, + ) + }, + } + } + + pub fn process_stream( + &mut self, + data: &mut [i16], + sample_rate: i32, + num_channels: i32, + ) -> Result<(), RtcError> { + let samples_count = (sample_rate as usize / 100) * num_channels as usize; + assert_eq!(data.len(), samples_count, "slice must have 10ms worth of samples"); + + if unsafe { + // using the same slice for src and dst is safe + self.sys_handle.pin_mut().process_stream( + data.as_mut_ptr(), + data.len(), + data.as_mut_ptr(), + data.len(), + sample_rate, + num_channels, + ) + } == 0 + { + Ok(()) + } else { + Err(RtcError { + error_type: RtcErrorType::Internal, + message: "Failed to process stream".to_string(), + }) + } + } + + pub fn process_reverse_stream( + &mut self, + data: &mut [i16], + sample_rate: i32, + num_channels: i32, + ) -> Result<(), RtcError> { + let samples_count = (sample_rate as usize / 100) * num_channels as usize; + assert_eq!(data.len(), samples_count, "slice must have 10ms worth of samples"); + + if unsafe { + // using the same slice for src and dst is safe + self.sys_handle.pin_mut().process_reverse_stream( + data.as_mut_ptr(), + data.len(), + data.as_mut_ptr(), + data.len(), + sample_rate, + num_channels, + ) + } == 0 + { + Ok(()) + } else { + Err(RtcError { + error_type: RtcErrorType::Internal, + message: "Failed to process reverse stream".to_string(), + }) + } + } +} diff --git a/libwebrtc/src/native/mod.rs b/libwebrtc/src/native/mod.rs index 4abae953a..98f757174 100644 --- a/libwebrtc/src/native/mod.rs +++ b/libwebrtc/src/native/mod.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +pub mod apm; #[cfg(target_os = "android")] pub mod android; pub mod audio_resampler; diff --git a/livekit-api/src/services/egress.rs b/livekit-api/src/services/egress.rs index b28b3979e..7c2fd08ce 100644 --- a/livekit-api/src/services/egress.rs +++ b/livekit-api/src/services/egress.rs @@ -120,6 +120,7 @@ impl EgressClient { segment_outputs, image_outputs, output: None, // Deprecated + ..Default::default() }, self.base .auth_header(VideoGrants { room_record: true, ..Default::default() }, None)?, diff --git a/livekit-api/src/services/sip.rs b/livekit-api/src/services/sip.rs index 773afd534..01e4d431a 100644 --- a/livekit-api/src/services/sip.rs +++ b/livekit-api/src/services/sip.rs @@ -452,6 +452,7 @@ impl SIPClient { headers: Default::default(), include_headers: Default::default(), media_encryption: Default::default(), + ..Default::default() }, self.base.auth_header( Default::default(), diff --git a/livekit-ffi/protocol/audio_frame.proto b/livekit-ffi/protocol/audio_frame.proto index 3dec06fe3..ce3c5a93c 100644 --- a/livekit-ffi/protocol/audio_frame.proto +++ b/livekit-ffi/protocol/audio_frame.proto @@ -91,6 +91,42 @@ message RemixAndResampleResponse { required OwnedAudioFrameBuffer buffer = 1; } +// AEC + + +message NewApmRequest { + required bool echo_canceller_enabled = 1; + required bool gain_controller_enabled = 2; + required bool high_pass_filter_enabled = 3; + required bool noise_suppression_enabled = 4; +} +message NewApmResponse { + required OwnedApm apm = 1; +} + +message ApmProcessStreamRequest { + required uint64 apm_handle = 1; + required uint64 data_ptr = 2; // *mut i16 + required uint32 size = 3; // in bytes + required uint32 sample_rate = 4; + required uint32 num_channels = 5; +} + +message ApmProcessStreamResponse { + optional string error = 1; +} + +message ApmProcessReverseStreamRequest { + required uint64 apm_handle = 1; + required uint64 data_ptr = 2; // *mut i16 + required uint32 size = 3; // in bytes + required uint32 sample_rate = 4; + required uint32 num_channels = 5; +} + +message ApmProcessReverseStreamResponse { + optional string error = 1; +} // New resampler using SoX (much better quality) @@ -241,6 +277,13 @@ message OwnedAudioResampler { } +// +// AEC +// + +message OwnedApm { + required FfiOwnedHandle handle = 1; +} // // Sox AudioResampler diff --git a/livekit-ffi/protocol/ffi.proto b/livekit-ffi/protocol/ffi.proto index d94ae263b..d6e034c7a 100644 --- a/livekit-ffi/protocol/ffi.proto +++ b/livekit-ffi/protocol/ffi.proto @@ -122,6 +122,12 @@ message FfiRequest { // Audio Filter Plugin LoadAudioFilterPluginRequest load_audio_filter_plugin = 49; + + NewApmRequest new_apm = 50; + ApmProcessStreamRequest apm_process_stream = 51; + ApmProcessReverseStreamRequest apm_process_reverse_stream = 52; + + // NEXT_ID: 53 } } @@ -191,6 +197,12 @@ message FfiResponse { // Audio Filter Plugin LoadAudioFilterPluginResponse load_audio_filter_plugin = 48; + + NewApmResponse new_apm = 49; + ApmProcessStreamResponse apm_process_stream = 50; + ApmProcessReverseStreamResponse apm_process_reverse_stream = 51; + + // NEXT_ID: 52 } } diff --git a/livekit-ffi/src/livekit.proto.rs b/livekit-ffi/src/livekit.proto.rs index 650f7e3f5..214799ee7 100644 --- a/livekit-ffi/src/livekit.proto.rs +++ b/livekit-ffi/src/livekit.proto.rs @@ -1,4 +1,5 @@ // @generated +// This file is @generated by prost-build. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FrameCryptor { @@ -3554,6 +3555,70 @@ pub struct RemixAndResampleResponse { #[prost(message, required, tag="1")] pub buffer: OwnedAudioFrameBuffer, } +// AEC + +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct NewApmRequest { + #[prost(bool, required, tag="1")] + pub echo_canceller_enabled: bool, + #[prost(bool, required, tag="2")] + pub gain_controller_enabled: bool, + #[prost(bool, required, tag="3")] + pub high_pass_filter_enabled: bool, + #[prost(bool, required, tag="4")] + pub noise_suppression_enabled: bool, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct NewApmResponse { + #[prost(message, required, tag="1")] + pub apm: OwnedApm, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ApmProcessStreamRequest { + #[prost(uint64, required, tag="1")] + pub apm_handle: u64, + /// *mut i16 + #[prost(uint64, required, tag="2")] + pub data_ptr: u64, + /// in bytes + #[prost(uint32, required, tag="3")] + pub size: u32, + #[prost(uint32, required, tag="4")] + pub sample_rate: u32, + #[prost(uint32, required, tag="5")] + pub num_channels: u32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ApmProcessStreamResponse { + #[prost(string, optional, tag="1")] + pub error: ::core::option::Option<::prost::alloc::string::String>, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ApmProcessReverseStreamRequest { + #[prost(uint64, required, tag="1")] + pub apm_handle: u64, + /// *mut i16 + #[prost(uint64, required, tag="2")] + pub data_ptr: u64, + /// in bytes + #[prost(uint32, required, tag="3")] + pub size: u32, + #[prost(uint32, required, tag="4")] + pub sample_rate: u32, + #[prost(uint32, required, tag="5")] + pub num_channels: u32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ApmProcessReverseStreamResponse { + #[prost(string, optional, tag="1")] + pub error: ::core::option::Option<::prost::alloc::string::String>, +} // New resampler using SoX (much better quality) #[allow(clippy::derive_partial_eq_without_eq)] @@ -3746,6 +3811,16 @@ pub struct OwnedAudioResampler { pub info: AudioResamplerInfo, } // +// AEC +// + +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct OwnedApm { + #[prost(message, required, tag="1")] + pub handle: FfiOwnedHandle, +} +// // Sox AudioResampler // @@ -4075,7 +4150,7 @@ pub struct RpcMethodInvocationEvent { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiRequest { - #[prost(oneof="ffi_request::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 48, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 49")] + #[prost(oneof="ffi_request::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 48, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 49, 50, 51, 52")] pub message: ::core::option::Option, } /// Nested message and enum types in `FfiRequest`. @@ -4188,13 +4263,19 @@ pub mod ffi_request { /// Audio Filter Plugin #[prost(message, tag="49")] LoadAudioFilterPlugin(super::LoadAudioFilterPluginRequest), + #[prost(message, tag="50")] + NewApm(super::NewApmRequest), + #[prost(message, tag="51")] + ApmProcessStream(super::ApmProcessStreamRequest), + #[prost(message, tag="52")] + ApmProcessReverseStream(super::ApmProcessReverseStreamRequest), } } /// This is the output of livekit_ffi_request function. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiResponse { - #[prost(oneof="ffi_response::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 47, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 48")] + #[prost(oneof="ffi_response::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 47, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 48, 49, 50, 51")] pub message: ::core::option::Option, } /// Nested message and enum types in `FfiResponse`. @@ -4305,6 +4386,12 @@ pub mod ffi_response { /// Audio Filter Plugin #[prost(message, tag="48")] LoadAudioFilterPlugin(super::LoadAudioFilterPluginResponse), + #[prost(message, tag="49")] + NewApm(super::NewApmResponse), + #[prost(message, tag="50")] + ApmProcessStream(super::ApmProcessStreamResponse), + #[prost(message, tag="51")] + ApmProcessReverseStream(super::ApmProcessReverseStreamResponse), } } /// To minimize complexity, participant events are not included in the protocol. diff --git a/livekit-ffi/src/server/mod.rs b/livekit-ffi/src/server/mod.rs index ae39473ac..80177bbac 100644 --- a/livekit-ffi/src/server/mod.rs +++ b/livekit-ffi/src/server/mod.rs @@ -24,7 +24,7 @@ use std::{ use dashmap::{mapref::one::MappedRef, DashMap}; use downcast_rs::{impl_downcast, Downcast}; -use livekit::webrtc::{native::audio_resampler::AudioResampler, prelude::*}; +use livekit::webrtc::{native::apm::AudioProcessingModule, native::audio_resampler::AudioResampler, prelude::*}; use parking_lot::{deadlock, Mutex}; use tokio::{sync::oneshot, task::JoinHandle}; @@ -67,6 +67,7 @@ pub struct FfiDataBuffer { impl FfiHandle for FfiDataBuffer {} impl FfiHandle for Arc> {} +impl FfiHandle for Arc> {} impl FfiHandle for Arc> {} impl FfiHandle for AudioFrame<'static> {} impl FfiHandle for BoxVideoBuffer {} diff --git a/livekit-ffi/src/server/requests.rs b/livekit-ffi/src/server/requests.rs index 83f41c01c..45e0a712a 100644 --- a/livekit-ffi/src/server/requests.rs +++ b/livekit-ffi/src/server/requests.rs @@ -18,7 +18,7 @@ use colorcvt::cvtimpl; use livekit::{ prelude::*, register_audio_filter_plugin, - webrtc::{native::audio_resampler, prelude::*}, + webrtc::{native::apm, native::audio_resampler, prelude::*}, AudioFilterPlugin, }; use parking_lot::Mutex; @@ -857,6 +857,90 @@ fn on_flush_sox_resampler( } } +fn on_new_apm( + server: &'static FfiServer, + new_apm: proto::NewApmRequest, +) -> FfiResult { + let apm = apm::AudioProcessingModule::new( + new_apm.echo_canceller_enabled, + new_apm.gain_controller_enabled, + new_apm.high_pass_filter_enabled, + new_apm.noise_suppression_enabled, + ); + + let apm = Arc::new(Mutex::new(apm)); + let handle_id = server.next_id(); + server.store_handle(handle_id, apm); + + Ok(proto::NewApmResponse { + apm: proto::OwnedApm { handle: proto::FfiOwnedHandle { id: handle_id } }, + }) +} + +fn on_apm_process_stream( + server: &'static FfiServer, + request: proto::ApmProcessStreamRequest, +) -> FfiResult { + let aec = server + .retrieve_handle::>>(request.apm_handle)? + .clone(); + + // make sure data is aligned for i16 + if request.data_ptr as usize % std::mem::size_of::() != 0 { + return Ok(proto::ApmProcessStreamResponse { + error: Some("data_ptr must be aligned for i16".into()), + }); + } + + let mut aec = aec.lock(); + let data = unsafe { + slice::from_raw_parts_mut( + request.data_ptr as *mut i16, + request.size as usize / std::mem::size_of::(), + ) + }; + + if let Err(e) = + aec.process_stream(data, request.sample_rate as i32, request.num_channels as i32) + { + return Ok(proto::ApmProcessStreamResponse { error: Some(e.to_string()) }); + } + + Ok(proto::ApmProcessStreamResponse { error: None }) +} + +fn on_apm_process_reverse_stream( + server: &'static FfiServer, + request: proto::ApmProcessReverseStreamRequest, +) -> FfiResult { + let aec = server + .retrieve_handle::>>(request.apm_handle)? + .clone(); + + // make sure data is aligned for i16 + if request.data_ptr as usize % std::mem::size_of::() != 0 { + return Ok(proto::ApmProcessReverseStreamResponse { + error: Some("data_ptr must be aligned for i16".into()), + }); + } + + let mut aec = aec.lock(); + let data = unsafe { + slice::from_raw_parts_mut( + request.data_ptr as *mut i16, + request.size as usize / std::mem::size_of::(), + ) + }; + + if let Err(e) = + aec.process_reverse_stream(data, request.sample_rate as i32, request.num_channels as i32) + { + return Ok(proto::ApmProcessReverseStreamResponse { error: Some(e.to_string()) }); + } + + Ok(proto::ApmProcessReverseStreamResponse { error: None }) +} + fn on_perform_rpc( server: &'static FfiServer, request: proto::PerformRpcRequest, @@ -866,6 +950,19 @@ fn on_perform_rpc( return ffi_participant.perform_rpc(server, request); } +fn on_load_audio_filter_plugin( + _server: &'static FfiServer, + request: proto::LoadAudioFilterPluginRequest, +) -> FfiResult { + let deps: Vec<_> = request.dependencies.iter().map(|d| d).collect(); + let plugin = AudioFilterPlugin::new_with_dependencies(&request.plugin_path, deps) + .map_err(|e| FfiError::InvalidRequest(format!("plugin error: {}", e).into()))?; + + register_audio_filter_plugin(request.module_id, plugin); + + Ok(proto::LoadAudioFilterPluginResponse { error: None }) +} + fn on_register_rpc_method( server: &'static FfiServer, request: proto::RegisterRpcMethodRequest, @@ -923,19 +1020,6 @@ fn on_set_data_channel_buffered_amount_low_threshold( )) } -fn on_load_audio_filter_plugin( - _server: &'static FfiServer, - request: proto::LoadAudioFilterPluginRequest, -) -> FfiResult { - let deps: Vec<_> = request.dependencies.iter().map(|d| d).collect(); - let plugin = AudioFilterPlugin::new_with_dependencies(&request.plugin_path, deps) - .map_err(|e| FfiError::InvalidRequest(format!("plugin error: {}", e).into()))?; - - register_audio_filter_plugin(request.module_id, plugin); - - Ok(proto::LoadAudioFilterPluginResponse { error: None }) -} - fn on_set_track_subscription_permissions( server: &'static FfiServer, set_permissions: proto::SetTrackSubscriptionPermissionsRequest, @@ -1080,6 +1164,19 @@ pub fn handle_request( server, flush_soxr, )?) } + proto::ffi_request::Message::NewApm(new_apm) => { + proto::ffi_response::Message::NewApm(on_new_apm(server, new_apm)?) + } + + proto::ffi_request::Message::ApmProcessStream(request) => { + proto::ffi_response::Message::ApmProcessStream(on_apm_process_stream(server, request)?) + } + + proto::ffi_request::Message::ApmProcessReverseStream(request) => { + proto::ffi_response::Message::ApmProcessReverseStream(on_apm_process_reverse_stream( + server, request, + )?) + } proto::ffi_request::Message::PerformRpc(request) => { proto::ffi_response::Message::PerformRpc(on_perform_rpc(server, request)?) } diff --git a/livekit-protocol/protocol b/livekit-protocol/protocol index 2e0a35efa..7d8d588f7 160000 --- a/livekit-protocol/protocol +++ b/livekit-protocol/protocol @@ -1 +1 @@ -Subproject commit 2e0a35efa9382b8b3eabaaf6c867c3f487431dd5 +Subproject commit 7d8d588f7509f169fd6b72817dfecf25c31414e8 diff --git a/livekit-protocol/src/livekit.rs b/livekit-protocol/src/livekit.rs index c1f96e776..6097e1704 100644 --- a/livekit-protocol/src/livekit.rs +++ b/livekit-protocol/src/livekit.rs @@ -498,6 +498,8 @@ pub struct TrackInfo { pub version: ::core::option::Option, #[prost(enumeration="AudioTrackFeature", repeated, tag="19")] pub audio_features: ::prost::alloc::vec::Vec, + #[prost(enumeration="BackupCodecPolicy", tag="20")] + pub backup_codec_policy: i32, } /// provide information about available spatial layers #[allow(clippy::derive_partial_eq_without_eq)] @@ -1361,6 +1363,35 @@ impl ImageCodec { } } } +/// Policy for publisher to handle subscribers that are unable to support the primary codec of a track +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum BackupCodecPolicy { + /// default behavior, regress to backup codec and all subscribers will receive the backup codec + Regression = 0, + /// encoding/send the primary and backup codec simultaneously + Simulcast = 1, +} +impl BackupCodecPolicy { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + BackupCodecPolicy::Regression => "REGRESSION", + BackupCodecPolicy::Simulcast => "SIMULCAST", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "REGRESSION" => Some(Self::Regression), + "SIMULCAST" => Some(Self::Simulcast), + _ => None, + } + } +} #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] pub enum TrackType { @@ -1708,6 +1739,9 @@ pub struct RoomCompositeEgressRequest { /// (default false) #[prost(bool, tag="3")] pub audio_only: bool, + /// only applies to audio_only egress (default DEFAULT_MIXING) + #[prost(enumeration="AudioMixing", tag="15")] + pub audio_mixing: i32, /// (default false) #[prost(bool, tag="4")] pub video_only: bool, @@ -2591,6 +2625,38 @@ impl StreamProtocol { } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] +pub enum AudioMixing { + /// all users are mixed together + DefaultMixing = 0, + /// agent audio in the left channel, all other audio in the right channel + DualChannelAgent = 1, + /// each new audio track alternates between left and right channels + DualChannelAlternate = 2, +} +impl AudioMixing { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + AudioMixing::DefaultMixing => "DEFAULT_MIXING", + AudioMixing::DualChannelAgent => "DUAL_CHANNEL_AGENT", + AudioMixing::DualChannelAlternate => "DUAL_CHANNEL_ALTERNATE", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "DEFAULT_MIXING" => Some(Self::DefaultMixing), + "DUAL_CHANNEL_AGENT" => Some(Self::DualChannelAgent), + "DUAL_CHANNEL_ALTERNATE" => Some(Self::DualChannelAlternate), + _ => None, + } + } +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] pub enum EncodingOptionsPreset { /// 1280x720, 30fps, 3000kpbs, H.264_MAIN / OPUS H264720p30 = 0, @@ -2903,6 +2969,8 @@ pub struct AddTrackRequest { /// if not specified, server will infer it from track source to bundle camera/microphone, screenshare/audio together #[prost(string, tag="15")] pub stream: ::prost::alloc::string::String, + #[prost(enumeration="BackupCodecPolicy", tag="16")] + pub backup_codec_policy: i32, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -3711,6 +3779,7 @@ pub struct JobTermination { pub enum JobType { JtRoom = 0, JtPublisher = 1, + JtParticipant = 2, } impl JobType { /// String value of the enum field names used in the ProtoBuf definition. @@ -3721,6 +3790,7 @@ impl JobType { match self { JobType::JtRoom => "JT_ROOM", JobType::JtPublisher => "JT_PUBLISHER", + JobType::JtParticipant => "JT_PARTICIPANT", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -3728,6 +3798,7 @@ impl JobType { match value { "JT_ROOM" => Some(Self::JtRoom), "JT_PUBLISHER" => Some(Self::JtPublisher), + "JT_PARTICIPANT" => Some(Self::JtParticipant), _ => None, } } @@ -4540,6 +4611,15 @@ pub struct WebhookEvent { #[prost(int32, tag="11")] pub num_dropped: i32, } +/// SIPStatus is returned as an error detail in CreateSIPParticipant. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SipStatus { + #[prost(enumeration="SipStatusCode", tag="1")] + pub code: i32, + #[prost(string, tag="2")] + pub status: ::prost::alloc::string::String, +} #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CreateSipTrunkRequest { @@ -5013,6 +5093,30 @@ pub struct DeleteSipDispatchRuleRequest { #[prost(string, tag="1")] pub sip_dispatch_rule_id: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SipOutboundConfig { + /// SIP server address + #[prost(string, tag="1")] + pub hostname: ::prost::alloc::string::String, + /// SIP Transport used for outbound call. + #[prost(enumeration="SipTransport", tag="2")] + pub transport: i32, + /// Username and password used to authenticate with SIP server. + /// May be empty to have no authentication. + #[prost(string, tag="3")] + pub auth_username: ::prost::alloc::string::String, + #[prost(string, tag="4")] + pub auth_password: ::prost::alloc::string::String, + /// Map SIP X-* headers from 200 OK to SIP participant attributes. + /// Keys are the names of X-* headers and values are the names of attributes they will be mapped to. + #[prost(map="string, string", tag="5")] + pub headers_to_attributes: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + /// Map LiveKit attributes to SIP X-* headers when sending BYE or REFER requests. + /// Keys are the names of attributes and values are the names of X-* headers they will be mapped to. + #[prost(map="string, string", tag="6")] + pub attributes_to_headers: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, +} /// A SIP Participant is a singular SIP session connected to a LiveKit room via /// a SIP Trunk into a SIP DispatchRule #[allow(clippy::derive_partial_eq_without_eq)] @@ -5021,6 +5125,8 @@ pub struct CreateSipParticipantRequest { /// What SIP Trunk should be used to dial the user #[prost(string, tag="1")] pub sip_trunk_id: ::prost::alloc::string::String, + #[prost(message, optional, tag="20")] + pub trunk: ::core::option::Option, /// What number should be dialed via SIP #[prost(string, tag="2")] pub sip_call_to: ::prost::alloc::string::String, @@ -5076,9 +5182,13 @@ pub struct CreateSipParticipantRequest { /// Enable voice isolation for the callee. #[prost(bool, tag="14")] pub krisp_enabled: bool, - /// NEXT ID: 19 #[prost(enumeration="SipMediaEncryption", tag="18")] pub media_encryption: i32, + /// Wait for the answer for the call before returning. + /// + /// NEXT ID: 21 + #[prost(bool, tag="19")] + pub wait_until_answered: bool, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -5115,6 +5225,10 @@ pub struct SipCallInfo { pub call_id: ::prost::alloc::string::String, #[prost(string, tag="2")] pub trunk_id: ::prost::alloc::string::String, + #[prost(string, tag="16")] + pub dispatch_rule_id: ::prost::alloc::string::String, + #[prost(string, tag="17")] + pub region: ::prost::alloc::string::String, #[prost(string, tag="3")] pub room_name: ::prost::alloc::string::String, /// ID of the current/previous room published to @@ -5122,26 +5236,43 @@ pub struct SipCallInfo { pub room_id: ::prost::alloc::string::String, #[prost(string, tag="5")] pub participant_identity: ::prost::alloc::string::String, + #[prost(map="string, string", tag="18")] + pub participant_attributes: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, #[prost(message, optional, tag="6")] pub from_uri: ::core::option::Option, #[prost(message, optional, tag="7")] pub to_uri: ::core::option::Option, - #[prost(enumeration="SipFeature", repeated, tag="14")] - pub enabled_features: ::prost::alloc::vec::Vec, - #[prost(enumeration="SipCallDirection", tag="15")] - pub call_direction: i32, - #[prost(enumeration="SipCallStatus", tag="8")] - pub call_status: i32, + #[deprecated] #[prost(int64, tag="9")] pub created_at: i64, + #[deprecated] #[prost(int64, tag="10")] pub started_at: i64, + #[deprecated] #[prost(int64, tag="11")] pub ended_at: i64, + #[prost(enumeration="SipFeature", repeated, tag="14")] + pub enabled_features: ::prost::alloc::vec::Vec, + #[prost(enumeration="SipCallDirection", tag="15")] + pub call_direction: i32, + #[prost(enumeration="SipCallStatus", tag="8")] + pub call_status: i32, + #[prost(int64, tag="22")] + pub created_at_ns: i64, + #[prost(int64, tag="23")] + pub started_at_ns: i64, + #[prost(int64, tag="24")] + pub ended_at_ns: i64, #[prost(enumeration="DisconnectReason", tag="12")] pub disconnect_reason: i32, #[prost(string, tag="13")] pub error: ::prost::alloc::string::String, + #[prost(message, optional, tag="19")] + pub call_status_code: ::core::option::Option, + #[prost(string, tag="20")] + pub audio_codec: ::prost::alloc::string::String, + #[prost(string, tag="21")] + pub media_encryption: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -5159,6 +5290,173 @@ pub struct SipUri { } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] +pub enum SipStatusCode { + SipStatusUnknown = 0, + SipStatusTrying = 100, + SipStatusRinging = 180, + SipStatusCallIsForwarded = 181, + SipStatusQueued = 182, + SipStatusSessionProgress = 183, + SipStatusOk = 200, + SipStatusAccepted = 202, + SipStatusMovedPermanently = 301, + SipStatusMovedTemporarily = 302, + SipStatusUseProxy = 305, + SipStatusBadRequest = 400, + SipStatusUnauthorized = 401, + SipStatusPaymentRequired = 402, + SipStatusForbidden = 403, + SipStatusNotfound = 404, + SipStatusMethodNotAllowed = 405, + SipStatusNotAcceptable = 406, + SipStatusProxyAuthRequired = 407, + SipStatusRequestTimeout = 408, + SipStatusConflict = 409, + SipStatusGone = 410, + SipStatusRequestEntityTooLarge = 413, + SipStatusRequestUriTooLong = 414, + SipStatusUnsupportedMediaType = 415, + SipStatusRequestedRangeNotSatisfiable = 416, + SipStatusBadExtension = 420, + SipStatusExtensionRequired = 421, + SipStatusIntervalTooBrief = 423, + SipStatusTemporarilyUnavailable = 480, + SipStatusCallTransactionDoesNotExists = 481, + SipStatusLoopDetected = 482, + SipStatusTooManyHops = 483, + SipStatusAddressIncomplete = 484, + SipStatusAmbiguous = 485, + SipStatusBusyHere = 486, + SipStatusRequestTerminated = 487, + SipStatusNotAcceptableHere = 488, + SipStatusInternalServerError = 500, + SipStatusNotImplemented = 501, + SipStatusBadGateway = 502, + SipStatusServiceUnavailable = 503, + SipStatusGatewayTimeout = 504, + SipStatusVersionNotSupported = 505, + SipStatusMessageTooLarge = 513, + SipStatusGlobalBusyEverywhere = 600, + SipStatusGlobalDecline = 603, + SipStatusGlobalDoesNotExistAnywhere = 604, + SipStatusGlobalNotAcceptable = 606, +} +impl SipStatusCode { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + SipStatusCode::SipStatusUnknown => "SIP_STATUS_UNKNOWN", + SipStatusCode::SipStatusTrying => "SIP_STATUS_TRYING", + SipStatusCode::SipStatusRinging => "SIP_STATUS_RINGING", + SipStatusCode::SipStatusCallIsForwarded => "SIP_STATUS_CALL_IS_FORWARDED", + SipStatusCode::SipStatusQueued => "SIP_STATUS_QUEUED", + SipStatusCode::SipStatusSessionProgress => "SIP_STATUS_SESSION_PROGRESS", + SipStatusCode::SipStatusOk => "SIP_STATUS_OK", + SipStatusCode::SipStatusAccepted => "SIP_STATUS_ACCEPTED", + SipStatusCode::SipStatusMovedPermanently => "SIP_STATUS_MOVED_PERMANENTLY", + SipStatusCode::SipStatusMovedTemporarily => "SIP_STATUS_MOVED_TEMPORARILY", + SipStatusCode::SipStatusUseProxy => "SIP_STATUS_USE_PROXY", + SipStatusCode::SipStatusBadRequest => "SIP_STATUS_BAD_REQUEST", + SipStatusCode::SipStatusUnauthorized => "SIP_STATUS_UNAUTHORIZED", + SipStatusCode::SipStatusPaymentRequired => "SIP_STATUS_PAYMENT_REQUIRED", + SipStatusCode::SipStatusForbidden => "SIP_STATUS_FORBIDDEN", + SipStatusCode::SipStatusNotfound => "SIP_STATUS_NOTFOUND", + SipStatusCode::SipStatusMethodNotAllowed => "SIP_STATUS_METHOD_NOT_ALLOWED", + SipStatusCode::SipStatusNotAcceptable => "SIP_STATUS_NOT_ACCEPTABLE", + SipStatusCode::SipStatusProxyAuthRequired => "SIP_STATUS_PROXY_AUTH_REQUIRED", + SipStatusCode::SipStatusRequestTimeout => "SIP_STATUS_REQUEST_TIMEOUT", + SipStatusCode::SipStatusConflict => "SIP_STATUS_CONFLICT", + SipStatusCode::SipStatusGone => "SIP_STATUS_GONE", + SipStatusCode::SipStatusRequestEntityTooLarge => "SIP_STATUS_REQUEST_ENTITY_TOO_LARGE", + SipStatusCode::SipStatusRequestUriTooLong => "SIP_STATUS_REQUEST_URI_TOO_LONG", + SipStatusCode::SipStatusUnsupportedMediaType => "SIP_STATUS_UNSUPPORTED_MEDIA_TYPE", + SipStatusCode::SipStatusRequestedRangeNotSatisfiable => "SIP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE", + SipStatusCode::SipStatusBadExtension => "SIP_STATUS_BAD_EXTENSION", + SipStatusCode::SipStatusExtensionRequired => "SIP_STATUS_EXTENSION_REQUIRED", + SipStatusCode::SipStatusIntervalTooBrief => "SIP_STATUS_INTERVAL_TOO_BRIEF", + SipStatusCode::SipStatusTemporarilyUnavailable => "SIP_STATUS_TEMPORARILY_UNAVAILABLE", + SipStatusCode::SipStatusCallTransactionDoesNotExists => "SIP_STATUS_CALL_TRANSACTION_DOES_NOT_EXISTS", + SipStatusCode::SipStatusLoopDetected => "SIP_STATUS_LOOP_DETECTED", + SipStatusCode::SipStatusTooManyHops => "SIP_STATUS_TOO_MANY_HOPS", + SipStatusCode::SipStatusAddressIncomplete => "SIP_STATUS_ADDRESS_INCOMPLETE", + SipStatusCode::SipStatusAmbiguous => "SIP_STATUS_AMBIGUOUS", + SipStatusCode::SipStatusBusyHere => "SIP_STATUS_BUSY_HERE", + SipStatusCode::SipStatusRequestTerminated => "SIP_STATUS_REQUEST_TERMINATED", + SipStatusCode::SipStatusNotAcceptableHere => "SIP_STATUS_NOT_ACCEPTABLE_HERE", + SipStatusCode::SipStatusInternalServerError => "SIP_STATUS_INTERNAL_SERVER_ERROR", + SipStatusCode::SipStatusNotImplemented => "SIP_STATUS_NOT_IMPLEMENTED", + SipStatusCode::SipStatusBadGateway => "SIP_STATUS_BAD_GATEWAY", + SipStatusCode::SipStatusServiceUnavailable => "SIP_STATUS_SERVICE_UNAVAILABLE", + SipStatusCode::SipStatusGatewayTimeout => "SIP_STATUS_GATEWAY_TIMEOUT", + SipStatusCode::SipStatusVersionNotSupported => "SIP_STATUS_VERSION_NOT_SUPPORTED", + SipStatusCode::SipStatusMessageTooLarge => "SIP_STATUS_MESSAGE_TOO_LARGE", + SipStatusCode::SipStatusGlobalBusyEverywhere => "SIP_STATUS_GLOBAL_BUSY_EVERYWHERE", + SipStatusCode::SipStatusGlobalDecline => "SIP_STATUS_GLOBAL_DECLINE", + SipStatusCode::SipStatusGlobalDoesNotExistAnywhere => "SIP_STATUS_GLOBAL_DOES_NOT_EXIST_ANYWHERE", + SipStatusCode::SipStatusGlobalNotAcceptable => "SIP_STATUS_GLOBAL_NOT_ACCEPTABLE", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "SIP_STATUS_UNKNOWN" => Some(Self::SipStatusUnknown), + "SIP_STATUS_TRYING" => Some(Self::SipStatusTrying), + "SIP_STATUS_RINGING" => Some(Self::SipStatusRinging), + "SIP_STATUS_CALL_IS_FORWARDED" => Some(Self::SipStatusCallIsForwarded), + "SIP_STATUS_QUEUED" => Some(Self::SipStatusQueued), + "SIP_STATUS_SESSION_PROGRESS" => Some(Self::SipStatusSessionProgress), + "SIP_STATUS_OK" => Some(Self::SipStatusOk), + "SIP_STATUS_ACCEPTED" => Some(Self::SipStatusAccepted), + "SIP_STATUS_MOVED_PERMANENTLY" => Some(Self::SipStatusMovedPermanently), + "SIP_STATUS_MOVED_TEMPORARILY" => Some(Self::SipStatusMovedTemporarily), + "SIP_STATUS_USE_PROXY" => Some(Self::SipStatusUseProxy), + "SIP_STATUS_BAD_REQUEST" => Some(Self::SipStatusBadRequest), + "SIP_STATUS_UNAUTHORIZED" => Some(Self::SipStatusUnauthorized), + "SIP_STATUS_PAYMENT_REQUIRED" => Some(Self::SipStatusPaymentRequired), + "SIP_STATUS_FORBIDDEN" => Some(Self::SipStatusForbidden), + "SIP_STATUS_NOTFOUND" => Some(Self::SipStatusNotfound), + "SIP_STATUS_METHOD_NOT_ALLOWED" => Some(Self::SipStatusMethodNotAllowed), + "SIP_STATUS_NOT_ACCEPTABLE" => Some(Self::SipStatusNotAcceptable), + "SIP_STATUS_PROXY_AUTH_REQUIRED" => Some(Self::SipStatusProxyAuthRequired), + "SIP_STATUS_REQUEST_TIMEOUT" => Some(Self::SipStatusRequestTimeout), + "SIP_STATUS_CONFLICT" => Some(Self::SipStatusConflict), + "SIP_STATUS_GONE" => Some(Self::SipStatusGone), + "SIP_STATUS_REQUEST_ENTITY_TOO_LARGE" => Some(Self::SipStatusRequestEntityTooLarge), + "SIP_STATUS_REQUEST_URI_TOO_LONG" => Some(Self::SipStatusRequestUriTooLong), + "SIP_STATUS_UNSUPPORTED_MEDIA_TYPE" => Some(Self::SipStatusUnsupportedMediaType), + "SIP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE" => Some(Self::SipStatusRequestedRangeNotSatisfiable), + "SIP_STATUS_BAD_EXTENSION" => Some(Self::SipStatusBadExtension), + "SIP_STATUS_EXTENSION_REQUIRED" => Some(Self::SipStatusExtensionRequired), + "SIP_STATUS_INTERVAL_TOO_BRIEF" => Some(Self::SipStatusIntervalTooBrief), + "SIP_STATUS_TEMPORARILY_UNAVAILABLE" => Some(Self::SipStatusTemporarilyUnavailable), + "SIP_STATUS_CALL_TRANSACTION_DOES_NOT_EXISTS" => Some(Self::SipStatusCallTransactionDoesNotExists), + "SIP_STATUS_LOOP_DETECTED" => Some(Self::SipStatusLoopDetected), + "SIP_STATUS_TOO_MANY_HOPS" => Some(Self::SipStatusTooManyHops), + "SIP_STATUS_ADDRESS_INCOMPLETE" => Some(Self::SipStatusAddressIncomplete), + "SIP_STATUS_AMBIGUOUS" => Some(Self::SipStatusAmbiguous), + "SIP_STATUS_BUSY_HERE" => Some(Self::SipStatusBusyHere), + "SIP_STATUS_REQUEST_TERMINATED" => Some(Self::SipStatusRequestTerminated), + "SIP_STATUS_NOT_ACCEPTABLE_HERE" => Some(Self::SipStatusNotAcceptableHere), + "SIP_STATUS_INTERNAL_SERVER_ERROR" => Some(Self::SipStatusInternalServerError), + "SIP_STATUS_NOT_IMPLEMENTED" => Some(Self::SipStatusNotImplemented), + "SIP_STATUS_BAD_GATEWAY" => Some(Self::SipStatusBadGateway), + "SIP_STATUS_SERVICE_UNAVAILABLE" => Some(Self::SipStatusServiceUnavailable), + "SIP_STATUS_GATEWAY_TIMEOUT" => Some(Self::SipStatusGatewayTimeout), + "SIP_STATUS_VERSION_NOT_SUPPORTED" => Some(Self::SipStatusVersionNotSupported), + "SIP_STATUS_MESSAGE_TOO_LARGE" => Some(Self::SipStatusMessageTooLarge), + "SIP_STATUS_GLOBAL_BUSY_EVERYWHERE" => Some(Self::SipStatusGlobalBusyEverywhere), + "SIP_STATUS_GLOBAL_DECLINE" => Some(Self::SipStatusGlobalDecline), + "SIP_STATUS_GLOBAL_DOES_NOT_EXIST_ANYWHERE" => Some(Self::SipStatusGlobalDoesNotExistAnywhere), + "SIP_STATUS_GLOBAL_NOT_ACCEPTABLE" => Some(Self::SipStatusGlobalNotAcceptable), + _ => None, + } + } +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] pub enum SipTransport { Auto = 0, Udp = 1, diff --git a/livekit-protocol/src/livekit.serde.rs b/livekit-protocol/src/livekit.serde.rs index 7dc86d65a..817bccf24 100644 --- a/livekit-protocol/src/livekit.serde.rs +++ b/livekit-protocol/src/livekit.serde.rs @@ -147,6 +147,9 @@ impl serde::Serialize for AddTrackRequest { if !self.stream.is_empty() { len += 1; } + if self.backup_codec_policy != 0 { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.AddTrackRequest", len)?; if !self.cid.is_empty() { struct_ser.serialize_field("cid", &self.cid)?; @@ -199,6 +202,11 @@ impl serde::Serialize for AddTrackRequest { if !self.stream.is_empty() { struct_ser.serialize_field("stream", &self.stream)?; } + if self.backup_codec_policy != 0 { + let v = BackupCodecPolicy::try_from(self.backup_codec_policy) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.backup_codec_policy)))?; + struct_ser.serialize_field("backupCodecPolicy", &v)?; + } struct_ser.end() } } @@ -227,6 +235,8 @@ impl<'de> serde::Deserialize<'de> for AddTrackRequest { "disableRed", "encryption", "stream", + "backup_codec_policy", + "backupCodecPolicy", ]; #[allow(clippy::enum_variant_names)] @@ -246,6 +256,7 @@ impl<'de> serde::Deserialize<'de> for AddTrackRequest { DisableRed, Encryption, Stream, + BackupCodecPolicy, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -283,6 +294,7 @@ impl<'de> serde::Deserialize<'de> for AddTrackRequest { "disableRed" | "disable_red" => Ok(GeneratedField::DisableRed), "encryption" => Ok(GeneratedField::Encryption), "stream" => Ok(GeneratedField::Stream), + "backupCodecPolicy" | "backup_codec_policy" => Ok(GeneratedField::BackupCodecPolicy), _ => Ok(GeneratedField::__SkipField__), } } @@ -317,6 +329,7 @@ impl<'de> serde::Deserialize<'de> for AddTrackRequest { let mut disable_red__ = None; let mut encryption__ = None; let mut stream__ = None; + let mut backup_codec_policy__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::Cid => { @@ -413,6 +426,12 @@ impl<'de> serde::Deserialize<'de> for AddTrackRequest { } stream__ = Some(map_.next_value()?); } + GeneratedField::BackupCodecPolicy => { + if backup_codec_policy__.is_some() { + return Err(serde::de::Error::duplicate_field("backupCodecPolicy")); + } + backup_codec_policy__ = Some(map_.next_value::()? as i32); + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -434,6 +453,7 @@ impl<'de> serde::Deserialize<'de> for AddTrackRequest { disable_red: disable_red__.unwrap_or_default(), encryption: encryption__.unwrap_or_default(), stream: stream__.unwrap_or_default(), + backup_codec_policy: backup_codec_policy__.unwrap_or_default(), }) } } @@ -981,6 +1001,80 @@ impl<'de> serde::Deserialize<'de> for AudioCodec { deserializer.deserialize_any(GeneratedVisitor) } } +impl serde::Serialize for AudioMixing { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + let variant = match self { + Self::DefaultMixing => "DEFAULT_MIXING", + Self::DualChannelAgent => "DUAL_CHANNEL_AGENT", + Self::DualChannelAlternate => "DUAL_CHANNEL_ALTERNATE", + }; + serializer.serialize_str(variant) + } +} +impl<'de> serde::Deserialize<'de> for AudioMixing { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "DEFAULT_MIXING", + "DUAL_CHANNEL_AGENT", + "DUAL_CHANNEL_ALTERNATE", + ]; + + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = AudioMixing; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + fn visit_i64(self, v: i64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self) + }) + } + + fn visit_u64(self, v: u64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(v), &self) + }) + } + + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "DEFAULT_MIXING" => Ok(AudioMixing::DefaultMixing), + "DUAL_CHANNEL_AGENT" => Ok(AudioMixing::DualChannelAgent), + "DUAL_CHANNEL_ALTERNATE" => Ok(AudioMixing::DualChannelAlternate), + _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), + } + } + } + deserializer.deserialize_any(GeneratedVisitor) + } +} impl serde::Serialize for AudioTrackFeature { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -1837,36 +1931,34 @@ impl<'de> serde::Deserialize<'de> for AzureBlobUpload { deserializer.deserialize_struct("livekit.AzureBlobUpload", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for CandidateProtocol { +impl serde::Serialize for BackupCodecPolicy { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where S: serde::Serializer, { let variant = match self { - Self::Udp => "UDP", - Self::Tcp => "TCP", - Self::Tls => "TLS", + Self::Regression => "REGRESSION", + Self::Simulcast => "SIMULCAST", }; serializer.serialize_str(variant) } } -impl<'de> serde::Deserialize<'de> for CandidateProtocol { +impl<'de> serde::Deserialize<'de> for BackupCodecPolicy { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "UDP", - "TCP", - "TLS", + "REGRESSION", + "SIMULCAST", ]; struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = CandidateProtocol; + type Value = BackupCodecPolicy; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(formatter, "expected one of: {:?}", &FIELDS) @@ -1901,9 +1993,8 @@ impl<'de> serde::Deserialize<'de> for CandidateProtocol { E: serde::de::Error, { match value { - "UDP" => Ok(CandidateProtocol::Udp), - "TCP" => Ok(CandidateProtocol::Tcp), - "TLS" => Ok(CandidateProtocol::Tls), + "REGRESSION" => Ok(BackupCodecPolicy::Regression), + "SIMULCAST" => Ok(BackupCodecPolicy::Simulcast), _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), } } @@ -1911,225 +2002,299 @@ impl<'de> serde::Deserialize<'de> for CandidateProtocol { deserializer.deserialize_any(GeneratedVisitor) } } -impl serde::Serialize for ChatMessage { - #[allow(deprecated)] - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::Serializer, - { - use serde::ser::SerializeStruct; - let mut len = 0; - if !self.id.is_empty() { - len += 1; - } - if self.timestamp != 0 { - len += 1; - } - if self.edit_timestamp.is_some() { - len += 1; - } - if !self.message.is_empty() { - len += 1; - } - if self.deleted { - len += 1; - } - if self.generated { - len += 1; - } - let mut struct_ser = serializer.serialize_struct("livekit.ChatMessage", len)?; - if !self.id.is_empty() { - struct_ser.serialize_field("id", &self.id)?; - } - if self.timestamp != 0 { - #[allow(clippy::needless_borrow)] - #[allow(clippy::needless_borrows_for_generic_args)] - struct_ser.serialize_field("timestamp", ToString::to_string(&self.timestamp).as_str())?; - } - if let Some(v) = self.edit_timestamp.as_ref() { - #[allow(clippy::needless_borrow)] - #[allow(clippy::needless_borrows_for_generic_args)] - struct_ser.serialize_field("editTimestamp", ToString::to_string(&v).as_str())?; - } - if !self.message.is_empty() { - struct_ser.serialize_field("message", &self.message)?; - } - if self.deleted { - struct_ser.serialize_field("deleted", &self.deleted)?; - } - if self.generated { - struct_ser.serialize_field("generated", &self.generated)?; - } - struct_ser.end() - } -} -impl<'de> serde::Deserialize<'de> for ChatMessage { - #[allow(deprecated)] - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - const FIELDS: &[&str] = &[ - "id", - "timestamp", - "edit_timestamp", - "editTimestamp", - "message", - "deleted", - "generated", - ]; - - #[allow(clippy::enum_variant_names)] - enum GeneratedField { - Id, - Timestamp, - EditTimestamp, - Message, - Deleted, - Generated, - __SkipField__, - } - impl<'de> serde::Deserialize<'de> for GeneratedField { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - struct GeneratedVisitor; - - impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = GeneratedField; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(formatter, "expected one of: {:?}", &FIELDS) - } - - #[allow(unused_variables)] - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - match value { - "id" => Ok(GeneratedField::Id), - "timestamp" => Ok(GeneratedField::Timestamp), - "editTimestamp" | "edit_timestamp" => Ok(GeneratedField::EditTimestamp), - "message" => Ok(GeneratedField::Message), - "deleted" => Ok(GeneratedField::Deleted), - "generated" => Ok(GeneratedField::Generated), - _ => Ok(GeneratedField::__SkipField__), - } - } - } - deserializer.deserialize_identifier(GeneratedVisitor) - } - } - struct GeneratedVisitor; - impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = ChatMessage; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.ChatMessage") - } - - fn visit_map(self, mut map_: V) -> std::result::Result - where - V: serde::de::MapAccess<'de>, - { - let mut id__ = None; - let mut timestamp__ = None; - let mut edit_timestamp__ = None; - let mut message__ = None; - let mut deleted__ = None; - let mut generated__ = None; - while let Some(k) = map_.next_key()? { - match k { - GeneratedField::Id => { - if id__.is_some() { - return Err(serde::de::Error::duplicate_field("id")); - } - id__ = Some(map_.next_value()?); - } - GeneratedField::Timestamp => { - if timestamp__.is_some() { - return Err(serde::de::Error::duplicate_field("timestamp")); - } - timestamp__ = - Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; - } - GeneratedField::EditTimestamp => { - if edit_timestamp__.is_some() { - return Err(serde::de::Error::duplicate_field("editTimestamp")); - } - edit_timestamp__ = - map_.next_value::<::std::option::Option<::pbjson::private::NumberDeserialize<_>>>()?.map(|x| x.0) - ; - } - GeneratedField::Message => { - if message__.is_some() { - return Err(serde::de::Error::duplicate_field("message")); - } - message__ = Some(map_.next_value()?); - } - GeneratedField::Deleted => { - if deleted__.is_some() { - return Err(serde::de::Error::duplicate_field("deleted")); - } - deleted__ = Some(map_.next_value()?); - } - GeneratedField::Generated => { - if generated__.is_some() { - return Err(serde::de::Error::duplicate_field("generated")); - } - generated__ = Some(map_.next_value()?); - } - GeneratedField::__SkipField__ => { - let _ = map_.next_value::()?; - } - } - } - Ok(ChatMessage { - id: id__.unwrap_or_default(), - timestamp: timestamp__.unwrap_or_default(), - edit_timestamp: edit_timestamp__, - message: message__.unwrap_or_default(), - deleted: deleted__.unwrap_or_default(), - generated: generated__.unwrap_or_default(), - }) - } - } - deserializer.deserialize_struct("livekit.ChatMessage", FIELDS, GeneratedVisitor) - } -} -impl serde::Serialize for ClientConfigSetting { +impl serde::Serialize for CandidateProtocol { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where S: serde::Serializer, { let variant = match self { - Self::Unset => "UNSET", - Self::Disabled => "DISABLED", - Self::Enabled => "ENABLED", + Self::Udp => "UDP", + Self::Tcp => "TCP", + Self::Tls => "TLS", }; serializer.serialize_str(variant) } } -impl<'de> serde::Deserialize<'de> for ClientConfigSetting { +impl<'de> serde::Deserialize<'de> for CandidateProtocol { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "UNSET", - "DISABLED", - "ENABLED", + "UDP", + "TCP", + "TLS", ]; struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = ClientConfigSetting; + type Value = CandidateProtocol; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + fn visit_i64(self, v: i64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self) + }) + } + + fn visit_u64(self, v: u64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(v), &self) + }) + } + + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "UDP" => Ok(CandidateProtocol::Udp), + "TCP" => Ok(CandidateProtocol::Tcp), + "TLS" => Ok(CandidateProtocol::Tls), + _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), + } + } + } + deserializer.deserialize_any(GeneratedVisitor) + } +} +impl serde::Serialize for ChatMessage { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.id.is_empty() { + len += 1; + } + if self.timestamp != 0 { + len += 1; + } + if self.edit_timestamp.is_some() { + len += 1; + } + if !self.message.is_empty() { + len += 1; + } + if self.deleted { + len += 1; + } + if self.generated { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.ChatMessage", len)?; + if !self.id.is_empty() { + struct_ser.serialize_field("id", &self.id)?; + } + if self.timestamp != 0 { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("timestamp", ToString::to_string(&self.timestamp).as_str())?; + } + if let Some(v) = self.edit_timestamp.as_ref() { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("editTimestamp", ToString::to_string(&v).as_str())?; + } + if !self.message.is_empty() { + struct_ser.serialize_field("message", &self.message)?; + } + if self.deleted { + struct_ser.serialize_field("deleted", &self.deleted)?; + } + if self.generated { + struct_ser.serialize_field("generated", &self.generated)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for ChatMessage { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "id", + "timestamp", + "edit_timestamp", + "editTimestamp", + "message", + "deleted", + "generated", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Id, + Timestamp, + EditTimestamp, + Message, + Deleted, + Generated, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "id" => Ok(GeneratedField::Id), + "timestamp" => Ok(GeneratedField::Timestamp), + "editTimestamp" | "edit_timestamp" => Ok(GeneratedField::EditTimestamp), + "message" => Ok(GeneratedField::Message), + "deleted" => Ok(GeneratedField::Deleted), + "generated" => Ok(GeneratedField::Generated), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = ChatMessage; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.ChatMessage") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut id__ = None; + let mut timestamp__ = None; + let mut edit_timestamp__ = None; + let mut message__ = None; + let mut deleted__ = None; + let mut generated__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Id => { + if id__.is_some() { + return Err(serde::de::Error::duplicate_field("id")); + } + id__ = Some(map_.next_value()?); + } + GeneratedField::Timestamp => { + if timestamp__.is_some() { + return Err(serde::de::Error::duplicate_field("timestamp")); + } + timestamp__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::EditTimestamp => { + if edit_timestamp__.is_some() { + return Err(serde::de::Error::duplicate_field("editTimestamp")); + } + edit_timestamp__ = + map_.next_value::<::std::option::Option<::pbjson::private::NumberDeserialize<_>>>()?.map(|x| x.0) + ; + } + GeneratedField::Message => { + if message__.is_some() { + return Err(serde::de::Error::duplicate_field("message")); + } + message__ = Some(map_.next_value()?); + } + GeneratedField::Deleted => { + if deleted__.is_some() { + return Err(serde::de::Error::duplicate_field("deleted")); + } + deleted__ = Some(map_.next_value()?); + } + GeneratedField::Generated => { + if generated__.is_some() { + return Err(serde::de::Error::duplicate_field("generated")); + } + generated__ = Some(map_.next_value()?); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(ChatMessage { + id: id__.unwrap_or_default(), + timestamp: timestamp__.unwrap_or_default(), + edit_timestamp: edit_timestamp__, + message: message__.unwrap_or_default(), + deleted: deleted__.unwrap_or_default(), + generated: generated__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.ChatMessage", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for ClientConfigSetting { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + let variant = match self { + Self::Unset => "UNSET", + Self::Disabled => "DISABLED", + Self::Enabled => "ENABLED", + }; + serializer.serialize_str(variant) + } +} +impl<'de> serde::Deserialize<'de> for ClientConfigSetting { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "UNSET", + "DISABLED", + "ENABLED", + ]; + + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = ClientConfigSetting; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(formatter, "expected one of: {:?}", &FIELDS) @@ -4318,6 +4483,9 @@ impl serde::Serialize for CreateSipParticipantRequest { if !self.sip_trunk_id.is_empty() { len += 1; } + if self.trunk.is_some() { + len += 1; + } if !self.sip_call_to.is_empty() { len += 1; } @@ -4369,10 +4537,16 @@ impl serde::Serialize for CreateSipParticipantRequest { if self.media_encryption != 0 { len += 1; } + if self.wait_until_answered { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.CreateSIPParticipantRequest", len)?; if !self.sip_trunk_id.is_empty() { struct_ser.serialize_field("sipTrunkId", &self.sip_trunk_id)?; } + if let Some(v) = self.trunk.as_ref() { + struct_ser.serialize_field("trunk", v)?; + } if !self.sip_call_to.is_empty() { struct_ser.serialize_field("sipCallTo", &self.sip_call_to)?; } @@ -4428,6 +4602,9 @@ impl serde::Serialize for CreateSipParticipantRequest { .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.media_encryption)))?; struct_ser.serialize_field("mediaEncryption", &v)?; } + if self.wait_until_answered { + struct_ser.serialize_field("waitUntilAnswered", &self.wait_until_answered)?; + } struct_ser.end() } } @@ -4440,6 +4617,7 @@ impl<'de> serde::Deserialize<'de> for CreateSipParticipantRequest { const FIELDS: &[&str] = &[ "sip_trunk_id", "sipTrunkId", + "trunk", "sip_call_to", "sipCallTo", "sip_number", @@ -4472,11 +4650,14 @@ impl<'de> serde::Deserialize<'de> for CreateSipParticipantRequest { "krispEnabled", "media_encryption", "mediaEncryption", + "wait_until_answered", + "waitUntilAnswered", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { SipTrunkId, + Trunk, SipCallTo, SipNumber, RoomName, @@ -4494,6 +4675,7 @@ impl<'de> serde::Deserialize<'de> for CreateSipParticipantRequest { MaxCallDuration, KrispEnabled, MediaEncryption, + WaitUntilAnswered, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -4517,6 +4699,7 @@ impl<'de> serde::Deserialize<'de> for CreateSipParticipantRequest { { match value { "sipTrunkId" | "sip_trunk_id" => Ok(GeneratedField::SipTrunkId), + "trunk" => Ok(GeneratedField::Trunk), "sipCallTo" | "sip_call_to" => Ok(GeneratedField::SipCallTo), "sipNumber" | "sip_number" => Ok(GeneratedField::SipNumber), "roomName" | "room_name" => Ok(GeneratedField::RoomName), @@ -4534,6 +4717,7 @@ impl<'de> serde::Deserialize<'de> for CreateSipParticipantRequest { "maxCallDuration" | "max_call_duration" => Ok(GeneratedField::MaxCallDuration), "krispEnabled" | "krisp_enabled" => Ok(GeneratedField::KrispEnabled), "mediaEncryption" | "media_encryption" => Ok(GeneratedField::MediaEncryption), + "waitUntilAnswered" | "wait_until_answered" => Ok(GeneratedField::WaitUntilAnswered), _ => Ok(GeneratedField::__SkipField__), } } @@ -4554,6 +4738,7 @@ impl<'de> serde::Deserialize<'de> for CreateSipParticipantRequest { V: serde::de::MapAccess<'de>, { let mut sip_trunk_id__ = None; + let mut trunk__ = None; let mut sip_call_to__ = None; let mut sip_number__ = None; let mut room_name__ = None; @@ -4571,6 +4756,7 @@ impl<'de> serde::Deserialize<'de> for CreateSipParticipantRequest { let mut max_call_duration__ = None; let mut krisp_enabled__ = None; let mut media_encryption__ = None; + let mut wait_until_answered__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::SipTrunkId => { @@ -4579,6 +4765,12 @@ impl<'de> serde::Deserialize<'de> for CreateSipParticipantRequest { } sip_trunk_id__ = Some(map_.next_value()?); } + GeneratedField::Trunk => { + if trunk__.is_some() { + return Err(serde::de::Error::duplicate_field("trunk")); + } + trunk__ = map_.next_value()?; + } GeneratedField::SipCallTo => { if sip_call_to__.is_some() { return Err(serde::de::Error::duplicate_field("sipCallTo")); @@ -4685,6 +4877,12 @@ impl<'de> serde::Deserialize<'de> for CreateSipParticipantRequest { } media_encryption__ = Some(map_.next_value::()? as i32); } + GeneratedField::WaitUntilAnswered => { + if wait_until_answered__.is_some() { + return Err(serde::de::Error::duplicate_field("waitUntilAnswered")); + } + wait_until_answered__ = Some(map_.next_value()?); + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -4692,6 +4890,7 @@ impl<'de> serde::Deserialize<'de> for CreateSipParticipantRequest { } Ok(CreateSipParticipantRequest { sip_trunk_id: sip_trunk_id__.unwrap_or_default(), + trunk: trunk__, sip_call_to: sip_call_to__.unwrap_or_default(), sip_number: sip_number__.unwrap_or_default(), room_name: room_name__.unwrap_or_default(), @@ -4709,6 +4908,7 @@ impl<'de> serde::Deserialize<'de> for CreateSipParticipantRequest { max_call_duration: max_call_duration__, krisp_enabled: krisp_enabled__.unwrap_or_default(), media_encryption: media_encryption__.unwrap_or_default(), + wait_until_answered: wait_until_answered__.unwrap_or_default(), }) } } @@ -13209,6 +13409,7 @@ impl serde::Serialize for JobType { let variant = match self { Self::JtRoom => "JT_ROOM", Self::JtPublisher => "JT_PUBLISHER", + Self::JtParticipant => "JT_PARTICIPANT", }; serializer.serialize_str(variant) } @@ -13222,6 +13423,7 @@ impl<'de> serde::Deserialize<'de> for JobType { const FIELDS: &[&str] = &[ "JT_ROOM", "JT_PUBLISHER", + "JT_PARTICIPANT", ]; struct GeneratedVisitor; @@ -13264,6 +13466,7 @@ impl<'de> serde::Deserialize<'de> for JobType { match value { "JT_ROOM" => Ok(JobType::JtRoom), "JT_PUBLISHER" => Ok(JobType::JtPublisher), + "JT_PARTICIPANT" => Ok(JobType::JtParticipant), _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), } } @@ -21911,6 +22114,9 @@ impl serde::Serialize for RoomCompositeEgressRequest { if self.audio_only { len += 1; } + if self.audio_mixing != 0 { + len += 1; + } if self.video_only { len += 1; } @@ -21945,6 +22151,11 @@ impl serde::Serialize for RoomCompositeEgressRequest { if self.audio_only { struct_ser.serialize_field("audioOnly", &self.audio_only)?; } + if self.audio_mixing != 0 { + let v = AudioMixing::try_from(self.audio_mixing) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.audio_mixing)))?; + struct_ser.serialize_field("audioMixing", &v)?; + } if self.video_only { struct_ser.serialize_field("videoOnly", &self.video_only)?; } @@ -22003,6 +22214,8 @@ impl<'de> serde::Deserialize<'de> for RoomCompositeEgressRequest { "layout", "audio_only", "audioOnly", + "audio_mixing", + "audioMixing", "video_only", "videoOnly", "custom_base_url", @@ -22027,6 +22240,7 @@ impl<'de> serde::Deserialize<'de> for RoomCompositeEgressRequest { RoomName, Layout, AudioOnly, + AudioMixing, VideoOnly, CustomBaseUrl, FileOutputs, @@ -22063,6 +22277,7 @@ impl<'de> serde::Deserialize<'de> for RoomCompositeEgressRequest { "roomName" | "room_name" => Ok(GeneratedField::RoomName), "layout" => Ok(GeneratedField::Layout), "audioOnly" | "audio_only" => Ok(GeneratedField::AudioOnly), + "audioMixing" | "audio_mixing" => Ok(GeneratedField::AudioMixing), "videoOnly" | "video_only" => Ok(GeneratedField::VideoOnly), "customBaseUrl" | "custom_base_url" => Ok(GeneratedField::CustomBaseUrl), "fileOutputs" | "file_outputs" => Ok(GeneratedField::FileOutputs), @@ -22096,6 +22311,7 @@ impl<'de> serde::Deserialize<'de> for RoomCompositeEgressRequest { let mut room_name__ = None; let mut layout__ = None; let mut audio_only__ = None; + let mut audio_mixing__ = None; let mut video_only__ = None; let mut custom_base_url__ = None; let mut file_outputs__ = None; @@ -22124,6 +22340,12 @@ impl<'de> serde::Deserialize<'de> for RoomCompositeEgressRequest { } audio_only__ = Some(map_.next_value()?); } + GeneratedField::AudioMixing => { + if audio_mixing__.is_some() { + return Err(serde::de::Error::duplicate_field("audioMixing")); + } + audio_mixing__ = Some(map_.next_value::()? as i32); + } GeneratedField::VideoOnly => { if video_only__.is_some() { return Err(serde::de::Error::duplicate_field("videoOnly")); @@ -22203,6 +22425,7 @@ impl<'de> serde::Deserialize<'de> for RoomCompositeEgressRequest { room_name: room_name__.unwrap_or_default(), layout: layout__.unwrap_or_default(), audio_only: audio_only__.unwrap_or_default(), + audio_mixing: audio_mixing__.unwrap_or_default(), video_only: video_only__.unwrap_or_default(), custom_base_url: custom_base_url__.unwrap_or_default(), file_outputs: file_outputs__.unwrap_or_default(), @@ -23684,6 +23907,12 @@ impl serde::Serialize for SipCallInfo { if !self.trunk_id.is_empty() { len += 1; } + if !self.dispatch_rule_id.is_empty() { + len += 1; + } + if !self.region.is_empty() { + len += 1; + } if !self.room_name.is_empty() { len += 1; } @@ -23693,12 +23922,24 @@ impl serde::Serialize for SipCallInfo { if !self.participant_identity.is_empty() { len += 1; } + if !self.participant_attributes.is_empty() { + len += 1; + } if self.from_uri.is_some() { len += 1; } if self.to_uri.is_some() { len += 1; } + if self.created_at != 0 { + len += 1; + } + if self.started_at != 0 { + len += 1; + } + if self.ended_at != 0 { + len += 1; + } if !self.enabled_features.is_empty() { len += 1; } @@ -23708,13 +23949,13 @@ impl serde::Serialize for SipCallInfo { if self.call_status != 0 { len += 1; } - if self.created_at != 0 { + if self.created_at_ns != 0 { len += 1; } - if self.started_at != 0 { + if self.started_at_ns != 0 { len += 1; } - if self.ended_at != 0 { + if self.ended_at_ns != 0 { len += 1; } if self.disconnect_reason != 0 { @@ -23723,6 +23964,15 @@ impl serde::Serialize for SipCallInfo { if !self.error.is_empty() { len += 1; } + if self.call_status_code.is_some() { + len += 1; + } + if !self.audio_codec.is_empty() { + len += 1; + } + if !self.media_encryption.is_empty() { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.SIPCallInfo", len)?; if !self.call_id.is_empty() { struct_ser.serialize_field("callId", &self.call_id)?; @@ -23730,6 +23980,12 @@ impl serde::Serialize for SipCallInfo { if !self.trunk_id.is_empty() { struct_ser.serialize_field("trunkId", &self.trunk_id)?; } + if !self.dispatch_rule_id.is_empty() { + struct_ser.serialize_field("dispatchRuleId", &self.dispatch_rule_id)?; + } + if !self.region.is_empty() { + struct_ser.serialize_field("region", &self.region)?; + } if !self.room_name.is_empty() { struct_ser.serialize_field("roomName", &self.room_name)?; } @@ -23739,12 +23995,30 @@ impl serde::Serialize for SipCallInfo { if !self.participant_identity.is_empty() { struct_ser.serialize_field("participantIdentity", &self.participant_identity)?; } + if !self.participant_attributes.is_empty() { + struct_ser.serialize_field("participantAttributes", &self.participant_attributes)?; + } if let Some(v) = self.from_uri.as_ref() { struct_ser.serialize_field("fromUri", v)?; } if let Some(v) = self.to_uri.as_ref() { struct_ser.serialize_field("toUri", v)?; } + if self.created_at != 0 { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("createdAt", ToString::to_string(&self.created_at).as_str())?; + } + if self.started_at != 0 { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("startedAt", ToString::to_string(&self.started_at).as_str())?; + } + if self.ended_at != 0 { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("endedAt", ToString::to_string(&self.ended_at).as_str())?; + } if !self.enabled_features.is_empty() { let v = self.enabled_features.iter().cloned().map(|v| { SipFeature::try_from(v) @@ -23762,20 +24036,20 @@ impl serde::Serialize for SipCallInfo { .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.call_status)))?; struct_ser.serialize_field("callStatus", &v)?; } - if self.created_at != 0 { + if self.created_at_ns != 0 { #[allow(clippy::needless_borrow)] #[allow(clippy::needless_borrows_for_generic_args)] - struct_ser.serialize_field("createdAt", ToString::to_string(&self.created_at).as_str())?; + struct_ser.serialize_field("createdAtNs", ToString::to_string(&self.created_at_ns).as_str())?; } - if self.started_at != 0 { + if self.started_at_ns != 0 { #[allow(clippy::needless_borrow)] #[allow(clippy::needless_borrows_for_generic_args)] - struct_ser.serialize_field("startedAt", ToString::to_string(&self.started_at).as_str())?; + struct_ser.serialize_field("startedAtNs", ToString::to_string(&self.started_at_ns).as_str())?; } - if self.ended_at != 0 { + if self.ended_at_ns != 0 { #[allow(clippy::needless_borrow)] #[allow(clippy::needless_borrows_for_generic_args)] - struct_ser.serialize_field("endedAt", ToString::to_string(&self.ended_at).as_str())?; + struct_ser.serialize_field("endedAtNs", ToString::to_string(&self.ended_at_ns).as_str())?; } if self.disconnect_reason != 0 { let v = DisconnectReason::try_from(self.disconnect_reason) @@ -23785,6 +24059,15 @@ impl serde::Serialize for SipCallInfo { if !self.error.is_empty() { struct_ser.serialize_field("error", &self.error)?; } + if let Some(v) = self.call_status_code.as_ref() { + struct_ser.serialize_field("callStatusCode", v)?; + } + if !self.audio_codec.is_empty() { + struct_ser.serialize_field("audioCodec", &self.audio_codec)?; + } + if !self.media_encryption.is_empty() { + struct_ser.serialize_field("mediaEncryption", &self.media_encryption)?; + } struct_ser.end() } } @@ -23799,50 +24082,76 @@ impl<'de> serde::Deserialize<'de> for SipCallInfo { "callId", "trunk_id", "trunkId", + "dispatch_rule_id", + "dispatchRuleId", + "region", "room_name", "roomName", "room_id", "roomId", "participant_identity", "participantIdentity", + "participant_attributes", + "participantAttributes", "from_uri", "fromUri", "to_uri", "toUri", - "enabled_features", - "enabledFeatures", - "call_direction", - "callDirection", - "call_status", - "callStatus", "created_at", "createdAt", "started_at", "startedAt", "ended_at", "endedAt", + "enabled_features", + "enabledFeatures", + "call_direction", + "callDirection", + "call_status", + "callStatus", + "created_at_ns", + "createdAtNs", + "started_at_ns", + "startedAtNs", + "ended_at_ns", + "endedAtNs", "disconnect_reason", "disconnectReason", "error", + "call_status_code", + "callStatusCode", + "audio_codec", + "audioCodec", + "media_encryption", + "mediaEncryption", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { CallId, TrunkId, + DispatchRuleId, + Region, RoomName, RoomId, ParticipantIdentity, + ParticipantAttributes, FromUri, ToUri, - EnabledFeatures, - CallDirection, - CallStatus, CreatedAt, StartedAt, EndedAt, + EnabledFeatures, + CallDirection, + CallStatus, + CreatedAtNs, + StartedAtNs, + EndedAtNs, DisconnectReason, Error, + CallStatusCode, + AudioCodec, + MediaEncryption, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -23867,19 +24176,28 @@ impl<'de> serde::Deserialize<'de> for SipCallInfo { match value { "callId" | "call_id" => Ok(GeneratedField::CallId), "trunkId" | "trunk_id" => Ok(GeneratedField::TrunkId), + "dispatchRuleId" | "dispatch_rule_id" => Ok(GeneratedField::DispatchRuleId), + "region" => Ok(GeneratedField::Region), "roomName" | "room_name" => Ok(GeneratedField::RoomName), "roomId" | "room_id" => Ok(GeneratedField::RoomId), "participantIdentity" | "participant_identity" => Ok(GeneratedField::ParticipantIdentity), + "participantAttributes" | "participant_attributes" => Ok(GeneratedField::ParticipantAttributes), "fromUri" | "from_uri" => Ok(GeneratedField::FromUri), "toUri" | "to_uri" => Ok(GeneratedField::ToUri), - "enabledFeatures" | "enabled_features" => Ok(GeneratedField::EnabledFeatures), - "callDirection" | "call_direction" => Ok(GeneratedField::CallDirection), - "callStatus" | "call_status" => Ok(GeneratedField::CallStatus), "createdAt" | "created_at" => Ok(GeneratedField::CreatedAt), "startedAt" | "started_at" => Ok(GeneratedField::StartedAt), "endedAt" | "ended_at" => Ok(GeneratedField::EndedAt), + "enabledFeatures" | "enabled_features" => Ok(GeneratedField::EnabledFeatures), + "callDirection" | "call_direction" => Ok(GeneratedField::CallDirection), + "callStatus" | "call_status" => Ok(GeneratedField::CallStatus), + "createdAtNs" | "created_at_ns" => Ok(GeneratedField::CreatedAtNs), + "startedAtNs" | "started_at_ns" => Ok(GeneratedField::StartedAtNs), + "endedAtNs" | "ended_at_ns" => Ok(GeneratedField::EndedAtNs), "disconnectReason" | "disconnect_reason" => Ok(GeneratedField::DisconnectReason), "error" => Ok(GeneratedField::Error), + "callStatusCode" | "call_status_code" => Ok(GeneratedField::CallStatusCode), + "audioCodec" | "audio_codec" => Ok(GeneratedField::AudioCodec), + "mediaEncryption" | "media_encryption" => Ok(GeneratedField::MediaEncryption), _ => Ok(GeneratedField::__SkipField__), } } @@ -23901,19 +24219,28 @@ impl<'de> serde::Deserialize<'de> for SipCallInfo { { let mut call_id__ = None; let mut trunk_id__ = None; + let mut dispatch_rule_id__ = None; + let mut region__ = None; let mut room_name__ = None; let mut room_id__ = None; let mut participant_identity__ = None; + let mut participant_attributes__ = None; let mut from_uri__ = None; let mut to_uri__ = None; - let mut enabled_features__ = None; - let mut call_direction__ = None; - let mut call_status__ = None; let mut created_at__ = None; let mut started_at__ = None; let mut ended_at__ = None; + let mut enabled_features__ = None; + let mut call_direction__ = None; + let mut call_status__ = None; + let mut created_at_ns__ = None; + let mut started_at_ns__ = None; + let mut ended_at_ns__ = None; let mut disconnect_reason__ = None; let mut error__ = None; + let mut call_status_code__ = None; + let mut audio_codec__ = None; + let mut media_encryption__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::CallId => { @@ -23928,6 +24255,18 @@ impl<'de> serde::Deserialize<'de> for SipCallInfo { } trunk_id__ = Some(map_.next_value()?); } + GeneratedField::DispatchRuleId => { + if dispatch_rule_id__.is_some() { + return Err(serde::de::Error::duplicate_field("dispatchRuleId")); + } + dispatch_rule_id__ = Some(map_.next_value()?); + } + GeneratedField::Region => { + if region__.is_some() { + return Err(serde::de::Error::duplicate_field("region")); + } + region__ = Some(map_.next_value()?); + } GeneratedField::RoomName => { if room_name__.is_some() { return Err(serde::de::Error::duplicate_field("roomName")); @@ -23946,6 +24285,14 @@ impl<'de> serde::Deserialize<'de> for SipCallInfo { } participant_identity__ = Some(map_.next_value()?); } + GeneratedField::ParticipantAttributes => { + if participant_attributes__.is_some() { + return Err(serde::de::Error::duplicate_field("participantAttributes")); + } + participant_attributes__ = Some( + map_.next_value::>()? + ); + } GeneratedField::FromUri => { if from_uri__.is_some() { return Err(serde::de::Error::duplicate_field("fromUri")); @@ -23958,6 +24305,30 @@ impl<'de> serde::Deserialize<'de> for SipCallInfo { } to_uri__ = map_.next_value()?; } + GeneratedField::CreatedAt => { + if created_at__.is_some() { + return Err(serde::de::Error::duplicate_field("createdAt")); + } + created_at__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::StartedAt => { + if started_at__.is_some() { + return Err(serde::de::Error::duplicate_field("startedAt")); + } + started_at__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::EndedAt => { + if ended_at__.is_some() { + return Err(serde::de::Error::duplicate_field("endedAt")); + } + ended_at__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } GeneratedField::EnabledFeatures => { if enabled_features__.is_some() { return Err(serde::de::Error::duplicate_field("enabledFeatures")); @@ -23976,27 +24347,27 @@ impl<'de> serde::Deserialize<'de> for SipCallInfo { } call_status__ = Some(map_.next_value::()? as i32); } - GeneratedField::CreatedAt => { - if created_at__.is_some() { - return Err(serde::de::Error::duplicate_field("createdAt")); + GeneratedField::CreatedAtNs => { + if created_at_ns__.is_some() { + return Err(serde::de::Error::duplicate_field("createdAtNs")); } - created_at__ = + created_at_ns__ = Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) ; } - GeneratedField::StartedAt => { - if started_at__.is_some() { - return Err(serde::de::Error::duplicate_field("startedAt")); + GeneratedField::StartedAtNs => { + if started_at_ns__.is_some() { + return Err(serde::de::Error::duplicate_field("startedAtNs")); } - started_at__ = + started_at_ns__ = Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) ; } - GeneratedField::EndedAt => { - if ended_at__.is_some() { - return Err(serde::de::Error::duplicate_field("endedAt")); + GeneratedField::EndedAtNs => { + if ended_at_ns__.is_some() { + return Err(serde::de::Error::duplicate_field("endedAtNs")); } - ended_at__ = + ended_at_ns__ = Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) ; } @@ -24012,6 +24383,24 @@ impl<'de> serde::Deserialize<'de> for SipCallInfo { } error__ = Some(map_.next_value()?); } + GeneratedField::CallStatusCode => { + if call_status_code__.is_some() { + return Err(serde::de::Error::duplicate_field("callStatusCode")); + } + call_status_code__ = map_.next_value()?; + } + GeneratedField::AudioCodec => { + if audio_codec__.is_some() { + return Err(serde::de::Error::duplicate_field("audioCodec")); + } + audio_codec__ = Some(map_.next_value()?); + } + GeneratedField::MediaEncryption => { + if media_encryption__.is_some() { + return Err(serde::de::Error::duplicate_field("mediaEncryption")); + } + media_encryption__ = Some(map_.next_value()?); + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -24020,19 +24409,28 @@ impl<'de> serde::Deserialize<'de> for SipCallInfo { Ok(SipCallInfo { call_id: call_id__.unwrap_or_default(), trunk_id: trunk_id__.unwrap_or_default(), + dispatch_rule_id: dispatch_rule_id__.unwrap_or_default(), + region: region__.unwrap_or_default(), room_name: room_name__.unwrap_or_default(), room_id: room_id__.unwrap_or_default(), participant_identity: participant_identity__.unwrap_or_default(), + participant_attributes: participant_attributes__.unwrap_or_default(), from_uri: from_uri__, to_uri: to_uri__, - enabled_features: enabled_features__.unwrap_or_default(), - call_direction: call_direction__.unwrap_or_default(), - call_status: call_status__.unwrap_or_default(), created_at: created_at__.unwrap_or_default(), started_at: started_at__.unwrap_or_default(), ended_at: ended_at__.unwrap_or_default(), + enabled_features: enabled_features__.unwrap_or_default(), + call_direction: call_direction__.unwrap_or_default(), + call_status: call_status__.unwrap_or_default(), + created_at_ns: created_at_ns__.unwrap_or_default(), + started_at_ns: started_at_ns__.unwrap_or_default(), + ended_at_ns: ended_at_ns__.unwrap_or_default(), disconnect_reason: disconnect_reason__.unwrap_or_default(), error: error__.unwrap_or_default(), + call_status_code: call_status_code__, + audio_codec: audio_codec__.unwrap_or_default(), + media_encryption: media_encryption__.unwrap_or_default(), }) } } @@ -25489,6 +25887,196 @@ impl<'de> serde::Deserialize<'de> for SipMediaEncryption { deserializer.deserialize_any(GeneratedVisitor) } } +impl serde::Serialize for SipOutboundConfig { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.hostname.is_empty() { + len += 1; + } + if self.transport != 0 { + len += 1; + } + if !self.auth_username.is_empty() { + len += 1; + } + if !self.auth_password.is_empty() { + len += 1; + } + if !self.headers_to_attributes.is_empty() { + len += 1; + } + if !self.attributes_to_headers.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.SIPOutboundConfig", len)?; + if !self.hostname.is_empty() { + struct_ser.serialize_field("hostname", &self.hostname)?; + } + if self.transport != 0 { + let v = SipTransport::try_from(self.transport) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.transport)))?; + struct_ser.serialize_field("transport", &v)?; + } + if !self.auth_username.is_empty() { + struct_ser.serialize_field("authUsername", &self.auth_username)?; + } + if !self.auth_password.is_empty() { + struct_ser.serialize_field("authPassword", &self.auth_password)?; + } + if !self.headers_to_attributes.is_empty() { + struct_ser.serialize_field("headersToAttributes", &self.headers_to_attributes)?; + } + if !self.attributes_to_headers.is_empty() { + struct_ser.serialize_field("attributesToHeaders", &self.attributes_to_headers)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for SipOutboundConfig { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "hostname", + "transport", + "auth_username", + "authUsername", + "auth_password", + "authPassword", + "headers_to_attributes", + "headersToAttributes", + "attributes_to_headers", + "attributesToHeaders", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Hostname, + Transport, + AuthUsername, + AuthPassword, + HeadersToAttributes, + AttributesToHeaders, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "hostname" => Ok(GeneratedField::Hostname), + "transport" => Ok(GeneratedField::Transport), + "authUsername" | "auth_username" => Ok(GeneratedField::AuthUsername), + "authPassword" | "auth_password" => Ok(GeneratedField::AuthPassword), + "headersToAttributes" | "headers_to_attributes" => Ok(GeneratedField::HeadersToAttributes), + "attributesToHeaders" | "attributes_to_headers" => Ok(GeneratedField::AttributesToHeaders), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = SipOutboundConfig; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.SIPOutboundConfig") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut hostname__ = None; + let mut transport__ = None; + let mut auth_username__ = None; + let mut auth_password__ = None; + let mut headers_to_attributes__ = None; + let mut attributes_to_headers__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Hostname => { + if hostname__.is_some() { + return Err(serde::de::Error::duplicate_field("hostname")); + } + hostname__ = Some(map_.next_value()?); + } + GeneratedField::Transport => { + if transport__.is_some() { + return Err(serde::de::Error::duplicate_field("transport")); + } + transport__ = Some(map_.next_value::()? as i32); + } + GeneratedField::AuthUsername => { + if auth_username__.is_some() { + return Err(serde::de::Error::duplicate_field("authUsername")); + } + auth_username__ = Some(map_.next_value()?); + } + GeneratedField::AuthPassword => { + if auth_password__.is_some() { + return Err(serde::de::Error::duplicate_field("authPassword")); + } + auth_password__ = Some(map_.next_value()?); + } + GeneratedField::HeadersToAttributes => { + if headers_to_attributes__.is_some() { + return Err(serde::de::Error::duplicate_field("headersToAttributes")); + } + headers_to_attributes__ = Some( + map_.next_value::>()? + ); + } + GeneratedField::AttributesToHeaders => { + if attributes_to_headers__.is_some() { + return Err(serde::de::Error::duplicate_field("attributesToHeaders")); + } + attributes_to_headers__ = Some( + map_.next_value::>()? + ); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(SipOutboundConfig { + hostname: hostname__.unwrap_or_default(), + transport: transport__.unwrap_or_default(), + auth_username: auth_username__.unwrap_or_default(), + auth_password: auth_password__.unwrap_or_default(), + headers_to_attributes: headers_to_attributes__.unwrap_or_default(), + attributes_to_headers: attributes_to_headers__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.SIPOutboundConfig", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for SipOutboundTrunkInfo { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -25957,6 +26545,332 @@ impl<'de> serde::Deserialize<'de> for SipParticipantInfo { deserializer.deserialize_struct("livekit.SIPParticipantInfo", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for SipStatus { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.code != 0 { + len += 1; + } + if !self.status.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.SIPStatus", len)?; + if self.code != 0 { + let v = SipStatusCode::try_from(self.code) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.code)))?; + struct_ser.serialize_field("code", &v)?; + } + if !self.status.is_empty() { + struct_ser.serialize_field("status", &self.status)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for SipStatus { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "code", + "status", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Code, + Status, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "code" => Ok(GeneratedField::Code), + "status" => Ok(GeneratedField::Status), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = SipStatus; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.SIPStatus") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut code__ = None; + let mut status__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Code => { + if code__.is_some() { + return Err(serde::de::Error::duplicate_field("code")); + } + code__ = Some(map_.next_value::()? as i32); + } + GeneratedField::Status => { + if status__.is_some() { + return Err(serde::de::Error::duplicate_field("status")); + } + status__ = Some(map_.next_value()?); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(SipStatus { + code: code__.unwrap_or_default(), + status: status__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.SIPStatus", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for SipStatusCode { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + let variant = match self { + Self::SipStatusUnknown => "SIP_STATUS_UNKNOWN", + Self::SipStatusTrying => "SIP_STATUS_TRYING", + Self::SipStatusRinging => "SIP_STATUS_RINGING", + Self::SipStatusCallIsForwarded => "SIP_STATUS_CALL_IS_FORWARDED", + Self::SipStatusQueued => "SIP_STATUS_QUEUED", + Self::SipStatusSessionProgress => "SIP_STATUS_SESSION_PROGRESS", + Self::SipStatusOk => "SIP_STATUS_OK", + Self::SipStatusAccepted => "SIP_STATUS_ACCEPTED", + Self::SipStatusMovedPermanently => "SIP_STATUS_MOVED_PERMANENTLY", + Self::SipStatusMovedTemporarily => "SIP_STATUS_MOVED_TEMPORARILY", + Self::SipStatusUseProxy => "SIP_STATUS_USE_PROXY", + Self::SipStatusBadRequest => "SIP_STATUS_BAD_REQUEST", + Self::SipStatusUnauthorized => "SIP_STATUS_UNAUTHORIZED", + Self::SipStatusPaymentRequired => "SIP_STATUS_PAYMENT_REQUIRED", + Self::SipStatusForbidden => "SIP_STATUS_FORBIDDEN", + Self::SipStatusNotfound => "SIP_STATUS_NOTFOUND", + Self::SipStatusMethodNotAllowed => "SIP_STATUS_METHOD_NOT_ALLOWED", + Self::SipStatusNotAcceptable => "SIP_STATUS_NOT_ACCEPTABLE", + Self::SipStatusProxyAuthRequired => "SIP_STATUS_PROXY_AUTH_REQUIRED", + Self::SipStatusRequestTimeout => "SIP_STATUS_REQUEST_TIMEOUT", + Self::SipStatusConflict => "SIP_STATUS_CONFLICT", + Self::SipStatusGone => "SIP_STATUS_GONE", + Self::SipStatusRequestEntityTooLarge => "SIP_STATUS_REQUEST_ENTITY_TOO_LARGE", + Self::SipStatusRequestUriTooLong => "SIP_STATUS_REQUEST_URI_TOO_LONG", + Self::SipStatusUnsupportedMediaType => "SIP_STATUS_UNSUPPORTED_MEDIA_TYPE", + Self::SipStatusRequestedRangeNotSatisfiable => "SIP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE", + Self::SipStatusBadExtension => "SIP_STATUS_BAD_EXTENSION", + Self::SipStatusExtensionRequired => "SIP_STATUS_EXTENSION_REQUIRED", + Self::SipStatusIntervalTooBrief => "SIP_STATUS_INTERVAL_TOO_BRIEF", + Self::SipStatusTemporarilyUnavailable => "SIP_STATUS_TEMPORARILY_UNAVAILABLE", + Self::SipStatusCallTransactionDoesNotExists => "SIP_STATUS_CALL_TRANSACTION_DOES_NOT_EXISTS", + Self::SipStatusLoopDetected => "SIP_STATUS_LOOP_DETECTED", + Self::SipStatusTooManyHops => "SIP_STATUS_TOO_MANY_HOPS", + Self::SipStatusAddressIncomplete => "SIP_STATUS_ADDRESS_INCOMPLETE", + Self::SipStatusAmbiguous => "SIP_STATUS_AMBIGUOUS", + Self::SipStatusBusyHere => "SIP_STATUS_BUSY_HERE", + Self::SipStatusRequestTerminated => "SIP_STATUS_REQUEST_TERMINATED", + Self::SipStatusNotAcceptableHere => "SIP_STATUS_NOT_ACCEPTABLE_HERE", + Self::SipStatusInternalServerError => "SIP_STATUS_INTERNAL_SERVER_ERROR", + Self::SipStatusNotImplemented => "SIP_STATUS_NOT_IMPLEMENTED", + Self::SipStatusBadGateway => "SIP_STATUS_BAD_GATEWAY", + Self::SipStatusServiceUnavailable => "SIP_STATUS_SERVICE_UNAVAILABLE", + Self::SipStatusGatewayTimeout => "SIP_STATUS_GATEWAY_TIMEOUT", + Self::SipStatusVersionNotSupported => "SIP_STATUS_VERSION_NOT_SUPPORTED", + Self::SipStatusMessageTooLarge => "SIP_STATUS_MESSAGE_TOO_LARGE", + Self::SipStatusGlobalBusyEverywhere => "SIP_STATUS_GLOBAL_BUSY_EVERYWHERE", + Self::SipStatusGlobalDecline => "SIP_STATUS_GLOBAL_DECLINE", + Self::SipStatusGlobalDoesNotExistAnywhere => "SIP_STATUS_GLOBAL_DOES_NOT_EXIST_ANYWHERE", + Self::SipStatusGlobalNotAcceptable => "SIP_STATUS_GLOBAL_NOT_ACCEPTABLE", + }; + serializer.serialize_str(variant) + } +} +impl<'de> serde::Deserialize<'de> for SipStatusCode { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "SIP_STATUS_UNKNOWN", + "SIP_STATUS_TRYING", + "SIP_STATUS_RINGING", + "SIP_STATUS_CALL_IS_FORWARDED", + "SIP_STATUS_QUEUED", + "SIP_STATUS_SESSION_PROGRESS", + "SIP_STATUS_OK", + "SIP_STATUS_ACCEPTED", + "SIP_STATUS_MOVED_PERMANENTLY", + "SIP_STATUS_MOVED_TEMPORARILY", + "SIP_STATUS_USE_PROXY", + "SIP_STATUS_BAD_REQUEST", + "SIP_STATUS_UNAUTHORIZED", + "SIP_STATUS_PAYMENT_REQUIRED", + "SIP_STATUS_FORBIDDEN", + "SIP_STATUS_NOTFOUND", + "SIP_STATUS_METHOD_NOT_ALLOWED", + "SIP_STATUS_NOT_ACCEPTABLE", + "SIP_STATUS_PROXY_AUTH_REQUIRED", + "SIP_STATUS_REQUEST_TIMEOUT", + "SIP_STATUS_CONFLICT", + "SIP_STATUS_GONE", + "SIP_STATUS_REQUEST_ENTITY_TOO_LARGE", + "SIP_STATUS_REQUEST_URI_TOO_LONG", + "SIP_STATUS_UNSUPPORTED_MEDIA_TYPE", + "SIP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE", + "SIP_STATUS_BAD_EXTENSION", + "SIP_STATUS_EXTENSION_REQUIRED", + "SIP_STATUS_INTERVAL_TOO_BRIEF", + "SIP_STATUS_TEMPORARILY_UNAVAILABLE", + "SIP_STATUS_CALL_TRANSACTION_DOES_NOT_EXISTS", + "SIP_STATUS_LOOP_DETECTED", + "SIP_STATUS_TOO_MANY_HOPS", + "SIP_STATUS_ADDRESS_INCOMPLETE", + "SIP_STATUS_AMBIGUOUS", + "SIP_STATUS_BUSY_HERE", + "SIP_STATUS_REQUEST_TERMINATED", + "SIP_STATUS_NOT_ACCEPTABLE_HERE", + "SIP_STATUS_INTERNAL_SERVER_ERROR", + "SIP_STATUS_NOT_IMPLEMENTED", + "SIP_STATUS_BAD_GATEWAY", + "SIP_STATUS_SERVICE_UNAVAILABLE", + "SIP_STATUS_GATEWAY_TIMEOUT", + "SIP_STATUS_VERSION_NOT_SUPPORTED", + "SIP_STATUS_MESSAGE_TOO_LARGE", + "SIP_STATUS_GLOBAL_BUSY_EVERYWHERE", + "SIP_STATUS_GLOBAL_DECLINE", + "SIP_STATUS_GLOBAL_DOES_NOT_EXIST_ANYWHERE", + "SIP_STATUS_GLOBAL_NOT_ACCEPTABLE", + ]; + + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = SipStatusCode; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + fn visit_i64(self, v: i64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self) + }) + } + + fn visit_u64(self, v: u64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(v), &self) + }) + } + + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "SIP_STATUS_UNKNOWN" => Ok(SipStatusCode::SipStatusUnknown), + "SIP_STATUS_TRYING" => Ok(SipStatusCode::SipStatusTrying), + "SIP_STATUS_RINGING" => Ok(SipStatusCode::SipStatusRinging), + "SIP_STATUS_CALL_IS_FORWARDED" => Ok(SipStatusCode::SipStatusCallIsForwarded), + "SIP_STATUS_QUEUED" => Ok(SipStatusCode::SipStatusQueued), + "SIP_STATUS_SESSION_PROGRESS" => Ok(SipStatusCode::SipStatusSessionProgress), + "SIP_STATUS_OK" => Ok(SipStatusCode::SipStatusOk), + "SIP_STATUS_ACCEPTED" => Ok(SipStatusCode::SipStatusAccepted), + "SIP_STATUS_MOVED_PERMANENTLY" => Ok(SipStatusCode::SipStatusMovedPermanently), + "SIP_STATUS_MOVED_TEMPORARILY" => Ok(SipStatusCode::SipStatusMovedTemporarily), + "SIP_STATUS_USE_PROXY" => Ok(SipStatusCode::SipStatusUseProxy), + "SIP_STATUS_BAD_REQUEST" => Ok(SipStatusCode::SipStatusBadRequest), + "SIP_STATUS_UNAUTHORIZED" => Ok(SipStatusCode::SipStatusUnauthorized), + "SIP_STATUS_PAYMENT_REQUIRED" => Ok(SipStatusCode::SipStatusPaymentRequired), + "SIP_STATUS_FORBIDDEN" => Ok(SipStatusCode::SipStatusForbidden), + "SIP_STATUS_NOTFOUND" => Ok(SipStatusCode::SipStatusNotfound), + "SIP_STATUS_METHOD_NOT_ALLOWED" => Ok(SipStatusCode::SipStatusMethodNotAllowed), + "SIP_STATUS_NOT_ACCEPTABLE" => Ok(SipStatusCode::SipStatusNotAcceptable), + "SIP_STATUS_PROXY_AUTH_REQUIRED" => Ok(SipStatusCode::SipStatusProxyAuthRequired), + "SIP_STATUS_REQUEST_TIMEOUT" => Ok(SipStatusCode::SipStatusRequestTimeout), + "SIP_STATUS_CONFLICT" => Ok(SipStatusCode::SipStatusConflict), + "SIP_STATUS_GONE" => Ok(SipStatusCode::SipStatusGone), + "SIP_STATUS_REQUEST_ENTITY_TOO_LARGE" => Ok(SipStatusCode::SipStatusRequestEntityTooLarge), + "SIP_STATUS_REQUEST_URI_TOO_LONG" => Ok(SipStatusCode::SipStatusRequestUriTooLong), + "SIP_STATUS_UNSUPPORTED_MEDIA_TYPE" => Ok(SipStatusCode::SipStatusUnsupportedMediaType), + "SIP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE" => Ok(SipStatusCode::SipStatusRequestedRangeNotSatisfiable), + "SIP_STATUS_BAD_EXTENSION" => Ok(SipStatusCode::SipStatusBadExtension), + "SIP_STATUS_EXTENSION_REQUIRED" => Ok(SipStatusCode::SipStatusExtensionRequired), + "SIP_STATUS_INTERVAL_TOO_BRIEF" => Ok(SipStatusCode::SipStatusIntervalTooBrief), + "SIP_STATUS_TEMPORARILY_UNAVAILABLE" => Ok(SipStatusCode::SipStatusTemporarilyUnavailable), + "SIP_STATUS_CALL_TRANSACTION_DOES_NOT_EXISTS" => Ok(SipStatusCode::SipStatusCallTransactionDoesNotExists), + "SIP_STATUS_LOOP_DETECTED" => Ok(SipStatusCode::SipStatusLoopDetected), + "SIP_STATUS_TOO_MANY_HOPS" => Ok(SipStatusCode::SipStatusTooManyHops), + "SIP_STATUS_ADDRESS_INCOMPLETE" => Ok(SipStatusCode::SipStatusAddressIncomplete), + "SIP_STATUS_AMBIGUOUS" => Ok(SipStatusCode::SipStatusAmbiguous), + "SIP_STATUS_BUSY_HERE" => Ok(SipStatusCode::SipStatusBusyHere), + "SIP_STATUS_REQUEST_TERMINATED" => Ok(SipStatusCode::SipStatusRequestTerminated), + "SIP_STATUS_NOT_ACCEPTABLE_HERE" => Ok(SipStatusCode::SipStatusNotAcceptableHere), + "SIP_STATUS_INTERNAL_SERVER_ERROR" => Ok(SipStatusCode::SipStatusInternalServerError), + "SIP_STATUS_NOT_IMPLEMENTED" => Ok(SipStatusCode::SipStatusNotImplemented), + "SIP_STATUS_BAD_GATEWAY" => Ok(SipStatusCode::SipStatusBadGateway), + "SIP_STATUS_SERVICE_UNAVAILABLE" => Ok(SipStatusCode::SipStatusServiceUnavailable), + "SIP_STATUS_GATEWAY_TIMEOUT" => Ok(SipStatusCode::SipStatusGatewayTimeout), + "SIP_STATUS_VERSION_NOT_SUPPORTED" => Ok(SipStatusCode::SipStatusVersionNotSupported), + "SIP_STATUS_MESSAGE_TOO_LARGE" => Ok(SipStatusCode::SipStatusMessageTooLarge), + "SIP_STATUS_GLOBAL_BUSY_EVERYWHERE" => Ok(SipStatusCode::SipStatusGlobalBusyEverywhere), + "SIP_STATUS_GLOBAL_DECLINE" => Ok(SipStatusCode::SipStatusGlobalDecline), + "SIP_STATUS_GLOBAL_DOES_NOT_EXIST_ANYWHERE" => Ok(SipStatusCode::SipStatusGlobalDoesNotExistAnywhere), + "SIP_STATUS_GLOBAL_NOT_ACCEPTABLE" => Ok(SipStatusCode::SipStatusGlobalNotAcceptable), + _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), + } + } + } + deserializer.deserialize_any(GeneratedVisitor) + } +} impl serde::Serialize for SipTransport { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -32527,6 +33441,9 @@ impl serde::Serialize for TrackInfo { if !self.audio_features.is_empty() { len += 1; } + if self.backup_codec_policy != 0 { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.TrackInfo", len)?; if !self.sid.is_empty() { struct_ser.serialize_field("sid", &self.sid)?; @@ -32595,6 +33512,11 @@ impl serde::Serialize for TrackInfo { }).collect::, _>>()?; struct_ser.serialize_field("audioFeatures", &v)?; } + if self.backup_codec_policy != 0 { + let v = BackupCodecPolicy::try_from(self.backup_codec_policy) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.backup_codec_policy)))?; + struct_ser.serialize_field("backupCodecPolicy", &v)?; + } struct_ser.end() } } @@ -32628,6 +33550,8 @@ impl<'de> serde::Deserialize<'de> for TrackInfo { "version", "audio_features", "audioFeatures", + "backup_codec_policy", + "backupCodecPolicy", ]; #[allow(clippy::enum_variant_names)] @@ -32651,6 +33575,7 @@ impl<'de> serde::Deserialize<'de> for TrackInfo { Stream, Version, AudioFeatures, + BackupCodecPolicy, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -32692,6 +33617,7 @@ impl<'de> serde::Deserialize<'de> for TrackInfo { "stream" => Ok(GeneratedField::Stream), "version" => Ok(GeneratedField::Version), "audioFeatures" | "audio_features" => Ok(GeneratedField::AudioFeatures), + "backupCodecPolicy" | "backup_codec_policy" => Ok(GeneratedField::BackupCodecPolicy), _ => Ok(GeneratedField::__SkipField__), } } @@ -32730,6 +33656,7 @@ impl<'de> serde::Deserialize<'de> for TrackInfo { let mut stream__ = None; let mut version__ = None; let mut audio_features__ = None; + let mut backup_codec_policy__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::Sid => { @@ -32850,6 +33777,12 @@ impl<'de> serde::Deserialize<'de> for TrackInfo { } audio_features__ = Some(map_.next_value::>()?.into_iter().map(|x| x as i32).collect()); } + GeneratedField::BackupCodecPolicy => { + if backup_codec_policy__.is_some() { + return Err(serde::de::Error::duplicate_field("backupCodecPolicy")); + } + backup_codec_policy__ = Some(map_.next_value::()? as i32); + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -32875,6 +33808,7 @@ impl<'de> serde::Deserialize<'de> for TrackInfo { stream: stream__.unwrap_or_default(), version: version__, audio_features: audio_features__.unwrap_or_default(), + backup_codec_policy: backup_codec_policy__.unwrap_or_default(), }) } } diff --git a/webrtc-sys/build.rs b/webrtc-sys/build.rs index 69695330c..9b32db1d7 100644 --- a/webrtc-sys/build.rs +++ b/webrtc-sys/build.rs @@ -48,6 +48,7 @@ fn main() { "src/audio_resampler.rs", "src/android.rs", "src/prohibit_libsrtp_initialization.rs", + "src/apm.rs", ]); builder.files(&[ @@ -75,6 +76,7 @@ fn main() { "src/frame_cryptor.cpp", "src/global_task_queue.cpp", "src/prohibit_libsrtp_initialization.cpp", + "src/apm.cpp", ]); let webrtc_dir = webrtc_sys_build::webrtc_dir(); @@ -95,6 +97,7 @@ fn main() { webrtc_include.join("sdk/objc"), webrtc_include.join("sdk/objc/base"), ]); + builder.define("WEBRTC_APM_DEBUG_DUMP", "0"); println!("cargo:rustc-link-search=native={}", webrtc_lib.to_str().unwrap()); diff --git a/webrtc-sys/compile_flags.txt b/webrtc-sys/compile_flags.txt index 950ea1edc..e1d524d1c 100644 --- a/webrtc-sys/compile_flags.txt +++ b/webrtc-sys/compile_flags.txt @@ -14,6 +14,7 @@ -DWEBRTC_ANDROID -DNDEBUG -DWEBRTC_ENABLE_SYMBOL_EXPORT +-DWEBRTC_APM_DEBUG_DUMP=0 -D__ANDROID_API__=29 -DWEBRTC_LIBRARY_IMPL --sysroot=/Users/theomonnom/Library/Android/sdk/ndk-bundle/toolchains/llvm/prebuilt/darwin-x86_64/sysroot diff --git a/webrtc-sys/include/livekit/apm.h b/webrtc-sys/include/livekit/apm.h new file mode 100644 index 000000000..180499dd0 --- /dev/null +++ b/webrtc-sys/include/livekit/apm.h @@ -0,0 +1,73 @@ +/* + * Copyright 2023 LiveKit + * + * Licensed under the Apache License, Version 2.0 (the “License”); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an “AS IS” BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include "api/scoped_refptr.h" +#include "api/video_codecs/video_decoder_factory.h" +#include "api/video_codecs/video_encoder_factory.h" +#include "modules/audio_processing/aec3/echo_canceller3.h" +#include "modules/audio_processing/audio_buffer.h" + +namespace livekit { + +struct AudioProcessingConfig { + bool echo_canceller_enabled; + bool gain_controller_enabled; + bool high_pass_filter_enabled; + bool noise_suppression_enabled; + + webrtc::AudioProcessing::Config ToWebrtcConfig() const { + webrtc::AudioProcessing::Config config; + config.echo_canceller.enabled = echo_canceller_enabled; + config.gain_controller2.enabled = gain_controller_enabled; + config.high_pass_filter.enabled = high_pass_filter_enabled; + config.noise_suppression.enabled = noise_suppression_enabled; + return config; + } +}; + +class AudioProcessingModule { + public: + AudioProcessingModule(const AudioProcessingConfig& config); + + int process_stream(const int16_t* src, + size_t src_len, + int16_t* dst, + size_t dst_len, + int sample_rate, + int num_channels); + + int process_reverse_stream(const int16_t* src, + size_t src_len, + int16_t* dst, + size_t dst_len, + int sample_rate, + int num_channels); + + private: + rtc::scoped_refptr apm_; +}; + +std::unique_ptr create_apm( + bool echo_canceller_enabled, + bool gain_controller_enabled, + bool high_pass_filter_enabled, + bool noise_suppression_enabled); + +} // namespace livekit diff --git a/webrtc-sys/src/apm.cpp b/webrtc-sys/src/apm.cpp new file mode 100644 index 000000000..358f7ed4a --- /dev/null +++ b/webrtc-sys/src/apm.cpp @@ -0,0 +1,49 @@ +#include "livekit/apm.h" + +#include +#include + +namespace livekit { + +AudioProcessingModule::AudioProcessingModule( + const AudioProcessingConfig& config) { + apm_ = webrtc::AudioProcessingBuilder().Create(); + + apm_->ApplyConfig(config.ToWebrtcConfig()); + apm_->Initialize(); +} + +int AudioProcessingModule::process_stream(const int16_t* src, + size_t src_len, + int16_t* dst, + size_t dst_len, + int sample_rate, + int num_channels) { + webrtc::StreamConfig stream_cfg(sample_rate, num_channels); + return apm_->ProcessStream(src, stream_cfg, stream_cfg, dst); +} + +int AudioProcessingModule::process_reverse_stream(const int16_t* src, + size_t src_len, + int16_t* dst, + size_t dst_len, + int sample_rate, + int num_channels) { + webrtc::StreamConfig stream_cfg(sample_rate, num_channels); + return apm_->ProcessReverseStream(src, stream_cfg, stream_cfg, dst); +} + +std::unique_ptr create_apm( + bool echo_canceller_enabled, + bool gain_controller_enabled, + bool high_pass_filter_enabled, + bool noise_suppression_enabled) { + AudioProcessingConfig config; + config.echo_canceller_enabled = echo_canceller_enabled; + config.gain_controller_enabled = gain_controller_enabled; + config.high_pass_filter_enabled = high_pass_filter_enabled; + config.noise_suppression_enabled = noise_suppression_enabled; + return std::make_unique(config); +} + +} // namespace livekit diff --git a/webrtc-sys/src/apm.rs b/webrtc-sys/src/apm.rs new file mode 100644 index 000000000..96d7631c5 --- /dev/null +++ b/webrtc-sys/src/apm.rs @@ -0,0 +1,53 @@ +// Copyright 2023 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::impl_thread_safety; + +#[cxx::bridge(namespace = "livekit")] +pub mod ffi { + unsafe extern "C++" { + include!("livekit/apm.h"); + + type AudioProcessingModule; + + unsafe fn process_stream( + self: Pin<&mut AudioProcessingModule>, + src: *const i16, + src_len: usize, + dst: *mut i16, + dst_len: usize, + sample_rate: i32, + num_channels: i32, + ) -> i32; + + unsafe fn process_reverse_stream( + self: Pin<&mut AudioProcessingModule>, + src: *const i16, + src_len: usize, + dst: *mut i16, + dst_len: usize, + sample_rate: i32, + num_channels: i32, + ) -> i32; + + fn create_apm( + echo_canceller_enabled: bool, + gain_controller_enabled: bool, + high_pass_filter_enabled: bool, + noise_suppression_enabled: bool, + ) -> UniquePtr; + } +} + +impl_thread_safety!(ffi::AudioProcessingModule, Send + Sync); diff --git a/webrtc-sys/src/lib.rs b/webrtc-sys/src/lib.rs index 657c2fb55..b3229fad7 100644 --- a/webrtc-sys/src/lib.rs +++ b/webrtc-sys/src/lib.rs @@ -36,6 +36,7 @@ pub mod video_frame_buffer; pub mod video_track; pub mod webrtc; pub mod yuv_helper; +pub mod apm; pub const MEDIA_TYPE_VIDEO: &str = "video"; pub const MEDIA_TYPE_AUDIO: &str = "audio"; From 1383b69cb59546727f002793df62116d0fce3166 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Tue, 4 Mar 2025 20:58:37 +0100 Subject: [PATCH 160/274] ffi-v0.13.0 (#590) --- libwebrtc/src/native/mod.rs | 2 +- livekit-ffi/Cargo.toml | 2 +- livekit-ffi/src/server/mod.rs | 4 +++- webrtc-sys/src/lib.rs | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/libwebrtc/src/native/mod.rs b/libwebrtc/src/native/mod.rs index 98f757174..8ad65145b 100644 --- a/libwebrtc/src/native/mod.rs +++ b/libwebrtc/src/native/mod.rs @@ -12,9 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -pub mod apm; #[cfg(target_os = "android")] pub mod android; +pub mod apm; pub mod audio_resampler; pub mod audio_source; pub mod audio_stream; diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 411a533dd..184530278 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.12.13" +version = "0.13.0" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" diff --git a/livekit-ffi/src/server/mod.rs b/livekit-ffi/src/server/mod.rs index 80177bbac..81cad9abb 100644 --- a/livekit-ffi/src/server/mod.rs +++ b/livekit-ffi/src/server/mod.rs @@ -24,7 +24,9 @@ use std::{ use dashmap::{mapref::one::MappedRef, DashMap}; use downcast_rs::{impl_downcast, Downcast}; -use livekit::webrtc::{native::apm::AudioProcessingModule, native::audio_resampler::AudioResampler, prelude::*}; +use livekit::webrtc::{ + native::apm::AudioProcessingModule, native::audio_resampler::AudioResampler, prelude::*, +}; use parking_lot::{deadlock, Mutex}; use tokio::{sync::oneshot, task::JoinHandle}; diff --git a/webrtc-sys/src/lib.rs b/webrtc-sys/src/lib.rs index b3229fad7..6baf7e2f6 100644 --- a/webrtc-sys/src/lib.rs +++ b/webrtc-sys/src/lib.rs @@ -14,6 +14,7 @@ #[cfg(target_os = "android")] pub mod android; +pub mod apm; pub mod audio_resampler; pub mod audio_track; pub mod candidate; @@ -36,7 +37,6 @@ pub mod video_frame_buffer; pub mod video_track; pub mod webrtc; pub mod yuv_helper; -pub mod apm; pub const MEDIA_TYPE_VIDEO: &str = "video"; pub const MEDIA_TYPE_AUDIO: &str = "audio"; From 17186b3cfaccebbb92888cf789abee2583742286 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Tue, 4 Mar 2025 21:04:44 +0100 Subject: [PATCH 161/274] bump versions (#591) --- .nanpa/blabla.kdl | 1 + 1 file changed, 1 insertion(+) create mode 100644 .nanpa/blabla.kdl diff --git a/.nanpa/blabla.kdl b/.nanpa/blabla.kdl new file mode 100644 index 000000000..c7f2bb660 --- /dev/null +++ b/.nanpa/blabla.kdl @@ -0,0 +1 @@ +patch type="added" package="livekit-ffi" "APM" From ced99cfba8667b20b30558f66d52278cc0c1d90a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Tue, 4 Mar 2025 21:21:02 +0100 Subject: [PATCH 162/274] Create bump-livekit-ffi.kdl (#592) --- .nanpa/bump-livekit-ffi.kdl | 1 + 1 file changed, 1 insertion(+) create mode 100644 .nanpa/bump-livekit-ffi.kdl diff --git a/.nanpa/bump-livekit-ffi.kdl b/.nanpa/bump-livekit-ffi.kdl new file mode 100644 index 000000000..851b94d8a --- /dev/null +++ b/.nanpa/bump-livekit-ffi.kdl @@ -0,0 +1 @@ +patch type="fixed" package="livekit-ffi" "debugging nanpa" \ No newline at end of file From 2516aab40cae61b331ca006e2723cfebd5ad2e03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Tue, 4 Mar 2025 21:23:55 +0100 Subject: [PATCH 163/274] undo manual bump (#593) --- livekit-ffi/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 184530278..411a533dd 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.13.0" +version = "0.12.13" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" From aea9f4e47dbcb0d34d146bfe315f75e5bfd63cc2 Mon Sep 17 00:00:00 2001 From: "ilo-nanpa[bot]" <174912985+ilo-nanpa[bot]@users.noreply.github.com> Date: Tue, 4 Mar 2025 20:41:41 +0000 Subject: [PATCH 164/274] nanpa: bump --- .nanpa/blabla.kdl | 1 - .nanpa/bump-livekit-ffi.kdl | 1 - Cargo.toml | 2 +- livekit-ffi/.nanpa/rival-fang-bride.kdl | 1 - livekit-ffi/.nanparc | 2 +- livekit-ffi/CHANGELOG.md | 11 +++++++++++ livekit-ffi/Cargo.toml | 2 +- 7 files changed, 14 insertions(+), 6 deletions(-) delete mode 100644 .nanpa/blabla.kdl delete mode 100644 .nanpa/bump-livekit-ffi.kdl delete mode 100644 livekit-ffi/.nanpa/rival-fang-bride.kdl diff --git a/.nanpa/blabla.kdl b/.nanpa/blabla.kdl deleted file mode 100644 index c7f2bb660..000000000 --- a/.nanpa/blabla.kdl +++ /dev/null @@ -1 +0,0 @@ -patch type="added" package="livekit-ffi" "APM" diff --git a/.nanpa/bump-livekit-ffi.kdl b/.nanpa/bump-livekit-ffi.kdl deleted file mode 100644 index 851b94d8a..000000000 --- a/.nanpa/bump-livekit-ffi.kdl +++ /dev/null @@ -1 +0,0 @@ -patch type="fixed" package="livekit-ffi" "debugging nanpa" \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index a3597e666..7562bb161 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ imgproc = { version = "0.3.12", path = "imgproc" } yuv-sys = { version = "0.3.7", path = "yuv-sys" } libwebrtc = { version = "0.3.10", path = "libwebrtc" } livekit-api = { version = "0.4.2", path = "livekit-api" } -livekit-ffi = { version = "0.12.13", path = "livekit-ffi" } +livekit-ffi = { version = "0.12.14", path = "livekit-ffi" } livekit-protocol = { version = "0.3.9", path = "livekit-protocol" } livekit-runtime = { version = "0.4.0", path = "livekit-runtime" } livekit = { version = "0.7.6", path = "livekit" } diff --git a/livekit-ffi/.nanpa/rival-fang-bride.kdl b/livekit-ffi/.nanpa/rival-fang-bride.kdl deleted file mode 100644 index 86dac442f..000000000 --- a/livekit-ffi/.nanpa/rival-fang-bride.kdl +++ /dev/null @@ -1 +0,0 @@ -patch type="fixed" package="livekit-ffi" "Fixed a packaging issue" diff --git a/livekit-ffi/.nanparc b/livekit-ffi/.nanparc index 2cbc25150..6ec83e8ba 100644 --- a/livekit-ffi/.nanparc +++ b/livekit-ffi/.nanparc @@ -1,2 +1,2 @@ -version 0.12.13 +version 0.12.14 language rust diff --git a/livekit-ffi/CHANGELOG.md b/livekit-ffi/CHANGELOG.md index d3c272d09..fc2e9858b 100644 --- a/livekit-ffi/CHANGELOG.md +++ b/livekit-ffi/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## [0.12.14] - 2025-03-04 + +### Added + +- APM + +### Fixed + +- debugging nanpa +- Fixed a packaging issue + ## [0.12.13] - 2025-03-04 ### Fixed diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 411a533dd..68e9b1626 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.12.13" +version = "0.12.14" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" From 835d6108ecb7d3c6ec50c33579272aa396b78d52 Mon Sep 17 00:00:00 2001 From: CloudWebRTC Date: Wed, 5 Mar 2025 10:22:33 +0800 Subject: [PATCH 165/274] fix libwebrtc.jar build issue (#586) * fix libwebrtc.jar build issue * Update android_use_libunwind.patch * Update android_use_libunwind.patch * Update android_use_libunwind.patch * Update webrtc-builds.yml * Create fixed-libwebrtc-jar-build * Rename fixed-libwebrtc-jar-build to fixed-libwebrtc-jar-build.kdl * Update webrtc-builds.yml --- .github/workflows/webrtc-builds.yml | 4 ++-- .nanpa/fixed-libwebrtc-jar-build.kdl | 1 + .../patches/android_use_libunwind.patch | 16 ++++++++-------- 3 files changed, 11 insertions(+), 10 deletions(-) create mode 100644 .nanpa/fixed-libwebrtc-jar-build.kdl diff --git a/.github/workflows/webrtc-builds.yml b/.github/workflows/webrtc-builds.yml index 13f4c307f..3f17bc8eb 100644 --- a/.github/workflows/webrtc-builds.yml +++ b/.github/workflows/webrtc-builds.yml @@ -56,12 +56,12 @@ jobs: arch: arm64 - name: android - os: ubuntu-24.04-arm + os: ubuntu-latest cmd: ./build_android.sh arch: arm64 - name: android - os: ubuntu-24.04-arm + os: ubuntu-latest cmd: ./build_android.sh arch: arm diff --git a/.nanpa/fixed-libwebrtc-jar-build.kdl b/.nanpa/fixed-libwebrtc-jar-build.kdl new file mode 100644 index 000000000..3af32880b --- /dev/null +++ b/.nanpa/fixed-libwebrtc-jar-build.kdl @@ -0,0 +1 @@ +patch type="fixed" "Build issue for libwebrtc.jar and libjingle_peerconnection_so.so" diff --git a/webrtc-sys/libwebrtc/patches/android_use_libunwind.patch b/webrtc-sys/libwebrtc/patches/android_use_libunwind.patch index e1e7e6c4d..82512a2fc 100644 --- a/webrtc-sys/libwebrtc/patches/android_use_libunwind.patch +++ b/webrtc-sys/libwebrtc/patches/android_use_libunwind.patch @@ -1,17 +1,17 @@ --- src/buildtools/third_party/libunwind/BUILD.gn 2023-07-10 10:19:16 +++ src/buildtools/third_party/libunwind/BUILD.gn 2023-07-10 10:19:23 -@@ -20,7 +20,7 @@ - } +@@ -21,7 +21,7 @@ config("libunwind_config") { + # TODO(crbug.com/1458042): Move this build file to third_party/libc++/BUILD.gn once submodule migration is done source_set("libunwind") { -- visibility = [] -+ visibility = ["//build/config:common_deps"] - if (is_fuchsia) { - visibility += [ "//buildtools/third_party/libc++abi" ] - } else if (is_android) { +- visibility = [ "//buildtools/third_party/libc++abi" ] ++ visibility = [ "//buildtools/third_party/libc++abi", "//build/config:common_deps" ] + if (is_android) { + visibility += [ "//services/tracing/public/cpp" ] + } --- src/build/config/BUILD.gn 2023-07-10 10:23:49 +++ src/build/config/BUILD.gn 2023-07-10 10:23:54 -@@ -244,6 +244,8 @@ +@@ -246,6 +246,8 @@ group("common_deps") { if (use_custom_libcxx) { public_deps += [ "//buildtools/third_party/libc++" ] From b509de3707b8cffd78de8c94aeb45f69330da150 Mon Sep 17 00:00:00 2001 From: Daisuke Murase Date: Wed, 5 Mar 2025 14:33:41 -0800 Subject: [PATCH 166/274] Ensure RTC session continues even if audio filter initialization fails (#594) * make the rtc session can continue even if the audio filter fails to initialize * add a changeset * fmt --- .nanpa/crumb-state-banjo.kdl | 1 + Cargo.lock | 2 +- livekit-ffi/src/server/audio_stream.rs | 41 +++++++++++++++++--------- livekit-ffi/src/server/room.rs | 12 ++------ 4 files changed, 31 insertions(+), 25 deletions(-) create mode 100644 .nanpa/crumb-state-banjo.kdl diff --git a/.nanpa/crumb-state-banjo.kdl b/.nanpa/crumb-state-banjo.kdl new file mode 100644 index 000000000..48fba8d61 --- /dev/null +++ b/.nanpa/crumb-state-banjo.kdl @@ -0,0 +1 @@ +patch package="livekit-ffi" type="fixed" "Ensure RTC session continues even if audio filter initialization fails" diff --git a/Cargo.lock b/Cargo.lock index 8fe183c44..bb79acf8a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1655,7 +1655,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.12.13" +version = "0.12.14" dependencies = [ "console-subscriber", "dashmap", diff --git a/livekit-ffi/src/server/audio_stream.rs b/livekit-ffi/src/server/audio_stream.rs index 8cb6843b4..b2fc504fa 100644 --- a/livekit-ffi/src/server/audio_stream.rs +++ b/livekit-ffi/src/server/audio_stream.rs @@ -101,23 +101,28 @@ impl FfiAudioStream { NativeAudioStream::new(rtc_track, sample_rate as i32, num_channels as i32); let stream = if let Some(audio_filter) = &audio_filter { - let Some(session) = audio_filter.clone().new_session( + let session = audio_filter.clone().new_session( sample_rate, new_stream.audio_filter_options.unwrap_or("".into()), stream_info, - ) else { - return Err(FfiError::InvalidRequest( - "audio filter is not initialized".into(), - )); - }; - let stream = AudioFilterAudioStream::new( - native_stream, - session, - Duration::from_millis(10), - sample_rate, - num_channels, ); - AudioStreamKind::Filtered(stream) + + match session { + Some(session) => { + let stream = AudioFilterAudioStream::new( + native_stream, + session, + Duration::from_millis(10), + sample_rate, + num_channels, + ); + AudioStreamKind::Filtered(stream) + } + None => { + log::error!("failed to initialize the audio filter. it will not be enabled for this session."); + AudioStreamKind::Native(native_stream) + } + } } else { AudioStreamKind::Native(native_stream) }; @@ -254,7 +259,15 @@ impl FfiAudioStream { track_id: track.sid().into(), }; - filter.clone().new_session(sample_rate as u32, &options, stream_info) + let session = filter.clone().new_session( + sample_rate as u32, + &options, + stream_info, + ); + if session.is_none() { + log::error!("failed to initialize the audio filter. it will not be enabled for this session."); + } + session } None => None, }, diff --git a/livekit-ffi/src/server/room.rs b/livekit-ffi/src/server/room.rs index 13913d01d..3a0d0bdf4 100644 --- a/livekit-ffi/src/server/room.rs +++ b/livekit-ffi/src/server/room.rs @@ -145,16 +145,8 @@ impl FfiRoom { match result { Err(e) | Ok(Err(e)) => { log::error!("error while initializing audio filter: {}", e); - let _ = server.send_event(proto::ffi_event::Message::Connect( - proto::ConnectCallback { - async_id, - message: Some(proto::connect_callback::Message::Error( - e.to_string(), - )), - ..Default::default() - }, - )); - return; + // Skip returning an error here to keep the rtc session alive + // But in this case, the filter isn't enabled in the session. } Ok(Ok(_)) => (), }; From dde59ce1bb5c1e7ecaad0f2787756717fd125eef Mon Sep 17 00:00:00 2001 From: "ilo-nanpa[bot]" <174912985+ilo-nanpa[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 22:35:44 +0000 Subject: [PATCH 167/274] nanpa: bump --- .nanpa/crumb-state-banjo.kdl | 1 - Cargo.toml | 2 +- livekit-ffi/.nanparc | 2 +- livekit-ffi/CHANGELOG.md | 6 ++++++ livekit-ffi/Cargo.toml | 2 +- 5 files changed, 9 insertions(+), 4 deletions(-) delete mode 100644 .nanpa/crumb-state-banjo.kdl diff --git a/.nanpa/crumb-state-banjo.kdl b/.nanpa/crumb-state-banjo.kdl deleted file mode 100644 index 48fba8d61..000000000 --- a/.nanpa/crumb-state-banjo.kdl +++ /dev/null @@ -1 +0,0 @@ -patch package="livekit-ffi" type="fixed" "Ensure RTC session continues even if audio filter initialization fails" diff --git a/Cargo.toml b/Cargo.toml index 7562bb161..ad123dd46 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ imgproc = { version = "0.3.12", path = "imgproc" } yuv-sys = { version = "0.3.7", path = "yuv-sys" } libwebrtc = { version = "0.3.10", path = "libwebrtc" } livekit-api = { version = "0.4.2", path = "livekit-api" } -livekit-ffi = { version = "0.12.14", path = "livekit-ffi" } +livekit-ffi = { version = "0.12.15", path = "livekit-ffi" } livekit-protocol = { version = "0.3.9", path = "livekit-protocol" } livekit-runtime = { version = "0.4.0", path = "livekit-runtime" } livekit = { version = "0.7.6", path = "livekit" } diff --git a/livekit-ffi/.nanparc b/livekit-ffi/.nanparc index 6ec83e8ba..7b26423fd 100644 --- a/livekit-ffi/.nanparc +++ b/livekit-ffi/.nanparc @@ -1,2 +1,2 @@ -version 0.12.14 +version 0.12.15 language rust diff --git a/livekit-ffi/CHANGELOG.md b/livekit-ffi/CHANGELOG.md index fc2e9858b..1e06b01bb 100644 --- a/livekit-ffi/CHANGELOG.md +++ b/livekit-ffi/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [0.12.15] - 2025-03-05 + +### Fixed + +- Ensure RTC session continues even if audio filter initialization fails + ## [0.12.14] - 2025-03-04 ### Added diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 68e9b1626..25a27f740 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.12.14" +version = "0.12.15" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" From a11f7d2cffd6a09f3f0339dfd285447418a07498 Mon Sep 17 00:00:00 2001 From: Daisuke Murase Date: Thu, 6 Mar 2025 13:22:15 -0800 Subject: [PATCH 168/274] Add a mechanism to update stream information for applied audio filters after AudioStream initialization (#596) * add a way to update stream_info after AudioStream is initialized * fmt * add a changeset --- .nanpa/candy-blimp-twice.kdl | 1 + Cargo.lock | 2 +- livekit-ffi/src/server/audio_stream.rs | 65 ++++++++++++++++++++++---- livekit-ffi/src/server/room.rs | 2 +- livekit/src/plugin.rs | 25 +++++++++- 5 files changed, 82 insertions(+), 13 deletions(-) create mode 100644 .nanpa/candy-blimp-twice.kdl diff --git a/.nanpa/candy-blimp-twice.kdl b/.nanpa/candy-blimp-twice.kdl new file mode 100644 index 000000000..cf5ac1016 --- /dev/null +++ b/.nanpa/candy-blimp-twice.kdl @@ -0,0 +1 @@ +patch package="livekit-ffi" type="fixed" "Fixed metric report issue on audio filter where room_id is sometimes empty" diff --git a/Cargo.lock b/Cargo.lock index bb79acf8a..f8a5eac4c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1655,7 +1655,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.12.14" +version = "0.12.15" dependencies = [ "console-subscriber", "dashmap", diff --git a/livekit-ffi/src/server/audio_stream.rs b/livekit-ffi/src/server/audio_stream.rs index b2fc504fa..902cdd281 100644 --- a/livekit-ffi/src/server/audio_stream.rs +++ b/livekit-ffi/src/server/audio_stream.rs @@ -57,7 +57,7 @@ impl FfiAudioStream { return Err(FfiError::InvalidRequest("not an audio track".into())); }; - let (audio_filter, stream_info) = match &new_stream.audio_filter_module_id { + let (audio_filter, info) = match &new_stream.audio_filter_module_id { Some(module_id) => { let Some(room_handle) = ffi_track.room_handle else { return Err(FfiError::InvalidRequest( @@ -83,9 +83,9 @@ impl FfiAudioStream { track_id: rtc_track.id(), }; - (Some(filter), stream_info) + (Some(filter), Some(AudioFilterInfo { stream_info, room_handle })) } - None => (None, AudioFilterStreamInfo::default()), + None => (None, None), }; let stream_type = new_stream.r#type(); @@ -104,7 +104,7 @@ impl FfiAudioStream { let session = audio_filter.clone().new_session( sample_rate, new_stream.audio_filter_options.unwrap_or("".into()), - stream_info, + info.as_ref().map(|i| i.stream_info.clone()).unwrap(), ); match session { @@ -134,6 +134,7 @@ impl FfiAudioStream { self_dropped_rx, server.watch_handle_dropped(new_stream.track_handle), true, + info, )); server.watch_panic(handle); Ok::(audio_stream) @@ -208,7 +209,8 @@ impl FfiAudioStream { // track_tx is no longer held, so the track_rx will be closed when track_changed_trigger is done let url = ffi_participant.room.url(); - let room_sid = ffi_participant.room.room.sid().await; + let room_sid = + ffi_participant.room.room.maybe_sid().map(|id| id.to_string()).unwrap_or("".into()); let room_name = ffi_participant.room.room.name(); let participant_identity = ffi_participant.participant.identity(); let participant_id = ffi_participant.participant.sid(); @@ -247,7 +249,7 @@ impl FfiAudioStream { } }); - let mut audio_filter_session = match &filter { + let (mut audio_filter_session, info) = match &filter { Some(filter) => match &request.audio_filter_options { Some(options) => { let stream_info = AudioFilterStreamInfo { @@ -259,19 +261,24 @@ impl FfiAudioStream { track_id: track.sid().into(), }; + let info = AudioFilterInfo { + stream_info, + room_handle: ffi_participant.room.handle_id, + }; + let session = filter.clone().new_session( sample_rate as u32, &options, - stream_info, + info.stream_info.clone(), ); if session.is_none() { log::error!("failed to initialize the audio filter. it will not be enabled for this session."); } - session + (session, Some(info)) } - None => None, + None => (None, None), }, - None => None, + None => (None, None), }; let native_stream = NativeAudioStream::new(rtc_track, sample_rate, num_channels); @@ -297,6 +304,7 @@ impl FfiAudioStream { c_rx, handle_dropped_rx, false, + info, ) .await; let _ = done_tx.send(()); @@ -332,6 +340,7 @@ impl FfiAudioStream { mut self_dropped_rx: oneshot::Receiver<()>, mut handle_dropped_rx: oneshot::Receiver<()>, send_eos: bool, + mut filter_info: Option, ) { loop { tokio::select! { @@ -346,6 +355,21 @@ impl FfiAudioStream { break; }; + if let Some(ref mut info) = filter_info { + if info.stream_info.room_id == "" { + // check if room_id is updated + if info.update_room_id(server) { + if info.stream_info.room_id != "" { + if let AudioStreamKind::Filtered(ref mut filter) = native_stream { + filter.update_stream_info(info.stream_info.clone()); + } + // room_id is updated, this check is no longer needed. + filter_info = None; + } + } + } + } + let handle_id = server.next_id(); let buffer_info = proto::AudioFrameBufferInfo::from(&frame); server.store_handle(handle_id, frame); @@ -383,3 +407,24 @@ impl FfiAudioStream { } } } + +// Used to update audio filter session when the stream info is changed. (Mainly room_id +#[derive(Default)] +struct AudioFilterInfo { + stream_info: AudioFilterStreamInfo, + room_handle: FfiHandleId, +} + +impl AudioFilterInfo { + fn update_room_id(&mut self, server: &'static server::FfiServer) -> bool { + let Ok(room) = server.retrieve_handle::(self.room_handle) else { + return false; + }; + let room_id = room.inner.room.maybe_sid().map(|id| id.to_string()).unwrap_or("".into()); + if room_id != "" { + self.stream_info.room_id = room_id.into(); + return true; + } + false + } +} diff --git a/livekit-ffi/src/server/room.rs b/livekit-ffi/src/server/room.rs index 3a0d0bdf4..d8399967b 100644 --- a/livekit-ffi/src/server/room.rs +++ b/livekit-ffi/src/server/room.rs @@ -56,7 +56,7 @@ pub struct FfiRoom { pub struct RoomInner { pub room: Room, - handle_id: FfiHandleId, + pub(crate) handle_id: FfiHandleId, data_tx: mpsc::UnboundedSender, transcription_tx: mpsc::UnboundedSender, dtmf_tx: mpsc::UnboundedSender, diff --git a/livekit/src/plugin.rs b/livekit/src/plugin.rs index 23af1c057..0ef2616ef 100644 --- a/livekit/src/plugin.rs +++ b/livekit/src/plugin.rs @@ -33,6 +33,7 @@ type CreateFn = unsafe extern "C" fn( type DestroyFn = unsafe extern "C" fn(*const c_void); type ProcessI16Fn = unsafe extern "C" fn(*const c_void, usize, *const i16, *mut i16); type ProcessF32Fn = unsafe extern "C" fn(*const c_void, usize, *const f32, *mut f32); +type UpdateStreamInfoFn = unsafe extern "C" fn(*const c_void, *const c_char); static REGISTERED_PLUGINS: LazyLock>>> = LazyLock::new(|| RwLock::new(HashMap::new())); @@ -57,6 +58,7 @@ pub struct AudioFilterPlugin { destroy_fn_ptr: *const c_void, process_i16_fn_ptr: *const c_void, process_f32_fn_ptr: *const c_void, + update_stream_info_fn_ptr: *const c_void, } impl AudioFilterPlugin { @@ -116,6 +118,11 @@ impl AudioFilterPlugin { .try_as_raw_ptr() .unwrap() }; + let update_stream_info_fn_ptr = unsafe { + lib.get::>(b"audio_filter_update_stream_info")? + .try_as_raw_ptr() + .unwrap() + }; Ok(Self { lib, @@ -125,6 +132,7 @@ impl AudioFilterPlugin { destroy_fn_ptr, process_i16_fn_ptr, process_f32_fn_ptr, + update_stream_info_fn_ptr, }) } @@ -196,6 +204,17 @@ impl AudioFilterSession { let process: ProcessF32Fn = unsafe { std::mem::transmute(self.plugin.process_f32_fn_ptr) }; unsafe { process(self.ptr, num_samples, input.as_ptr(), output.as_mut_ptr()) }; } + + pub fn update_stream_info(&self, info: AudioFilterStreamInfo) { + if self.plugin.update_stream_info_fn_ptr.is_null() { + return; + } + let update_stream_info_fn: UpdateStreamInfoFn = + unsafe { std::mem::transmute(self.plugin.update_stream_info_fn_ptr) }; + let info_json = serde_json::to_string(&info).unwrap(); + let info_json = CString::new(info_json).unwrap_or(CString::new("").unwrap()); + unsafe { update_stream_info_fn(self.ptr, info_json.as_ptr()) } + } } impl Drop for AudioFilterSession { @@ -234,6 +253,10 @@ impl AudioFilterAudioStream { frame_size, } } + + pub fn update_stream_info(&mut self, info: AudioFilterStreamInfo) { + self.session.update_stream_info(info); + } } impl Stream for AudioFilterAudioStream { @@ -267,7 +290,7 @@ impl Stream for AudioFilterAudioStream { } } -#[derive(Debug, Serialize, Default)] +#[derive(Debug, Serialize, Default, Clone)] #[serde(rename_all = "camelCase")] pub struct AudioFilterStreamInfo { pub url: String, From 3aeced9522a2b06b1d2e7778a06ed2c8ddfaf76f Mon Sep 17 00:00:00 2001 From: "ilo-nanpa[bot]" <174912985+ilo-nanpa[bot]@users.noreply.github.com> Date: Thu, 6 Mar 2025 21:23:27 +0000 Subject: [PATCH 169/274] nanpa: bump --- .nanpa/candy-blimp-twice.kdl | 1 - Cargo.toml | 2 +- livekit-ffi/.nanparc | 2 +- livekit-ffi/CHANGELOG.md | 6 ++++++ livekit-ffi/Cargo.toml | 2 +- 5 files changed, 9 insertions(+), 4 deletions(-) delete mode 100644 .nanpa/candy-blimp-twice.kdl diff --git a/.nanpa/candy-blimp-twice.kdl b/.nanpa/candy-blimp-twice.kdl deleted file mode 100644 index cf5ac1016..000000000 --- a/.nanpa/candy-blimp-twice.kdl +++ /dev/null @@ -1 +0,0 @@ -patch package="livekit-ffi" type="fixed" "Fixed metric report issue on audio filter where room_id is sometimes empty" diff --git a/Cargo.toml b/Cargo.toml index ad123dd46..75b6aab19 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ imgproc = { version = "0.3.12", path = "imgproc" } yuv-sys = { version = "0.3.7", path = "yuv-sys" } libwebrtc = { version = "0.3.10", path = "libwebrtc" } livekit-api = { version = "0.4.2", path = "livekit-api" } -livekit-ffi = { version = "0.12.15", path = "livekit-ffi" } +livekit-ffi = { version = "0.12.16", path = "livekit-ffi" } livekit-protocol = { version = "0.3.9", path = "livekit-protocol" } livekit-runtime = { version = "0.4.0", path = "livekit-runtime" } livekit = { version = "0.7.6", path = "livekit" } diff --git a/livekit-ffi/.nanparc b/livekit-ffi/.nanparc index 7b26423fd..e1b5b32d4 100644 --- a/livekit-ffi/.nanparc +++ b/livekit-ffi/.nanparc @@ -1,2 +1,2 @@ -version 0.12.15 +version 0.12.16 language rust diff --git a/livekit-ffi/CHANGELOG.md b/livekit-ffi/CHANGELOG.md index 1e06b01bb..3d586069e 100644 --- a/livekit-ffi/CHANGELOG.md +++ b/livekit-ffi/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [0.12.16] - 2025-03-06 + +### Fixed + +- Fixed metric report issue on audio filter where room_id is sometimes empty + ## [0.12.15] - 2025-03-05 ### Fixed diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 25a27f740..8b6e6cb64 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.12.15" +version = "0.12.16" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" From ff4c783a4c0cb31214ebbcac2c23cd332ac0058f Mon Sep 17 00:00:00 2001 From: Toby Pohlen Date: Thu, 13 Mar 2025 15:03:48 +0000 Subject: [PATCH 170/274] add missing with_room_config API on the access tokens (#602) --- Cargo.lock | 2 +- livekit-api/src/access_token.rs | 73 +++++++++++++++++++++++++++++++-- livekit-api/src/test_token.txt | 1 + 3 files changed, 72 insertions(+), 4 deletions(-) create mode 100644 livekit-api/src/test_token.txt diff --git a/Cargo.lock b/Cargo.lock index f8a5eac4c..2cdf711ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1655,7 +1655,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.12.15" +version = "0.12.16" dependencies = [ "console-subscriber", "dashmap", diff --git a/livekit-api/src/access_token.rs b/livekit-api/src/access_token.rs index 521313265..aa1d23f39 100644 --- a/livekit-api/src/access_token.rs +++ b/livekit-api/src/access_token.rs @@ -39,41 +39,60 @@ pub enum AccessTokenError { Encoding(#[from] jsonwebtoken::errors::Error), } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct VideoGrants { // actions on rooms + #[serde(default)] pub room_create: bool, + #[serde(default)] pub room_list: bool, + #[serde(default)] pub room_record: bool, // actions on a particular room + #[serde(default)] pub room_admin: bool, + #[serde(default)] pub room_join: bool, + #[serde(default)] pub room: String, // permissions within a room + #[serde(default = "default_true")] pub can_publish: bool, + #[serde(default = "default_true")] pub can_subscribe: bool, + #[serde(default = "default_true")] pub can_publish_data: bool, // TrackSource types that a participant may publish. // When set, it supercedes CanPublish. Only sources explicitly set here can be published + #[serde(default)] pub can_publish_sources: Vec, // keys keep track of each source // by default, a participant is not allowed to update its own metadata + #[serde(default)] pub can_update_own_metadata: bool, // actions on ingresses + #[serde(default)] pub ingress_admin: bool, // applies to all ingress // participant is not visible to other participants (useful when making bots) + #[serde(default)] pub hidden: bool, // indicates to the room that current participant is a recorder + #[serde(default)] pub recorder: bool, } +/// Used for fields that default to true instead of using the `Default` trait. +fn default_true() -> bool { + true +} + impl Default for VideoGrants { fn default() -> Self { Self { @@ -95,7 +114,7 @@ impl Default for VideoGrants { } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct SIPGrants { // manage sip resources @@ -110,7 +129,7 @@ impl Default for SIPGrants { } } -#[derive(Debug, Clone, Serialize, Default, Deserialize)] +#[derive(Debug, Clone, Serialize, Default, Deserialize, PartialEq)] #[serde(default)] #[serde(rename_all = "camelCase")] pub struct Claims { @@ -124,6 +143,7 @@ pub struct Claims { pub sip: SIPGrants, pub sha256: String, // Used to verify the integrity of the message body pub metadata: String, + pub room_config: Option, } #[derive(Clone)] @@ -159,9 +179,16 @@ impl AccessToken { sip: SIPGrants::default(), sha256: Default::default(), metadata: Default::default(), + room_config: Default::default(), }, } } + + #[cfg(test)] + pub fn from_parts(api_key: &str, api_secret: &str, claims: Claims) -> Self { + Self { api_key: api_key.to_owned(), api_secret: api_secret.to_owned(), claims } + } + pub fn new() -> Result { // Try to get the API Key and the Secret Key from the environment let (api_key, api_secret) = get_env_keys()?; @@ -204,6 +231,11 @@ impl AccessToken { self } + pub fn with_room_config(mut self, config: livekit_protocol::RoomConfiguration) -> Self { + self.claims.room_config = Some(config); + self + } + pub fn to_jwt(self) -> Result { if self.api_key.is_empty() || self.api_secret.is_empty() { return Err(AccessTokenError::InvalidKeys); @@ -271,14 +303,25 @@ mod tests { const TEST_API_KEY: &str = "myapikey"; const TEST_API_SECRET: &str = "thiskeyistotallyunsafe"; + const TEST_TOKEN: &str = include_str!("test_token.txt"); #[test] fn test_access_token() { + let room_config = livekit_protocol::RoomConfiguration { + name: "name".to_string(), + agents: vec![livekit_protocol::RoomAgentDispatch { + agent_name: "test-agent".to_string(), + metadata: "test-metadata".to_string(), + }], + ..Default::default() + }; + let token = AccessToken::with_api_key(TEST_API_KEY, TEST_API_SECRET) .with_ttl(Duration::from_secs(60)) .with_identity("test") .with_name("test") .with_grants(VideoGrants::default()) + .with_room_config(room_config.clone()) .to_jwt() .unwrap(); @@ -288,6 +331,7 @@ mod tests { assert_eq!(claims.sub, "test"); assert_eq!(claims.name, "test"); assert_eq!(claims.iss, TEST_API_KEY); + assert_eq!(claims.room_config, Some(room_config)); let incorrect_issuer = TokenVerifier::with_api_key("incorrect", TEST_API_SECRET); assert!(incorrect_issuer.verify(&token).is_err()); @@ -295,4 +339,27 @@ mod tests { let incorrect_token = TokenVerifier::with_api_key(TEST_API_KEY, "incorrect"); assert!(incorrect_token.verify(&token).is_err()); } + + #[test] + fn test_verify_token_with_room_config() { + let verifier = TokenVerifier::with_api_key(TEST_API_KEY, TEST_API_SECRET); + // This token was generated using the Python SDK. + let claims = verifier.verify(TEST_TOKEN).expect("Failed to verify token."); + + assert_eq!( + super::Claims { + sub: "identity".to_string(), + name: "name".to_string(), + room_config: Some(livekit_protocol::RoomConfiguration { + agents: vec![livekit_protocol::RoomAgentDispatch { + agent_name: "test-agent".to_string(), + metadata: "test-metadata".to_string(), + }], + ..Default::default() + }), + ..claims.clone() + }, + claims + ); + } } diff --git a/livekit-api/src/test_token.txt b/livekit-api/src/test_token.txt new file mode 100644 index 000000000..764311e00 --- /dev/null +++ b/livekit-api/src/test_token.txt @@ -0,0 +1 @@ +eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoibmFtZSIsInZpZGVvIjp7InJvb21Kb2luIjp0cnVlLCJyb29tIjoibXktcm9vbSIsImNhblB1Ymxpc2giOnRydWUsImNhblN1YnNjcmliZSI6dHJ1ZSwiY2FuUHVibGlzaERhdGEiOnRydWV9LCJyb29tQ29uZmlnIjp7ImFnZW50cyI6W3siYWdlbnROYW1lIjoidGVzdC1hZ2VudCIsIm1ldGFkYXRhIjoidGVzdC1tZXRhZGF0YSJ9XX0sInN1YiI6ImlkZW50aXR5IiwiaXNzIjoibXlhcGlrZXkiLCJuYmYiOjE3NDE4Njk2NzksImV4cCI6MTc0MTg5MTI3OX0.9_eOcZ1RNaRXxwdU36LTYoxsHtv_IhfhLk-kTd8MDY4 \ No newline at end of file From db42022b2e99b7474b647e91823af1ae6a834f8f Mon Sep 17 00:00:00 2001 From: Daisuke Murase Date: Mon, 17 Mar 2025 20:37:01 -0700 Subject: [PATCH 171/274] Move RPC handlers to room (#599) * move rpc data into room * update example as well * fix ffi as well * fmt * impl deprecated functions to keep compatibility * add a changeset * fix test --- .nanpa/savor-swell-year.kdl | 3 + examples/Cargo.lock | 193 +++++++++---- examples/rpc/src/main.rs | 13 +- livekit-api/src/access_token.rs | 4 + livekit-ffi/protocol/rpc.proto | 4 +- livekit-ffi/src/livekit.proto.rs | 5 +- livekit-ffi/src/server/participant.rs | 85 ------ livekit-ffi/src/server/requests.rs | 12 +- livekit-ffi/src/server/room.rs | 64 +++- livekit/src/room/mod.rs | 256 +++++++++++++++- .../src/room/participant/local_participant.rs | 273 +++--------------- livekit/src/room/participant/mod.rs | 3 + 12 files changed, 523 insertions(+), 392 deletions(-) create mode 100644 .nanpa/savor-swell-year.kdl diff --git a/.nanpa/savor-swell-year.kdl b/.nanpa/savor-swell-year.kdl new file mode 100644 index 000000000..0b5f1ec1a --- /dev/null +++ b/.nanpa/savor-swell-year.kdl @@ -0,0 +1,3 @@ +patch package="livekit" type="fixed" "Move RPC handlers to room" +patch package="livekit-ffi" type="fixed" "Move RPC handlers to room" + diff --git a/examples/Cargo.lock b/examples/Cargo.lock index 06ad21b8f..f2471b63d 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "ab_glyph" @@ -64,7 +64,7 @@ dependencies = [ "once_cell", "serde", "version_check", - "zerocopy", + "zerocopy 0.7.31", ] [[package]] @@ -356,7 +356,7 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.100", ] [[package]] @@ -584,7 +584,7 @@ checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.100", ] [[package]] @@ -951,7 +951,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.41", + "syn 2.0.100", ] [[package]] @@ -968,7 +968,7 @@ checksum = "5c6888cd161769d65134846d4d4981d5a6654307cc46ec83fb917e530aea5f84" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.100", ] [[package]] @@ -1041,7 +1041,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading 0.8.1", + "libloading 0.7.4", ] [[package]] @@ -1170,7 +1170,7 @@ checksum = "c2ad8cef1d801a4686bfd8919f0b30eac4c8e48968c437a6405ded4fb5272d2b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.100", ] [[package]] @@ -1467,7 +1467,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.100", ] [[package]] @@ -1529,10 +1529,22 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets 0.52.0", +] + [[package]] name = "gif" version = "0.12.0" @@ -2094,9 +2106,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "libc" -version = "0.2.151" +version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" [[package]] name = "libloading" @@ -2110,12 +2122,12 @@ dependencies = [ [[package]] name = "libloading" -version = "0.8.1" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-sys 0.48.0", + "windows-targets 0.52.0", ] [[package]] @@ -2142,7 +2154,7 @@ dependencies = [ [[package]] name = "libwebrtc" -version = "0.3.7" +version = "0.3.10" dependencies = [ "cxx", "jni", @@ -2185,11 +2197,12 @@ checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "livekit" -version = "0.6.0" +version = "0.7.6" dependencies = [ "chrono", "futures-util", "lazy_static", + "libloading 0.8.6", "libwebrtc", "livekit-api", "livekit-protocol", @@ -2206,7 +2219,7 @@ dependencies = [ [[package]] name = "livekit-api" -version = "0.4.0" +version = "0.4.2" dependencies = [ "async-tungstenite", "base64", @@ -2217,7 +2230,9 @@ dependencies = [ "livekit-runtime", "log", "parking_lot", + "pbjson-types", "prost 0.12.3", + "rand 0.9.0", "reqwest", "scopeguard", "serde", @@ -2231,7 +2246,7 @@ dependencies = [ [[package]] name = "livekit-protocol" -version = "0.3.5" +version = "0.3.9" dependencies = [ "futures-util", "livekit-runtime", @@ -2247,7 +2262,7 @@ dependencies = [ [[package]] name = "livekit-runtime" -version = "0.3.0" +version = "0.4.0" dependencies = [ "tokio", "tokio-stream", @@ -2382,7 +2397,7 @@ checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", "log", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.48.0", ] @@ -2604,7 +2619,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.100", ] [[package]] @@ -2710,7 +2725,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.100", ] [[package]] @@ -2788,7 +2803,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" dependencies = [ "base64ct", - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -2874,7 +2889,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.100", ] [[package]] @@ -2974,7 +2989,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" dependencies = [ "proc-macro2", - "syn 2.0.41", + "syn 2.0.100", ] [[package]] @@ -2989,9 +3004,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.70" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" dependencies = [ "unicode-ident", ] @@ -3039,7 +3054,7 @@ dependencies = [ "prost 0.12.3", "prost-types 0.12.3", "regex", - "syn 2.0.41", + "syn 2.0.100", "tempfile", "which", ] @@ -3067,7 +3082,7 @@ dependencies = [ "itertools 0.11.0", "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.100", ] [[package]] @@ -3099,9 +3114,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.33" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" dependencies = [ "proc-macro2", ] @@ -3113,8 +3128,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", + "zerocopy 0.8.23", ] [[package]] @@ -3124,7 +3150,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", ] [[package]] @@ -3133,7 +3169,16 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.11", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.1", ] [[package]] @@ -3192,7 +3237,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" dependencies = [ - "getrandom", + "getrandom 0.2.11", "libredox 0.0.1", "thiserror", ] @@ -3298,7 +3343,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" dependencies = [ "cc", - "getrandom", + "getrandom 0.2.11", "libc", "spin", "untrusted", @@ -3325,7 +3370,7 @@ dependencies = [ "livekit", "livekit-api", "log", - "rand", + "rand 0.8.5", "serde_json", "tokio", ] @@ -3540,7 +3585,7 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.100", ] [[package]] @@ -3741,9 +3786,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.41" +version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", @@ -3816,7 +3861,7 @@ checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.100", ] [[package]] @@ -3946,7 +3991,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.100", ] [[package]] @@ -4067,7 +4112,7 @@ dependencies = [ "indexmap 1.9.3", "pin-project", "pin-project-lite", - "rand", + "rand 0.8.5", "slab", "tokio", "tokio-util", @@ -4107,7 +4152,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.100", ] [[package]] @@ -4161,7 +4206,7 @@ dependencies = [ "httparse", "log", "native-tls", - "rand", + "rand 0.8.5", "rustls", "sha1", "thiserror", @@ -4182,7 +4227,7 @@ dependencies = [ "httparse", "log", "native-tls", - "rand", + "rand 0.8.5", "sha1", "thiserror", "url", @@ -4321,6 +4366,15 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasm-bindgen" version = "0.2.89" @@ -4342,7 +4396,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.100", "wasm-bindgen-shared", ] @@ -4376,7 +4430,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.100", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4504,7 +4558,7 @@ checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" [[package]] name = "webrtc-sys" -version = "0.3.5" +version = "0.3.7" dependencies = [ "cc", "cxx", @@ -4516,7 +4570,7 @@ dependencies = [ [[package]] name = "webrtc-sys-build" -version = "0.3.5" +version = "0.3.6" dependencies = [ "fs2", "regex", @@ -4602,7 +4656,7 @@ dependencies = [ "js-sys", "khronos-egl", "libc", - "libloading 0.8.1", + "libloading 0.8.6", "log", "metal", "naga", @@ -4980,6 +5034,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags 2.4.1", +] + [[package]] name = "x11-dl" version = "2.21.0" @@ -5031,7 +5094,16 @@ version = "0.7.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c4061bedbb353041c12f413700357bec76df2c7e2ca8e4df8bac24c6bf68e3d" dependencies = [ - "zerocopy-derive", + "zerocopy-derive 0.7.31", +] + +[[package]] +name = "zerocopy" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6" +dependencies = [ + "zerocopy-derive 0.8.23", ] [[package]] @@ -5042,7 +5114,18 @@ checksum = "b3c129550b3e6de3fd0ba67ba5c81818f9805e58b8d7fee80a3a59d2c9fc601a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.100", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", ] [[package]] diff --git a/examples/rpc/src/main.rs b/examples/rpc/src/main.rs index bc53ab69e..ee05890d2 100644 --- a/examples/rpc/src/main.rs +++ b/examples/rpc/src/main.rs @@ -81,7 +81,7 @@ async fn main() -> Result<(), Box> { } async fn register_receiver_methods(greeters_room: Arc, math_genius_room: Arc) { - greeters_room.local_participant().register_rpc_method("arrival".to_string(), |data| { + greeters_room.register_rpc_method("arrival".to_string(), |data| { Box::pin(async move { println!( "[{}] [Greeter] Oh {} arrived and said \"{}\"", @@ -94,7 +94,7 @@ async fn register_receiver_methods(greeters_room: Arc, math_genius_room: A }) }); - math_genius_room.local_participant().register_rpc_method( + math_genius_room.register_rpc_method( "square-root".to_string(), |data| { Box::pin(async move { @@ -118,7 +118,7 @@ async fn register_receiver_methods(greeters_room: Arc, math_genius_room: A }, ); - math_genius_room.local_participant().register_rpc_method("divide".to_string(), |data| { + math_genius_room.register_rpc_method("divide".to_string(), |data| { Box::pin(async move { let json_data: Value = serde_json::from_str(&data.payload).unwrap(); let dividend = json_data["dividend"].as_i64().unwrap(); @@ -137,8 +137,9 @@ async fn register_receiver_methods(greeters_room: Arc, math_genius_room: A }) }); - math_genius_room.local_participant().register_rpc_method("nested-calculation".to_string(), move |data| { - let math_genius_room = math_genius_room.clone(); + let inner = math_genius_room.clone(); + math_genius_room.register_rpc_method("nested-calculation".to_string(), move |data| { + let math_genius_room = inner.clone(); Box::pin(async move { let json_data: Value = serde_json::from_str(&data.payload).unwrap(); let number = json_data["number"].as_f64().unwrap(); @@ -269,7 +270,7 @@ async fn perform_division(room: &Arc) -> Result<(), Box) -> Result<(), Box> { - room.local_participant().register_rpc_method("provide-intermediate".to_string(), |data| { + room.register_rpc_method("provide-intermediate".to_string(), |data| { Box::pin(async move { let json_data: Value = serde_json::from_str(&data.payload).unwrap(); let original = json_data["original"].as_f64().unwrap(); diff --git a/livekit-api/src/access_token.rs b/livekit-api/src/access_token.rs index aa1d23f39..4fce13576 100644 --- a/livekit-api/src/access_token.rs +++ b/livekit-api/src/access_token.rs @@ -282,6 +282,10 @@ impl TokenVerifier { pub fn verify(&self, token: &str) -> Result { let mut validation = jsonwebtoken::Validation::new(jsonwebtoken::Algorithm::HS256); validation.validate_exp = true; + #[cfg(test)] // FIXME: TEST_TOKEN is expired, TODO: generate TEST_TOKEN at test runtime + { + validation.validate_exp = false; + } validation.validate_nbf = true; validation.set_issuer(&[&self.api_key]); diff --git a/livekit-ffi/protocol/rpc.proto b/livekit-ffi/protocol/rpc.proto index 19fd75f11..41cdadd8e 100644 --- a/livekit-ffi/protocol/rpc.proto +++ b/livekit-ffi/protocol/rpc.proto @@ -33,12 +33,12 @@ message PerformRpcRequest { } message RegisterRpcMethodRequest { - required uint64 local_participant_handle = 1; + required uint64 room_handle = 1; required string method = 2; } message UnregisterRpcMethodRequest { - required uint64 local_participant_handle = 1; + required uint64 room_handle = 1; required string method = 2; } diff --git a/livekit-ffi/src/livekit.proto.rs b/livekit-ffi/src/livekit.proto.rs index 214799ee7..bdf97e171 100644 --- a/livekit-ffi/src/livekit.proto.rs +++ b/livekit-ffi/src/livekit.proto.rs @@ -1,5 +1,4 @@ // @generated -// This file is @generated by prost-build. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FrameCryptor { @@ -4044,7 +4043,7 @@ pub struct PerformRpcRequest { #[derive(Clone, PartialEq, ::prost::Message)] pub struct RegisterRpcMethodRequest { #[prost(uint64, required, tag="1")] - pub local_participant_handle: u64, + pub room_handle: u64, #[prost(string, required, tag="2")] pub method: ::prost::alloc::string::String, } @@ -4052,7 +4051,7 @@ pub struct RegisterRpcMethodRequest { #[derive(Clone, PartialEq, ::prost::Message)] pub struct UnregisterRpcMethodRequest { #[prost(uint64, required, tag="1")] - pub local_participant_handle: u64, + pub room_handle: u64, #[prost(string, required, tag="2")] pub method: ::prost::alloc::string::String, } diff --git a/livekit-ffi/src/server/participant.rs b/livekit-ffi/src/server/participant.rs index e6d789876..f2817fa94 100644 --- a/livekit-ffi/src/server/participant.rs +++ b/livekit-ffi/src/server/participant.rs @@ -16,7 +16,6 @@ use std::sync::Arc; use livekit::prelude::*; use std::time::Duration; -use tokio::sync::oneshot; use crate::{ proto, @@ -77,88 +76,4 @@ impl FfiParticipant { server.watch_panic(handle); Ok(proto::PerformRpcResponse { async_id }) } - - pub fn register_rpc_method( - &self, - server: &'static FfiServer, - request: proto::RegisterRpcMethodRequest, - ) -> FfiResult { - let method = request.method.clone(); - - let local = match &self.participant { - Participant::Local(local) => local.clone(), - Participant::Remote(_) => { - return Err(FfiError::InvalidRequest("Expected local participant".into())) - } - }; - - let local_participant_handle = self.handle.clone(); - let room: Arc = self.room.clone(); - local.register_rpc_method(method.clone(), move |data| { - Box::pin({ - let room = room.clone(); - let method = method.clone(); - async move { - forward_rpc_method_invocation( - server, - room, - local_participant_handle, - method, - data, - ) - .await - } - }) - }); - Ok(proto::RegisterRpcMethodResponse {}) - } - - pub fn unregister_rpc_method( - &self, - request: proto::UnregisterRpcMethodRequest, - ) -> FfiResult { - let local = match &self.participant { - Participant::Local(local) => local.clone(), - Participant::Remote(_) => { - return Err(FfiError::InvalidRequest("Expected local participant".into())) - } - }; - - local.unregister_rpc_method(request.method); - - Ok(proto::UnregisterRpcMethodResponse {}) - } -} - -async fn forward_rpc_method_invocation( - server: &'static FfiServer, - room: Arc, - local_participant_handle: FfiHandleId, - method: String, - data: RpcInvocationData, -) -> Result { - let (tx, rx) = oneshot::channel(); - let invocation_id = server.next_id(); - - room.store_rpc_method_invocation_waiter(invocation_id, tx); - - let _ = server.send_event(proto::ffi_event::Message::RpcMethodInvocation( - proto::RpcMethodInvocationEvent { - local_participant_handle: local_participant_handle as u64, - invocation_id, - method, - request_id: data.request_id, - caller_identity: data.caller_identity.into(), - payload: data.payload, - response_timeout_ms: data.response_timeout.as_millis() as u32, - }, - )); - - rx.await.unwrap_or_else(|_| { - Err(RpcError { - code: RpcErrorCode::ApplicationError as u32, - message: "Error from method handler".to_string(), - data: None, - }) - }) } diff --git a/livekit-ffi/src/server/requests.rs b/livekit-ffi/src/server/requests.rs index 45e0a712a..b8cb0db99 100644 --- a/livekit-ffi/src/server/requests.rs +++ b/livekit-ffi/src/server/requests.rs @@ -27,7 +27,7 @@ use super::{ audio_source, audio_stream, colorcvt, participant::FfiParticipant, resampler, - room::{self, FfiPublication, FfiTrack}, + room::{self, FfiPublication, FfiRoom, FfiTrack}, video_source, video_stream, FfiError, FfiResult, FfiServer, }; use crate::proto; @@ -967,18 +967,16 @@ fn on_register_rpc_method( server: &'static FfiServer, request: proto::RegisterRpcMethodRequest, ) -> FfiResult { - let ffi_participant = - server.retrieve_handle::(request.local_participant_handle)?.clone(); - return ffi_participant.register_rpc_method(server, request); + let ffi_room = server.retrieve_handle::(request.room_handle)?.clone(); + ffi_room.inner.register_rpc_method(server, request) } fn on_unregister_rpc_method( server: &'static FfiServer, request: proto::UnregisterRpcMethodRequest, ) -> FfiResult { - let ffi_participant = - server.retrieve_handle::(request.local_participant_handle)?.clone(); - return ffi_participant.unregister_rpc_method(request); + let ffi_room = server.retrieve_handle::(request.room_handle)?.clone(); + ffi_room.inner.unregister_rpc_method(request) } fn on_rpc_method_invocation_response( diff --git a/livekit-ffi/src/server/room.rs b/livekit-ffi/src/server/room.rs index d8399967b..36e71adc1 100644 --- a/livekit-ffi/src/server/room.rs +++ b/livekit-ffi/src/server/room.rs @@ -19,7 +19,7 @@ use std::{collections::HashSet, slice, sync::Arc}; use livekit::ChatMessage; use livekit::{prelude::*, registered_audio_filter_plugins}; use livekit_protocol as lk_proto; -use parking_lot::Mutex; +use parking_lot::{Mutex, RwLock}; use tokio::sync::{broadcast, mpsc, oneshot, Mutex as AsyncMutex}; use tokio::task::JoinHandle; @@ -71,6 +71,7 @@ pub struct RoomInner { // Used to forward RPC method invocation to the FfiClient and collect their results rpc_method_invocation_waiters: Mutex>>>, + local_participant_handle: RwLock>, // ws url associated with this room url: String, @@ -175,11 +176,13 @@ impl FfiRoom { pending_unpublished_tracks: Default::default(), track_handle_lookup: Default::default(), rpc_method_invocation_waiters: Default::default(), + local_participant_handle: Default::default(), url: connect.url, }); let (local_info, remote_infos) = build_initial_states(server, &inner, participants_with_tracks); + *inner.local_participant_handle.write() = Some(local_info.handle.id); // Send callback let ffi_room = Self { inner: inner.clone(), handle: Default::default() }; @@ -786,6 +789,32 @@ impl RoomInner { proto::SendStreamTrailerResponse { async_id } } + pub fn register_rpc_method( + self: &Arc, + server: &'static FfiServer, + request: proto::RegisterRpcMethodRequest, + ) -> FfiResult { + let method = request.method.clone(); + + let room = self.clone(); + self.room.register_rpc_method(method.clone(), move |data| { + Box::pin({ + let room = room.clone(); + let method = method.clone(); + async move { forward_rpc_method_invocation(server, room, method, data).await } + }) + }); + Ok(proto::RegisterRpcMethodResponse {}) + } + + pub fn unregister_rpc_method( + &self, + request: proto::UnregisterRpcMethodRequest, + ) -> FfiResult { + self.room.unregister_rpc_method(request.method); + Ok(proto::UnregisterRpcMethodResponse {}) + } + pub fn store_rpc_method_invocation_waiter( &self, invocation_id: u64, @@ -1391,3 +1420,36 @@ fn build_initial_states( remote_infos, ) } + +async fn forward_rpc_method_invocation( + server: &'static FfiServer, + room: Arc, + method: String, + data: RpcInvocationData, +) -> Result { + let (tx, rx) = oneshot::channel(); + let invocation_id = server.next_id(); + let local_participant_handle = room.local_participant_handle.read().unwrap(); + + room.store_rpc_method_invocation_waiter(invocation_id, tx); + + let _ = server.send_event(proto::ffi_event::Message::RpcMethodInvocation( + proto::RpcMethodInvocationEvent { + local_participant_handle, + invocation_id, + method, + request_id: data.request_id, + caller_identity: data.caller_identity.into(), + payload: data.payload, + response_timeout_ms: data.response_timeout.as_millis() as u32, + }, + )); + + rx.await.unwrap_or_else(|_| { + Err(RpcError { + code: RpcErrorCode::ApplicationError as u32, + message: "Error from method handler".to_string(), + data: None, + }) + }) +} diff --git a/livekit/src/room/mod.rs b/livekit/src/room/mod.rs index 938743f09..3ac898ea5 100644 --- a/livekit/src/room/mod.rs +++ b/livekit/src/room/mod.rs @@ -12,10 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::{collections::HashMap, fmt::Debug, sync::Arc, time::Duration}; +use std::{collections::HashMap, fmt::Debug, pin::Pin, sync::Arc, time::Duration}; +use futures_util::Future; use libwebrtc::{ - native::frame_cryptor::EncryptionState, + native::{create_random_uuid, frame_cryptor::EncryptionState}, prelude::{ ContinualGatheringPolicy, IceTransportsType, MediaStream, MediaStreamTrack, RtcConfiguration, @@ -27,9 +28,11 @@ use livekit_api::signal_client::{SignalOptions, SignalSdkOptions}; use livekit_protocol as proto; use livekit_protocol::observer::Dispatcher; use livekit_runtime::JoinHandle; -use parking_lot::RwLock; +use parking_lot::{Mutex, RwLock}; +use participant::MAX_PAYLOAD_BYTES; pub use proto::DisconnectReason; use proto::{promise::Promise, SignalTarget}; +use semver::Version; use thiserror::Error; use tokio::sync::{mpsc, oneshot, Mutex as AsyncMutex}; @@ -268,6 +271,28 @@ pub struct ChatMessage { pub generated: Option, } +type RpcHandler = Arc< + dyn Fn(RpcInvocationData) -> Pin> + Send>> + + Send + + Sync, +>; + +struct RpcState { + pending_acks: HashMap>, + pending_responses: HashMap>>, + handlers: HashMap, +} + +impl RpcState { + fn new() -> Self { + Self { + pending_acks: HashMap::new(), + pending_responses: HashMap::new(), + handlers: HashMap::new(), + } + } +} + #[derive(Debug, Clone)] pub struct RpcRequest { pub destination_identity: String, @@ -391,6 +416,7 @@ pub(crate) struct RoomSession { remote_participants: RwLock>, e2ee_manager: E2eeManager, room_task: AsyncMutex, oneshot::Sender<()>)>>, + rpc_state: Mutex, } impl Debug for RoomSession { @@ -534,7 +560,9 @@ impl Room { dispatcher: dispatcher.clone(), e2ee_manager: e2ee_manager.clone(), room_task: Default::default(), + rpc_state: Mutex::new(RpcState::new()), }); + inner.local_participant.set_session(Arc::downgrade(&inner)); e2ee_manager.on_state_changed({ let dispatcher = dispatcher.clone(); @@ -649,6 +677,21 @@ impl Room { DataPacketKind::Reliable => self.inner.info.read().reliable_dc_options.clone(), } } + + pub fn register_rpc_method( + &self, + method: String, + handler: impl Fn(RpcInvocationData) -> Pin> + Send>> + + Send + + Sync + + 'static, + ) { + self.inner.register_rpc_method(method, handler); + } + + pub fn unregister_rpc_method(&self, method: String) { + self.inner.unregister_rpc_method(method); + } } impl RoomSession { @@ -731,9 +774,9 @@ impl RoomSession { log::warn!("Received RPC request with null caller identity"); return Ok(()); } - let local_participant = self.local_participant.clone(); + let inner = self.clone(); livekit_runtime::spawn(async move { - local_participant + inner .handle_incoming_rpc_request( caller_identity.unwrap(), request_id, @@ -746,10 +789,10 @@ impl RoomSession { }); } EngineEvent::RpcResponse { request_id, payload, error } => { - self.local_participant.handle_incoming_rpc_response(request_id, payload, error); + self.handle_incoming_rpc_response(request_id, payload, error); } EngineEvent::RpcAck { request_id } => { - self.local_participant.handle_incoming_rpc_ack(request_id); + self.handle_incoming_rpc_ack(request_id); } EngineEvent::SpeakersChanged { speakers } => self.handle_speakers_changed(speakers), EngineEvent::ConnectionQuality { updates } => { @@ -1490,6 +1533,205 @@ impl RoomSession { } return self.get_participant_by_identity(identity).map(Participant::Remote); } + + pub(crate) fn register_rpc_method( + &self, + method: String, + handler: impl Fn(RpcInvocationData) -> Pin> + Send>> + + Send + + Sync + + 'static, + ) { + self.rpc_state.lock().handlers.insert(method, Arc::new(handler)); + } + + pub(crate) fn unregister_rpc_method(&self, method: String) { + self.rpc_state.lock().handlers.remove(&method); + } + + pub(crate) async fn perform_rpc(&self, data: PerformRpcData) -> Result { + let max_round_trip_latency = Duration::from_millis(2000); + + if data.payload.len() > MAX_PAYLOAD_BYTES { + return Err(RpcError::built_in(RpcErrorCode::RequestPayloadTooLarge, None)); + } + + if let Some(server_info) = + self.rtc_engine.session().signal_client().join_response().server_info + { + if !server_info.version.is_empty() { + let server_version = Version::parse(&server_info.version).unwrap(); + let min_required_version = Version::parse("1.8.0").unwrap(); + if server_version < min_required_version { + return Err(RpcError::built_in(RpcErrorCode::UnsupportedServer, None)); + } + } + } + + let id = create_random_uuid(); + let (ack_tx, ack_rx) = oneshot::channel(); + let (response_tx, response_rx) = oneshot::channel(); + + match self + .local_participant + .publish_rpc_request(RpcRequest { + destination_identity: data.destination_identity.clone(), + id: id.clone(), + method: data.method.clone(), + payload: data.payload.clone(), + response_timeout: data.response_timeout - max_round_trip_latency, + version: 1, + }) + .await + { + Ok(_) => { + let mut rpc_state = self.rpc_state.lock(); + rpc_state.pending_acks.insert(id.clone(), ack_tx); + rpc_state.pending_responses.insert(id.clone(), response_tx); + } + Err(e) => { + log::error!("Failed to publish RPC request: {}", e); + return Err(RpcError::built_in(RpcErrorCode::SendFailed, Some(e.to_string()))); + } + } + + // Wait for ack timeout + match tokio::time::timeout(max_round_trip_latency, ack_rx).await { + Err(_) => { + let mut rpc_state = self.rpc_state.lock(); + rpc_state.pending_acks.remove(&id); + rpc_state.pending_responses.remove(&id); + return Err(RpcError::built_in(RpcErrorCode::ConnectionTimeout, None)); + } + Ok(_) => { + // Ack received, continue to wait for response + } + } + + // Wait for response timout + let response = match tokio::time::timeout(data.response_timeout, response_rx).await { + Err(_) => { + self.rpc_state.lock().pending_responses.remove(&id); + return Err(RpcError::built_in(RpcErrorCode::ResponseTimeout, None)); + } + Ok(result) => result, + }; + + match response { + Err(_) => { + // Something went wrong locally + Err(RpcError::built_in(RpcErrorCode::RecipientDisconnected, None)) + } + Ok(Err(e)) => { + // RPC error from remote, forward it + Err(e) + } + Ok(Ok(payload)) => { + // Successful response + Ok(payload) + } + } + } + + fn handle_incoming_rpc_ack(&self, request_id: String) { + let mut rpc_state = self.rpc_state.lock(); + if let Some(tx) = rpc_state.pending_acks.remove(&request_id) { + let _ = tx.send(()); + } else { + log::error!("Ack received for unexpected RPC request: {}", request_id); + } + } + + fn handle_incoming_rpc_response( + &self, + request_id: String, + payload: Option, + error: Option, + ) { + let mut rpc_state = self.rpc_state.lock(); + if let Some(tx) = rpc_state.pending_responses.remove(&request_id) { + let _ = tx.send(match error { + Some(e) => Err(RpcError::from_proto(e)), + None => Ok(payload.unwrap_or_default()), + }); + } else { + log::error!("Response received for unexpected RPC request: {}", request_id); + } + } + + async fn handle_incoming_rpc_request( + &self, + caller_identity: ParticipantIdentity, + request_id: String, + method: String, + payload: String, + response_timeout: Duration, + version: u32, + ) { + if let Err(e) = self + .local_participant + .publish_rpc_ack(RpcAck { + destination_identity: caller_identity.to_string(), + request_id: request_id.clone(), + }) + .await + { + log::error!("Failed to publish RPC ACK: {:?}", e); + } + + let caller_identity_2 = caller_identity.clone(); + let request_id_2 = request_id.clone(); + + let response = if version != 1 { + Err(RpcError::built_in(RpcErrorCode::UnsupportedVersion, None)) + } else { + let handler = self.rpc_state.lock().handlers.get(&method).cloned(); + + match handler { + Some(handler) => { + match tokio::task::spawn(async move { + handler(RpcInvocationData { + request_id: request_id.clone(), + caller_identity: caller_identity.clone(), + payload: payload.clone(), + response_timeout, + }) + .await + }) + .await + { + Ok(result) => result, + Err(e) => { + log::error!("RPC method handler returned an error: {:?}", e); + Err(RpcError::built_in(RpcErrorCode::ApplicationError, None)) + } + } + } + None => Err(RpcError::built_in(RpcErrorCode::UnsupportedMethod, None)), + } + }; + + let (payload, error) = match response { + Ok(response_payload) if response_payload.len() <= MAX_PAYLOAD_BYTES => { + (Some(response_payload), None) + } + Ok(_) => (None, Some(RpcError::built_in(RpcErrorCode::ResponsePayloadTooLarge, None))), + Err(e) => (None, Some(e.into())), + }; + + if let Err(e) = self + .local_participant + .publish_rpc_response(RpcResponse { + destination_identity: caller_identity_2.to_string(), + request_id: request_id_2, + payload, + error: error.map(|e| e.to_proto()), + }) + .await + { + log::error!("Failed to publish RPC response: {:?}", e); + } + } } fn unpack_stream_id(stream_id: &str) -> Option<(&str, &str)> { diff --git a/livekit/src/room/participant/local_participant.rs b/livekit/src/room/participant/local_participant.rs index 67605ba29..e7fbc7170 100644 --- a/livekit/src/room/participant/local_participant.rs +++ b/livekit/src/room/participant/local_participant.rs @@ -12,34 +12,34 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::{collections::HashMap, fmt::Debug, pin::Pin, sync::Arc, time::Duration}; +use std::{ + collections::HashMap, + fmt::Debug, + future::Future, + pin::Pin, + sync::{Arc, Weak}, + time::Duration, +}; -use super::{ConnectionQuality, ParticipantInner, ParticipantKind, ParticipantTrackPermission}; +use super::{ + ConnectionQuality, ParticipantInner, ParticipantKind, ParticipantTrackPermission, RoomSession, +}; use crate::{ e2ee::EncryptionType, options::{self, compute_video_encodings, video_layers_from_encodings, TrackPublishOptions}, prelude::*, - room::participant::rpc::{RpcError, RpcErrorCode, RpcInvocationData, MAX_PAYLOAD_BYTES}, + room::participant::rpc::RpcError, rtc_engine::{EngineError, RtcEngine}, ChatMessage, DataPacket, RpcAck, RpcRequest, RpcResponse, SipDTMF, Transcription, }; use chrono::Utc; -use futures_util::Future; use libwebrtc::{native::create_random_uuid, rtp_parameters::RtpEncodingParameters}; use livekit_api::signal_client::SignalError; use livekit_protocol as proto; use livekit_runtime::timeout; -use parking_lot::Mutex; +use parking_lot::{Mutex, RwLock}; use proto::request_response::Reason; -use semver::Version; -use tokio::sync::oneshot; - -type RpcHandler = Arc< - dyn Fn(RpcInvocationData) -> Pin> + Send>> - + Send - + Sync, ->; const REQUEST_TIMEOUT: Duration = Duration::from_secs(5); @@ -52,27 +52,12 @@ struct LocalEvents { local_track_unpublished: Mutex>, } -struct RpcState { - pending_acks: HashMap>, - pending_responses: HashMap>>, - handlers: HashMap, -} - -impl RpcState { - fn new() -> Self { - Self { - pending_acks: HashMap::new(), - pending_responses: HashMap::new(), - handlers: HashMap::new(), - } - } -} struct LocalInfo { events: LocalEvents, encryption_type: EncryptionType, - rpc_state: Mutex, all_participants_allowed: Mutex, track_permissions: Mutex>, + session: RwLock>>, } #[derive(Clone)] @@ -107,13 +92,21 @@ impl LocalParticipant { local: Arc::new(LocalInfo { events: LocalEvents::default(), encryption_type, - rpc_state: Mutex::new(RpcState::new()), all_participants_allowed: Mutex::new(true), track_permissions: Mutex::new(vec![]), + session: Default::default(), }), } } + pub(crate) fn set_session(&self, session: Weak) { + *self.local.session.write() = Some(session); + } + + pub(crate) fn session(&self) -> Option> { + self.local.session.read().as_ref().and_then(|s| s.upgrade()) + } + pub(crate) fn internal_track_publications(&self) -> HashMap { self.inner.track_publications.read().clone() } @@ -544,7 +537,7 @@ impl LocalParticipant { self.inner.rtc_engine.publish_data(data, DataPacketKind::Reliable).await.map_err(Into::into) } - async fn publish_rpc_request(&self, rpc_request: RpcRequest) -> RoomResult<()> { + pub(crate) async fn publish_rpc_request(&self, rpc_request: RpcRequest) -> RoomResult<()> { let destination_identities = vec![rpc_request.destination_identity]; let rpc_request_message = proto::RpcRequest { id: rpc_request.id, @@ -564,7 +557,7 @@ impl LocalParticipant { self.inner.rtc_engine.publish_data(data, DataPacketKind::Reliable).await.map_err(Into::into) } - async fn publish_rpc_response(&self, rpc_response: RpcResponse) -> RoomResult<()> { + pub(crate) async fn publish_rpc_response(&self, rpc_response: RpcResponse) -> RoomResult<()> { let destination_identities = vec![rpc_response.destination_identity]; let rpc_response_message = proto::RpcResponse { request_id: rpc_response.request_id, @@ -588,7 +581,7 @@ impl LocalParticipant { self.inner.rtc_engine.publish_data(data, DataPacketKind::Reliable).await.map_err(Into::into) } - async fn publish_rpc_ack(&self, rpc_ack: RpcAck) -> RoomResult<()> { + pub(crate) async fn publish_rpc_ack(&self, rpc_ack: RpcAck) -> RoomResult<()> { let destination_identities = vec![rpc_ack.destination_identity]; let rpc_ack_message = proto::RpcAck { request_id: rpc_ack.request_id, ..Default::default() }; @@ -602,6 +595,25 @@ impl LocalParticipant { self.inner.rtc_engine.publish_data(data, DataPacketKind::Reliable).await.map_err(Into::into) } + #[deprecated(note = "Use `room.register_rpc_method` instead.")] + pub fn register_rpc_method( + &self, + method: String, + handler: impl Fn(RpcInvocationData) -> Pin> + Send>> + + Send + + Sync + + 'static, + ) { + let Some(session) = self.session() else { return }; + session.register_rpc_method(method, handler); + } + + #[deprecated(note = "Use `room.unregister_rpc_method` instead.")] + pub fn unregister_rpc_method(&self, method: String) { + let Some(session) = self.session() else { return }; + session.unregister_rpc_method(method); + } + pub(crate) async fn update_track_subscription_permissions(&self) { let all_participants_allowed = *self.local.all_participants_allowed.lock(); let track_permissions = self @@ -690,198 +702,7 @@ impl LocalParticipant { } pub async fn perform_rpc(&self, data: PerformRpcData) -> Result { - let max_round_trip_latency = Duration::from_millis(2000); - - if data.payload.len() > MAX_PAYLOAD_BYTES { - return Err(RpcError::built_in(RpcErrorCode::RequestPayloadTooLarge, None)); - } - - if let Some(server_info) = - self.inner.rtc_engine.session().signal_client().join_response().server_info - { - if !server_info.version.is_empty() { - let server_version = Version::parse(&server_info.version).unwrap(); - let min_required_version = Version::parse("1.8.0").unwrap(); - if server_version < min_required_version { - return Err(RpcError::built_in(RpcErrorCode::UnsupportedServer, None)); - } - } - } - - let id = create_random_uuid(); - let (ack_tx, ack_rx) = oneshot::channel(); - let (response_tx, response_rx) = oneshot::channel(); - - match self - .publish_rpc_request(RpcRequest { - destination_identity: data.destination_identity.clone(), - id: id.clone(), - method: data.method.clone(), - payload: data.payload.clone(), - response_timeout: data.response_timeout - max_round_trip_latency, - version: 1, - }) - .await - { - Ok(_) => { - let mut rpc_state = self.local.rpc_state.lock(); - rpc_state.pending_acks.insert(id.clone(), ack_tx); - rpc_state.pending_responses.insert(id.clone(), response_tx); - } - Err(e) => { - log::error!("Failed to publish RPC request: {}", e); - return Err(RpcError::built_in(RpcErrorCode::SendFailed, Some(e.to_string()))); - } - } - - // Wait for ack timeout - match tokio::time::timeout(max_round_trip_latency, ack_rx).await { - Err(_) => { - let mut rpc_state = self.local.rpc_state.lock(); - rpc_state.pending_acks.remove(&id); - rpc_state.pending_responses.remove(&id); - return Err(RpcError::built_in(RpcErrorCode::ConnectionTimeout, None)); - } - Ok(_) => { - // Ack received, continue to wait for response - } - } - - // Wait for response timout - let response = match tokio::time::timeout(data.response_timeout, response_rx).await { - Err(_) => { - self.local.rpc_state.lock().pending_responses.remove(&id); - return Err(RpcError::built_in(RpcErrorCode::ResponseTimeout, None)); - } - Ok(result) => result, - }; - - match response { - Err(_) => { - // Something went wrong locally - Err(RpcError::built_in(RpcErrorCode::RecipientDisconnected, None)) - } - Ok(Err(e)) => { - // RPC error from remote, forward it - Err(e) - } - Ok(Ok(payload)) => { - // Successful response - Ok(payload) - } - } - } - - pub fn register_rpc_method( - &self, - method: String, - handler: impl Fn(RpcInvocationData) -> Pin> + Send>> - + Send - + Sync - + 'static, - ) { - self.local.rpc_state.lock().handlers.insert(method, Arc::new(handler)); - } - - pub fn unregister_rpc_method(&self, method: String) { - self.local.rpc_state.lock().handlers.remove(&method); - } - - pub(crate) fn handle_incoming_rpc_ack(&self, request_id: String) { - let mut rpc_state = self.local.rpc_state.lock(); - if let Some(tx) = rpc_state.pending_acks.remove(&request_id) { - let _ = tx.send(()); - } else { - log::error!("Ack received for unexpected RPC request: {}", request_id); - } - } - - pub(crate) fn handle_incoming_rpc_response( - &self, - request_id: String, - payload: Option, - error: Option, - ) { - let mut rpc_state = self.local.rpc_state.lock(); - if let Some(tx) = rpc_state.pending_responses.remove(&request_id) { - let _ = tx.send(match error { - Some(e) => Err(RpcError::from_proto(e)), - None => Ok(payload.unwrap_or_default()), - }); - } else { - log::error!("Response received for unexpected RPC request: {}", request_id); - } - } - - pub(crate) async fn handle_incoming_rpc_request( - &self, - caller_identity: ParticipantIdentity, - request_id: String, - method: String, - payload: String, - response_timeout: Duration, - version: u32, - ) { - if let Err(e) = self - .publish_rpc_ack(RpcAck { - destination_identity: caller_identity.to_string(), - request_id: request_id.clone(), - }) - .await - { - log::error!("Failed to publish RPC ACK: {:?}", e); - } - - let caller_identity_2 = caller_identity.clone(); - let request_id_2 = request_id.clone(); - - let response = if version != 1 { - Err(RpcError::built_in(RpcErrorCode::UnsupportedVersion, None)) - } else { - let handler = self.local.rpc_state.lock().handlers.get(&method).cloned(); - - match handler { - Some(handler) => { - match tokio::task::spawn(async move { - handler(RpcInvocationData { - request_id: request_id.clone(), - caller_identity: caller_identity.clone(), - payload: payload.clone(), - response_timeout, - }) - .await - }) - .await - { - Ok(result) => result, - Err(e) => { - log::error!("RPC method handler returned an error: {:?}", e); - Err(RpcError::built_in(RpcErrorCode::ApplicationError, None)) - } - } - } - None => Err(RpcError::built_in(RpcErrorCode::UnsupportedMethod, None)), - } - }; - - let (payload, error) = match response { - Ok(response_payload) if response_payload.len() <= MAX_PAYLOAD_BYTES => { - (Some(response_payload), None) - } - Ok(_) => (None, Some(RpcError::built_in(RpcErrorCode::ResponsePayloadTooLarge, None))), - Err(e) => (None, Some(e.into())), - }; - - if let Err(e) = self - .publish_rpc_response(RpcResponse { - destination_identity: caller_identity_2.to_string(), - request_id: request_id_2, - payload, - error: error.map(|e| e.to_proto()), - }) - .await - { - log::error!("Failed to publish RPC response: {:?}", e); - } + let session = self.session().unwrap(); + session.perform_rpc(data).await } } diff --git a/livekit/src/room/participant/mod.rs b/livekit/src/room/participant/mod.rs index 30cf0544c..53de26c65 100644 --- a/livekit/src/room/participant/mod.rs +++ b/livekit/src/room/participant/mod.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::sync::Weak; use std::{collections::HashMap, fmt::Debug, sync::Arc}; use livekit_protocol as proto; @@ -29,6 +30,8 @@ pub use local_participant::*; pub use remote_participant::*; pub use rpc::*; +use super::RoomSession; + #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum ConnectionQuality { Excellent, From e2aeb65fc0c6495f5902043e37eb640ac7d962cc Mon Sep 17 00:00:00 2001 From: "ilo-nanpa[bot]" <174912985+ilo-nanpa[bot]@users.noreply.github.com> Date: Tue, 18 Mar 2025 03:37:56 +0000 Subject: [PATCH 172/274] nanpa: bump --- .nanpa/savor-swell-year.kdl | 3 --- Cargo.toml | 4 ++-- livekit-ffi/.nanparc | 2 +- livekit-ffi/CHANGELOG.md | 6 ++++++ livekit-ffi/Cargo.toml | 2 +- livekit/.nanparc | 2 +- livekit/CHANGELOG.md | 6 ++++++ livekit/Cargo.toml | 2 +- 8 files changed, 18 insertions(+), 9 deletions(-) delete mode 100644 .nanpa/savor-swell-year.kdl diff --git a/.nanpa/savor-swell-year.kdl b/.nanpa/savor-swell-year.kdl deleted file mode 100644 index 0b5f1ec1a..000000000 --- a/.nanpa/savor-swell-year.kdl +++ /dev/null @@ -1,3 +0,0 @@ -patch package="livekit" type="fixed" "Move RPC handlers to room" -patch package="livekit-ffi" type="fixed" "Move RPC handlers to room" - diff --git a/Cargo.toml b/Cargo.toml index 75b6aab19..186134d3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,10 +19,10 @@ imgproc = { version = "0.3.12", path = "imgproc" } yuv-sys = { version = "0.3.7", path = "yuv-sys" } libwebrtc = { version = "0.3.10", path = "libwebrtc" } livekit-api = { version = "0.4.2", path = "livekit-api" } -livekit-ffi = { version = "0.12.16", path = "livekit-ffi" } +livekit-ffi = { version = "0.12.17", path = "livekit-ffi" } livekit-protocol = { version = "0.3.9", path = "livekit-protocol" } livekit-runtime = { version = "0.4.0", path = "livekit-runtime" } -livekit = { version = "0.7.6", path = "livekit" } +livekit = { version = "0.7.7", path = "livekit" } soxr-sys = { version = "0.1.0", path = "soxr-sys" } webrtc-sys-build = { version = "0.3.6", path = "webrtc-sys/build" } webrtc-sys = { version = "0.3.7", path = "webrtc-sys" } diff --git a/livekit-ffi/.nanparc b/livekit-ffi/.nanparc index e1b5b32d4..03c3b04bc 100644 --- a/livekit-ffi/.nanparc +++ b/livekit-ffi/.nanparc @@ -1,2 +1,2 @@ -version 0.12.16 +version 0.12.17 language rust diff --git a/livekit-ffi/CHANGELOG.md b/livekit-ffi/CHANGELOG.md index 3d586069e..96fe84930 100644 --- a/livekit-ffi/CHANGELOG.md +++ b/livekit-ffi/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [0.12.17] - 2025-03-18 + +### Fixed + +- Move RPC handlers to room + ## [0.12.16] - 2025-03-06 ### Fixed diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 8b6e6cb64..8acbef267 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.12.16" +version = "0.12.17" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" diff --git a/livekit/.nanparc b/livekit/.nanparc index 4f0ae7374..ba054a383 100644 --- a/livekit/.nanparc +++ b/livekit/.nanparc @@ -1,2 +1,2 @@ -version 0.7.6 +version 0.7.7 language rust diff --git a/livekit/CHANGELOG.md b/livekit/CHANGELOG.md index fa844ed40..7f4850e85 100644 --- a/livekit/CHANGELOG.md +++ b/livekit/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [0.7.7] - 2025-03-18 + +### Fixed + +- Move RPC handlers to room + ## [0.7.6] - 2025-02-28 ### Added diff --git a/livekit/Cargo.toml b/livekit/Cargo.toml index d3f612d19..66f1849ba 100644 --- a/livekit/Cargo.toml +++ b/livekit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit" -version = "0.7.6" +version = "0.7.7" edition = "2021" license = "Apache-2.0" description = "Rust Client SDK for LiveKit" From dd06b5bd47113a9b258c38354e110efc7c453923 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 20 Mar 2025 06:13:58 -0600 Subject: [PATCH 173/274] Add i420_to_nv12 (#605) --- .nanpa/i420_to_nv12.kdl | 1 + libwebrtc/src/native/yuv_helper.rs | 45 +++++++++++++++++++++++++ webrtc-sys/include/livekit/yuv_helper.h | 17 ++++++++++ webrtc-sys/src/yuv_helper.rs | 15 +++++++++ 4 files changed, 78 insertions(+) create mode 100644 .nanpa/i420_to_nv12.kdl diff --git a/.nanpa/i420_to_nv12.kdl b/.nanpa/i420_to_nv12.kdl new file mode 100644 index 000000000..cae0c2e70 --- /dev/null +++ b/.nanpa/i420_to_nv12.kdl @@ -0,0 +1 @@ +minor type="added" "Add i420_to_nv12" diff --git a/libwebrtc/src/native/yuv_helper.rs b/libwebrtc/src/native/yuv_helper.rs index e14af5b1f..4295d57dd 100644 --- a/libwebrtc/src/native/yuv_helper.rs +++ b/libwebrtc/src/native/yuv_helper.rs @@ -245,6 +245,51 @@ i420_to_rgba!(i420_to_bgra); i420_to_rgba!(i420_to_abgr); i420_to_rgba!(i420_to_rgba); +pub fn i420_to_nv12( + src_y: &[u8], + src_stride_y: u32, + src_u: &[u8], + src_stride_u: u32, + src_v: &[u8], + src_stride_v: u32, + dst_y: &mut [u8], + dst_stride_y: u32, + dst_uv: &mut [u8], + dst_stride_uv: u32, + width: i32, + height: i32, +) { + i420_assert_safety( + src_y, + src_stride_y, + src_u, + src_stride_u, + src_v, + src_stride_v, + width, + height, + ); + nv12_assert_safety(dst_y, dst_stride_y, dst_uv, dst_stride_uv, width, height); + + unsafe { + yuv_sys::ffi::i420_to_nv12( + src_y.as_ptr(), + src_stride_y as i32, + src_u.as_ptr(), + src_stride_u as i32, + src_v.as_ptr(), + src_stride_v as i32, + dst_y.as_mut_ptr(), + dst_stride_y as i32, + dst_uv.as_mut_ptr(), + dst_stride_uv as i32, + width, + height, + ) + .unwrap(); + } +} + pub fn nv12_to_i420( src_y: &[u8], src_stride_y: u32, diff --git a/webrtc-sys/include/livekit/yuv_helper.h b/webrtc-sys/include/livekit/yuv_helper.h index acec26739..10132273d 100644 --- a/webrtc-sys/include/livekit/yuv_helper.h +++ b/webrtc-sys/include/livekit/yuv_helper.h @@ -164,6 +164,23 @@ static void nv12_to_i420(const uint8_t* src_y, dst_v, dst_stride_v, width, height)); } +static void i420_to_nv12(const uint8_t* src_y, + int src_stride_y, + const uint8_t* src_u, + int src_stride_u, + uint8_t* src_v, + int src_stride_v, + uint8_t* dst_y, + int dst_stride_y, + uint8_t* dst_uv, + int dst_stride_uv, + int width, + int height) { + THROW_ON_ERROR(webrtc::NV12ToI420(src_y, src_stride_y, src_u, src_stride_u, + src_v, src_stride_v, dst_y, dst_stride_y, + dst_uv, dst_stride_uv, width, height)); +} + static void i444_to_i420(const uint8_t* src_y, int src_stride_y, const uint8_t* src_u, diff --git a/webrtc-sys/src/yuv_helper.rs b/webrtc-sys/src/yuv_helper.rs index c1ddb5c6e..abf55e9af 100644 --- a/webrtc-sys/src/yuv_helper.rs +++ b/webrtc-sys/src/yuv_helper.rs @@ -121,6 +121,21 @@ pub mod ffi { height: i32, ) -> Result<()>; + unsafe fn i420_to_nv12( + src_y: *const u8, + src_stride_y: i32, + src_u: *const u8, + src_stride_u: i32, + src_v: *const u8, + src_stride_v: i32, + dst_y: *mut u8, + dst_stride_y: i32, + dst_uv: *mut u8, + dst_stride_uv: i32, + width: i32, + height: i32, + ) -> Result<()>; + unsafe fn i444_to_i420( src_y: *const u8, src_stride_y: i32, From d6524ba45b612be9428a368c363096fbb124b767 Mon Sep 17 00:00:00 2001 From: Daisuke Murase Date: Fri, 21 Mar 2025 08:52:23 -0700 Subject: [PATCH 174/274] Revert "Move RPC handlers to room (#599)" (#606) * Revert "Move RPC handlers to room (#599)" This reverts commit db42022b2e99b7474b647e91823af1ae6a834f8f. * changeset * leave a fix for test case * oops --- .nanpa/swirl-sage-repay.kdl | 2 + examples/Cargo.lock | 193 ++++--------- examples/rpc/src/main.rs | 13 +- livekit-ffi/protocol/rpc.proto | 4 +- livekit-ffi/src/livekit.proto.rs | 5 +- livekit-ffi/src/server/participant.rs | 85 ++++++ livekit-ffi/src/server/requests.rs | 12 +- livekit-ffi/src/server/room.rs | 64 +--- livekit/src/room/mod.rs | 256 +--------------- .../src/room/participant/local_participant.rs | 273 +++++++++++++++--- livekit/src/room/participant/mod.rs | 3 - 11 files changed, 394 insertions(+), 516 deletions(-) create mode 100644 .nanpa/swirl-sage-repay.kdl diff --git a/.nanpa/swirl-sage-repay.kdl b/.nanpa/swirl-sage-repay.kdl new file mode 100644 index 000000000..955dab412 --- /dev/null +++ b/.nanpa/swirl-sage-repay.kdl @@ -0,0 +1,2 @@ +patch package="livekit" type="fixed" "Revert the RPC change, need more robust way" +patch package="livekit-ffi" type="fixed" "Revert the RPC change, need more robust way" diff --git a/examples/Cargo.lock b/examples/Cargo.lock index f2471b63d..06ad21b8f 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 4 +version = 3 [[package]] name = "ab_glyph" @@ -64,7 +64,7 @@ dependencies = [ "once_cell", "serde", "version_check", - "zerocopy 0.7.31", + "zerocopy", ] [[package]] @@ -356,7 +356,7 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.41", ] [[package]] @@ -584,7 +584,7 @@ checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.41", ] [[package]] @@ -951,7 +951,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.100", + "syn 2.0.41", ] [[package]] @@ -968,7 +968,7 @@ checksum = "5c6888cd161769d65134846d4d4981d5a6654307cc46ec83fb917e530aea5f84" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.41", ] [[package]] @@ -1041,7 +1041,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading 0.7.4", + "libloading 0.8.1", ] [[package]] @@ -1170,7 +1170,7 @@ checksum = "c2ad8cef1d801a4686bfd8919f0b30eac4c8e48968c437a6405ded4fb5272d2b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.41", ] [[package]] @@ -1467,7 +1467,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.41", ] [[package]] @@ -1529,22 +1529,10 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] -[[package]] -name = "getrandom" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.13.3+wasi-0.2.2", - "windows-targets 0.52.0", -] - [[package]] name = "gif" version = "0.12.0" @@ -2106,9 +2094,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "libc" -version = "0.2.171" +version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" [[package]] name = "libloading" @@ -2122,12 +2110,12 @@ dependencies = [ [[package]] name = "libloading" -version = "0.8.6" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" dependencies = [ "cfg-if", - "windows-targets 0.52.0", + "windows-sys 0.48.0", ] [[package]] @@ -2154,7 +2142,7 @@ dependencies = [ [[package]] name = "libwebrtc" -version = "0.3.10" +version = "0.3.7" dependencies = [ "cxx", "jni", @@ -2197,12 +2185,11 @@ checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "livekit" -version = "0.7.6" +version = "0.6.0" dependencies = [ "chrono", "futures-util", "lazy_static", - "libloading 0.8.6", "libwebrtc", "livekit-api", "livekit-protocol", @@ -2219,7 +2206,7 @@ dependencies = [ [[package]] name = "livekit-api" -version = "0.4.2" +version = "0.4.0" dependencies = [ "async-tungstenite", "base64", @@ -2230,9 +2217,7 @@ dependencies = [ "livekit-runtime", "log", "parking_lot", - "pbjson-types", "prost 0.12.3", - "rand 0.9.0", "reqwest", "scopeguard", "serde", @@ -2246,7 +2231,7 @@ dependencies = [ [[package]] name = "livekit-protocol" -version = "0.3.9" +version = "0.3.5" dependencies = [ "futures-util", "livekit-runtime", @@ -2262,7 +2247,7 @@ dependencies = [ [[package]] name = "livekit-runtime" -version = "0.4.0" +version = "0.3.0" dependencies = [ "tokio", "tokio-stream", @@ -2397,7 +2382,7 @@ checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", "log", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "windows-sys 0.48.0", ] @@ -2619,7 +2604,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.41", ] [[package]] @@ -2725,7 +2710,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.41", ] [[package]] @@ -2803,7 +2788,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" dependencies = [ "base64ct", - "rand_core 0.6.4", + "rand_core", "subtle", ] @@ -2889,7 +2874,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.41", ] [[package]] @@ -2989,7 +2974,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" dependencies = [ "proc-macro2", - "syn 2.0.100", + "syn 2.0.41", ] [[package]] @@ -3004,9 +2989,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" dependencies = [ "unicode-ident", ] @@ -3054,7 +3039,7 @@ dependencies = [ "prost 0.12.3", "prost-types 0.12.3", "regex", - "syn 2.0.100", + "syn 2.0.41", "tempfile", "which", ] @@ -3082,7 +3067,7 @@ dependencies = [ "itertools 0.11.0", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.41", ] [[package]] @@ -3114,9 +3099,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.39" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -3128,19 +3113,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.3", - "zerocopy 0.8.23", + "rand_chacha", + "rand_core", ] [[package]] @@ -3150,17 +3124,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.3", + "rand_core", ] [[package]] @@ -3169,16 +3133,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.11", -] - -[[package]] -name = "rand_core" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" -dependencies = [ - "getrandom 0.3.1", + "getrandom", ] [[package]] @@ -3237,7 +3192,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" dependencies = [ - "getrandom 0.2.11", + "getrandom", "libredox 0.0.1", "thiserror", ] @@ -3343,7 +3298,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" dependencies = [ "cc", - "getrandom 0.2.11", + "getrandom", "libc", "spin", "untrusted", @@ -3370,7 +3325,7 @@ dependencies = [ "livekit", "livekit-api", "log", - "rand 0.8.5", + "rand", "serde_json", "tokio", ] @@ -3585,7 +3540,7 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.41", ] [[package]] @@ -3786,9 +3741,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.100" +version = "2.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" dependencies = [ "proc-macro2", "quote", @@ -3861,7 +3816,7 @@ checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.41", ] [[package]] @@ -3991,7 +3946,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.41", ] [[package]] @@ -4112,7 +4067,7 @@ dependencies = [ "indexmap 1.9.3", "pin-project", "pin-project-lite", - "rand 0.8.5", + "rand", "slab", "tokio", "tokio-util", @@ -4152,7 +4107,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.41", ] [[package]] @@ -4206,7 +4161,7 @@ dependencies = [ "httparse", "log", "native-tls", - "rand 0.8.5", + "rand", "rustls", "sha1", "thiserror", @@ -4227,7 +4182,7 @@ dependencies = [ "httparse", "log", "native-tls", - "rand 0.8.5", + "rand", "sha1", "thiserror", "url", @@ -4366,15 +4321,6 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -[[package]] -name = "wasi" -version = "0.13.3+wasi-0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" -dependencies = [ - "wit-bindgen-rt", -] - [[package]] name = "wasm-bindgen" version = "0.2.89" @@ -4396,7 +4342,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.41", "wasm-bindgen-shared", ] @@ -4430,7 +4376,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.41", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4558,7 +4504,7 @@ checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" [[package]] name = "webrtc-sys" -version = "0.3.7" +version = "0.3.5" dependencies = [ "cc", "cxx", @@ -4570,7 +4516,7 @@ dependencies = [ [[package]] name = "webrtc-sys-build" -version = "0.3.6" +version = "0.3.5" dependencies = [ "fs2", "regex", @@ -4656,7 +4602,7 @@ dependencies = [ "js-sys", "khronos-egl", "libc", - "libloading 0.8.6", + "libloading 0.8.1", "log", "metal", "naga", @@ -5034,15 +4980,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "wit-bindgen-rt" -version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" -dependencies = [ - "bitflags 2.4.1", -] - [[package]] name = "x11-dl" version = "2.21.0" @@ -5094,16 +5031,7 @@ version = "0.7.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c4061bedbb353041c12f413700357bec76df2c7e2ca8e4df8bac24c6bf68e3d" dependencies = [ - "zerocopy-derive 0.7.31", -] - -[[package]] -name = "zerocopy" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6" -dependencies = [ - "zerocopy-derive 0.8.23", + "zerocopy-derive", ] [[package]] @@ -5114,18 +5042,7 @@ checksum = "b3c129550b3e6de3fd0ba67ba5c81818f9805e58b8d7fee80a3a59d2c9fc601a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", + "syn 2.0.41", ] [[package]] diff --git a/examples/rpc/src/main.rs b/examples/rpc/src/main.rs index ee05890d2..bc53ab69e 100644 --- a/examples/rpc/src/main.rs +++ b/examples/rpc/src/main.rs @@ -81,7 +81,7 @@ async fn main() -> Result<(), Box> { } async fn register_receiver_methods(greeters_room: Arc, math_genius_room: Arc) { - greeters_room.register_rpc_method("arrival".to_string(), |data| { + greeters_room.local_participant().register_rpc_method("arrival".to_string(), |data| { Box::pin(async move { println!( "[{}] [Greeter] Oh {} arrived and said \"{}\"", @@ -94,7 +94,7 @@ async fn register_receiver_methods(greeters_room: Arc, math_genius_room: A }) }); - math_genius_room.register_rpc_method( + math_genius_room.local_participant().register_rpc_method( "square-root".to_string(), |data| { Box::pin(async move { @@ -118,7 +118,7 @@ async fn register_receiver_methods(greeters_room: Arc, math_genius_room: A }, ); - math_genius_room.register_rpc_method("divide".to_string(), |data| { + math_genius_room.local_participant().register_rpc_method("divide".to_string(), |data| { Box::pin(async move { let json_data: Value = serde_json::from_str(&data.payload).unwrap(); let dividend = json_data["dividend"].as_i64().unwrap(); @@ -137,9 +137,8 @@ async fn register_receiver_methods(greeters_room: Arc, math_genius_room: A }) }); - let inner = math_genius_room.clone(); - math_genius_room.register_rpc_method("nested-calculation".to_string(), move |data| { - let math_genius_room = inner.clone(); + math_genius_room.local_participant().register_rpc_method("nested-calculation".to_string(), move |data| { + let math_genius_room = math_genius_room.clone(); Box::pin(async move { let json_data: Value = serde_json::from_str(&data.payload).unwrap(); let number = json_data["number"].as_f64().unwrap(); @@ -270,7 +269,7 @@ async fn perform_division(room: &Arc) -> Result<(), Box) -> Result<(), Box> { - room.register_rpc_method("provide-intermediate".to_string(), |data| { + room.local_participant().register_rpc_method("provide-intermediate".to_string(), |data| { Box::pin(async move { let json_data: Value = serde_json::from_str(&data.payload).unwrap(); let original = json_data["original"].as_f64().unwrap(); diff --git a/livekit-ffi/protocol/rpc.proto b/livekit-ffi/protocol/rpc.proto index 41cdadd8e..19fd75f11 100644 --- a/livekit-ffi/protocol/rpc.proto +++ b/livekit-ffi/protocol/rpc.proto @@ -33,12 +33,12 @@ message PerformRpcRequest { } message RegisterRpcMethodRequest { - required uint64 room_handle = 1; + required uint64 local_participant_handle = 1; required string method = 2; } message UnregisterRpcMethodRequest { - required uint64 room_handle = 1; + required uint64 local_participant_handle = 1; required string method = 2; } diff --git a/livekit-ffi/src/livekit.proto.rs b/livekit-ffi/src/livekit.proto.rs index bdf97e171..214799ee7 100644 --- a/livekit-ffi/src/livekit.proto.rs +++ b/livekit-ffi/src/livekit.proto.rs @@ -1,4 +1,5 @@ // @generated +// This file is @generated by prost-build. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FrameCryptor { @@ -4043,7 +4044,7 @@ pub struct PerformRpcRequest { #[derive(Clone, PartialEq, ::prost::Message)] pub struct RegisterRpcMethodRequest { #[prost(uint64, required, tag="1")] - pub room_handle: u64, + pub local_participant_handle: u64, #[prost(string, required, tag="2")] pub method: ::prost::alloc::string::String, } @@ -4051,7 +4052,7 @@ pub struct RegisterRpcMethodRequest { #[derive(Clone, PartialEq, ::prost::Message)] pub struct UnregisterRpcMethodRequest { #[prost(uint64, required, tag="1")] - pub room_handle: u64, + pub local_participant_handle: u64, #[prost(string, required, tag="2")] pub method: ::prost::alloc::string::String, } diff --git a/livekit-ffi/src/server/participant.rs b/livekit-ffi/src/server/participant.rs index f2817fa94..e6d789876 100644 --- a/livekit-ffi/src/server/participant.rs +++ b/livekit-ffi/src/server/participant.rs @@ -16,6 +16,7 @@ use std::sync::Arc; use livekit::prelude::*; use std::time::Duration; +use tokio::sync::oneshot; use crate::{ proto, @@ -76,4 +77,88 @@ impl FfiParticipant { server.watch_panic(handle); Ok(proto::PerformRpcResponse { async_id }) } + + pub fn register_rpc_method( + &self, + server: &'static FfiServer, + request: proto::RegisterRpcMethodRequest, + ) -> FfiResult { + let method = request.method.clone(); + + let local = match &self.participant { + Participant::Local(local) => local.clone(), + Participant::Remote(_) => { + return Err(FfiError::InvalidRequest("Expected local participant".into())) + } + }; + + let local_participant_handle = self.handle.clone(); + let room: Arc = self.room.clone(); + local.register_rpc_method(method.clone(), move |data| { + Box::pin({ + let room = room.clone(); + let method = method.clone(); + async move { + forward_rpc_method_invocation( + server, + room, + local_participant_handle, + method, + data, + ) + .await + } + }) + }); + Ok(proto::RegisterRpcMethodResponse {}) + } + + pub fn unregister_rpc_method( + &self, + request: proto::UnregisterRpcMethodRequest, + ) -> FfiResult { + let local = match &self.participant { + Participant::Local(local) => local.clone(), + Participant::Remote(_) => { + return Err(FfiError::InvalidRequest("Expected local participant".into())) + } + }; + + local.unregister_rpc_method(request.method); + + Ok(proto::UnregisterRpcMethodResponse {}) + } +} + +async fn forward_rpc_method_invocation( + server: &'static FfiServer, + room: Arc, + local_participant_handle: FfiHandleId, + method: String, + data: RpcInvocationData, +) -> Result { + let (tx, rx) = oneshot::channel(); + let invocation_id = server.next_id(); + + room.store_rpc_method_invocation_waiter(invocation_id, tx); + + let _ = server.send_event(proto::ffi_event::Message::RpcMethodInvocation( + proto::RpcMethodInvocationEvent { + local_participant_handle: local_participant_handle as u64, + invocation_id, + method, + request_id: data.request_id, + caller_identity: data.caller_identity.into(), + payload: data.payload, + response_timeout_ms: data.response_timeout.as_millis() as u32, + }, + )); + + rx.await.unwrap_or_else(|_| { + Err(RpcError { + code: RpcErrorCode::ApplicationError as u32, + message: "Error from method handler".to_string(), + data: None, + }) + }) } diff --git a/livekit-ffi/src/server/requests.rs b/livekit-ffi/src/server/requests.rs index b8cb0db99..45e0a712a 100644 --- a/livekit-ffi/src/server/requests.rs +++ b/livekit-ffi/src/server/requests.rs @@ -27,7 +27,7 @@ use super::{ audio_source, audio_stream, colorcvt, participant::FfiParticipant, resampler, - room::{self, FfiPublication, FfiRoom, FfiTrack}, + room::{self, FfiPublication, FfiTrack}, video_source, video_stream, FfiError, FfiResult, FfiServer, }; use crate::proto; @@ -967,16 +967,18 @@ fn on_register_rpc_method( server: &'static FfiServer, request: proto::RegisterRpcMethodRequest, ) -> FfiResult { - let ffi_room = server.retrieve_handle::(request.room_handle)?.clone(); - ffi_room.inner.register_rpc_method(server, request) + let ffi_participant = + server.retrieve_handle::(request.local_participant_handle)?.clone(); + return ffi_participant.register_rpc_method(server, request); } fn on_unregister_rpc_method( server: &'static FfiServer, request: proto::UnregisterRpcMethodRequest, ) -> FfiResult { - let ffi_room = server.retrieve_handle::(request.room_handle)?.clone(); - ffi_room.inner.unregister_rpc_method(request) + let ffi_participant = + server.retrieve_handle::(request.local_participant_handle)?.clone(); + return ffi_participant.unregister_rpc_method(request); } fn on_rpc_method_invocation_response( diff --git a/livekit-ffi/src/server/room.rs b/livekit-ffi/src/server/room.rs index 36e71adc1..d8399967b 100644 --- a/livekit-ffi/src/server/room.rs +++ b/livekit-ffi/src/server/room.rs @@ -19,7 +19,7 @@ use std::{collections::HashSet, slice, sync::Arc}; use livekit::ChatMessage; use livekit::{prelude::*, registered_audio_filter_plugins}; use livekit_protocol as lk_proto; -use parking_lot::{Mutex, RwLock}; +use parking_lot::Mutex; use tokio::sync::{broadcast, mpsc, oneshot, Mutex as AsyncMutex}; use tokio::task::JoinHandle; @@ -71,7 +71,6 @@ pub struct RoomInner { // Used to forward RPC method invocation to the FfiClient and collect their results rpc_method_invocation_waiters: Mutex>>>, - local_participant_handle: RwLock>, // ws url associated with this room url: String, @@ -176,13 +175,11 @@ impl FfiRoom { pending_unpublished_tracks: Default::default(), track_handle_lookup: Default::default(), rpc_method_invocation_waiters: Default::default(), - local_participant_handle: Default::default(), url: connect.url, }); let (local_info, remote_infos) = build_initial_states(server, &inner, participants_with_tracks); - *inner.local_participant_handle.write() = Some(local_info.handle.id); // Send callback let ffi_room = Self { inner: inner.clone(), handle: Default::default() }; @@ -789,32 +786,6 @@ impl RoomInner { proto::SendStreamTrailerResponse { async_id } } - pub fn register_rpc_method( - self: &Arc, - server: &'static FfiServer, - request: proto::RegisterRpcMethodRequest, - ) -> FfiResult { - let method = request.method.clone(); - - let room = self.clone(); - self.room.register_rpc_method(method.clone(), move |data| { - Box::pin({ - let room = room.clone(); - let method = method.clone(); - async move { forward_rpc_method_invocation(server, room, method, data).await } - }) - }); - Ok(proto::RegisterRpcMethodResponse {}) - } - - pub fn unregister_rpc_method( - &self, - request: proto::UnregisterRpcMethodRequest, - ) -> FfiResult { - self.room.unregister_rpc_method(request.method); - Ok(proto::UnregisterRpcMethodResponse {}) - } - pub fn store_rpc_method_invocation_waiter( &self, invocation_id: u64, @@ -1420,36 +1391,3 @@ fn build_initial_states( remote_infos, ) } - -async fn forward_rpc_method_invocation( - server: &'static FfiServer, - room: Arc, - method: String, - data: RpcInvocationData, -) -> Result { - let (tx, rx) = oneshot::channel(); - let invocation_id = server.next_id(); - let local_participant_handle = room.local_participant_handle.read().unwrap(); - - room.store_rpc_method_invocation_waiter(invocation_id, tx); - - let _ = server.send_event(proto::ffi_event::Message::RpcMethodInvocation( - proto::RpcMethodInvocationEvent { - local_participant_handle, - invocation_id, - method, - request_id: data.request_id, - caller_identity: data.caller_identity.into(), - payload: data.payload, - response_timeout_ms: data.response_timeout.as_millis() as u32, - }, - )); - - rx.await.unwrap_or_else(|_| { - Err(RpcError { - code: RpcErrorCode::ApplicationError as u32, - message: "Error from method handler".to_string(), - data: None, - }) - }) -} diff --git a/livekit/src/room/mod.rs b/livekit/src/room/mod.rs index 3ac898ea5..938743f09 100644 --- a/livekit/src/room/mod.rs +++ b/livekit/src/room/mod.rs @@ -12,11 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::{collections::HashMap, fmt::Debug, pin::Pin, sync::Arc, time::Duration}; +use std::{collections::HashMap, fmt::Debug, sync::Arc, time::Duration}; -use futures_util::Future; use libwebrtc::{ - native::{create_random_uuid, frame_cryptor::EncryptionState}, + native::frame_cryptor::EncryptionState, prelude::{ ContinualGatheringPolicy, IceTransportsType, MediaStream, MediaStreamTrack, RtcConfiguration, @@ -28,11 +27,9 @@ use livekit_api::signal_client::{SignalOptions, SignalSdkOptions}; use livekit_protocol as proto; use livekit_protocol::observer::Dispatcher; use livekit_runtime::JoinHandle; -use parking_lot::{Mutex, RwLock}; -use participant::MAX_PAYLOAD_BYTES; +use parking_lot::RwLock; pub use proto::DisconnectReason; use proto::{promise::Promise, SignalTarget}; -use semver::Version; use thiserror::Error; use tokio::sync::{mpsc, oneshot, Mutex as AsyncMutex}; @@ -271,28 +268,6 @@ pub struct ChatMessage { pub generated: Option, } -type RpcHandler = Arc< - dyn Fn(RpcInvocationData) -> Pin> + Send>> - + Send - + Sync, ->; - -struct RpcState { - pending_acks: HashMap>, - pending_responses: HashMap>>, - handlers: HashMap, -} - -impl RpcState { - fn new() -> Self { - Self { - pending_acks: HashMap::new(), - pending_responses: HashMap::new(), - handlers: HashMap::new(), - } - } -} - #[derive(Debug, Clone)] pub struct RpcRequest { pub destination_identity: String, @@ -416,7 +391,6 @@ pub(crate) struct RoomSession { remote_participants: RwLock>, e2ee_manager: E2eeManager, room_task: AsyncMutex, oneshot::Sender<()>)>>, - rpc_state: Mutex, } impl Debug for RoomSession { @@ -560,9 +534,7 @@ impl Room { dispatcher: dispatcher.clone(), e2ee_manager: e2ee_manager.clone(), room_task: Default::default(), - rpc_state: Mutex::new(RpcState::new()), }); - inner.local_participant.set_session(Arc::downgrade(&inner)); e2ee_manager.on_state_changed({ let dispatcher = dispatcher.clone(); @@ -677,21 +649,6 @@ impl Room { DataPacketKind::Reliable => self.inner.info.read().reliable_dc_options.clone(), } } - - pub fn register_rpc_method( - &self, - method: String, - handler: impl Fn(RpcInvocationData) -> Pin> + Send>> - + Send - + Sync - + 'static, - ) { - self.inner.register_rpc_method(method, handler); - } - - pub fn unregister_rpc_method(&self, method: String) { - self.inner.unregister_rpc_method(method); - } } impl RoomSession { @@ -774,9 +731,9 @@ impl RoomSession { log::warn!("Received RPC request with null caller identity"); return Ok(()); } - let inner = self.clone(); + let local_participant = self.local_participant.clone(); livekit_runtime::spawn(async move { - inner + local_participant .handle_incoming_rpc_request( caller_identity.unwrap(), request_id, @@ -789,10 +746,10 @@ impl RoomSession { }); } EngineEvent::RpcResponse { request_id, payload, error } => { - self.handle_incoming_rpc_response(request_id, payload, error); + self.local_participant.handle_incoming_rpc_response(request_id, payload, error); } EngineEvent::RpcAck { request_id } => { - self.handle_incoming_rpc_ack(request_id); + self.local_participant.handle_incoming_rpc_ack(request_id); } EngineEvent::SpeakersChanged { speakers } => self.handle_speakers_changed(speakers), EngineEvent::ConnectionQuality { updates } => { @@ -1533,205 +1490,6 @@ impl RoomSession { } return self.get_participant_by_identity(identity).map(Participant::Remote); } - - pub(crate) fn register_rpc_method( - &self, - method: String, - handler: impl Fn(RpcInvocationData) -> Pin> + Send>> - + Send - + Sync - + 'static, - ) { - self.rpc_state.lock().handlers.insert(method, Arc::new(handler)); - } - - pub(crate) fn unregister_rpc_method(&self, method: String) { - self.rpc_state.lock().handlers.remove(&method); - } - - pub(crate) async fn perform_rpc(&self, data: PerformRpcData) -> Result { - let max_round_trip_latency = Duration::from_millis(2000); - - if data.payload.len() > MAX_PAYLOAD_BYTES { - return Err(RpcError::built_in(RpcErrorCode::RequestPayloadTooLarge, None)); - } - - if let Some(server_info) = - self.rtc_engine.session().signal_client().join_response().server_info - { - if !server_info.version.is_empty() { - let server_version = Version::parse(&server_info.version).unwrap(); - let min_required_version = Version::parse("1.8.0").unwrap(); - if server_version < min_required_version { - return Err(RpcError::built_in(RpcErrorCode::UnsupportedServer, None)); - } - } - } - - let id = create_random_uuid(); - let (ack_tx, ack_rx) = oneshot::channel(); - let (response_tx, response_rx) = oneshot::channel(); - - match self - .local_participant - .publish_rpc_request(RpcRequest { - destination_identity: data.destination_identity.clone(), - id: id.clone(), - method: data.method.clone(), - payload: data.payload.clone(), - response_timeout: data.response_timeout - max_round_trip_latency, - version: 1, - }) - .await - { - Ok(_) => { - let mut rpc_state = self.rpc_state.lock(); - rpc_state.pending_acks.insert(id.clone(), ack_tx); - rpc_state.pending_responses.insert(id.clone(), response_tx); - } - Err(e) => { - log::error!("Failed to publish RPC request: {}", e); - return Err(RpcError::built_in(RpcErrorCode::SendFailed, Some(e.to_string()))); - } - } - - // Wait for ack timeout - match tokio::time::timeout(max_round_trip_latency, ack_rx).await { - Err(_) => { - let mut rpc_state = self.rpc_state.lock(); - rpc_state.pending_acks.remove(&id); - rpc_state.pending_responses.remove(&id); - return Err(RpcError::built_in(RpcErrorCode::ConnectionTimeout, None)); - } - Ok(_) => { - // Ack received, continue to wait for response - } - } - - // Wait for response timout - let response = match tokio::time::timeout(data.response_timeout, response_rx).await { - Err(_) => { - self.rpc_state.lock().pending_responses.remove(&id); - return Err(RpcError::built_in(RpcErrorCode::ResponseTimeout, None)); - } - Ok(result) => result, - }; - - match response { - Err(_) => { - // Something went wrong locally - Err(RpcError::built_in(RpcErrorCode::RecipientDisconnected, None)) - } - Ok(Err(e)) => { - // RPC error from remote, forward it - Err(e) - } - Ok(Ok(payload)) => { - // Successful response - Ok(payload) - } - } - } - - fn handle_incoming_rpc_ack(&self, request_id: String) { - let mut rpc_state = self.rpc_state.lock(); - if let Some(tx) = rpc_state.pending_acks.remove(&request_id) { - let _ = tx.send(()); - } else { - log::error!("Ack received for unexpected RPC request: {}", request_id); - } - } - - fn handle_incoming_rpc_response( - &self, - request_id: String, - payload: Option, - error: Option, - ) { - let mut rpc_state = self.rpc_state.lock(); - if let Some(tx) = rpc_state.pending_responses.remove(&request_id) { - let _ = tx.send(match error { - Some(e) => Err(RpcError::from_proto(e)), - None => Ok(payload.unwrap_or_default()), - }); - } else { - log::error!("Response received for unexpected RPC request: {}", request_id); - } - } - - async fn handle_incoming_rpc_request( - &self, - caller_identity: ParticipantIdentity, - request_id: String, - method: String, - payload: String, - response_timeout: Duration, - version: u32, - ) { - if let Err(e) = self - .local_participant - .publish_rpc_ack(RpcAck { - destination_identity: caller_identity.to_string(), - request_id: request_id.clone(), - }) - .await - { - log::error!("Failed to publish RPC ACK: {:?}", e); - } - - let caller_identity_2 = caller_identity.clone(); - let request_id_2 = request_id.clone(); - - let response = if version != 1 { - Err(RpcError::built_in(RpcErrorCode::UnsupportedVersion, None)) - } else { - let handler = self.rpc_state.lock().handlers.get(&method).cloned(); - - match handler { - Some(handler) => { - match tokio::task::spawn(async move { - handler(RpcInvocationData { - request_id: request_id.clone(), - caller_identity: caller_identity.clone(), - payload: payload.clone(), - response_timeout, - }) - .await - }) - .await - { - Ok(result) => result, - Err(e) => { - log::error!("RPC method handler returned an error: {:?}", e); - Err(RpcError::built_in(RpcErrorCode::ApplicationError, None)) - } - } - } - None => Err(RpcError::built_in(RpcErrorCode::UnsupportedMethod, None)), - } - }; - - let (payload, error) = match response { - Ok(response_payload) if response_payload.len() <= MAX_PAYLOAD_BYTES => { - (Some(response_payload), None) - } - Ok(_) => (None, Some(RpcError::built_in(RpcErrorCode::ResponsePayloadTooLarge, None))), - Err(e) => (None, Some(e.into())), - }; - - if let Err(e) = self - .local_participant - .publish_rpc_response(RpcResponse { - destination_identity: caller_identity_2.to_string(), - request_id: request_id_2, - payload, - error: error.map(|e| e.to_proto()), - }) - .await - { - log::error!("Failed to publish RPC response: {:?}", e); - } - } } fn unpack_stream_id(stream_id: &str) -> Option<(&str, &str)> { diff --git a/livekit/src/room/participant/local_participant.rs b/livekit/src/room/participant/local_participant.rs index e7fbc7170..67605ba29 100644 --- a/livekit/src/room/participant/local_participant.rs +++ b/livekit/src/room/participant/local_participant.rs @@ -12,34 +12,34 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::{ - collections::HashMap, - fmt::Debug, - future::Future, - pin::Pin, - sync::{Arc, Weak}, - time::Duration, -}; +use std::{collections::HashMap, fmt::Debug, pin::Pin, sync::Arc, time::Duration}; -use super::{ - ConnectionQuality, ParticipantInner, ParticipantKind, ParticipantTrackPermission, RoomSession, -}; +use super::{ConnectionQuality, ParticipantInner, ParticipantKind, ParticipantTrackPermission}; use crate::{ e2ee::EncryptionType, options::{self, compute_video_encodings, video_layers_from_encodings, TrackPublishOptions}, prelude::*, - room::participant::rpc::RpcError, + room::participant::rpc::{RpcError, RpcErrorCode, RpcInvocationData, MAX_PAYLOAD_BYTES}, rtc_engine::{EngineError, RtcEngine}, ChatMessage, DataPacket, RpcAck, RpcRequest, RpcResponse, SipDTMF, Transcription, }; use chrono::Utc; +use futures_util::Future; use libwebrtc::{native::create_random_uuid, rtp_parameters::RtpEncodingParameters}; use livekit_api::signal_client::SignalError; use livekit_protocol as proto; use livekit_runtime::timeout; -use parking_lot::{Mutex, RwLock}; +use parking_lot::Mutex; use proto::request_response::Reason; +use semver::Version; +use tokio::sync::oneshot; + +type RpcHandler = Arc< + dyn Fn(RpcInvocationData) -> Pin> + Send>> + + Send + + Sync, +>; const REQUEST_TIMEOUT: Duration = Duration::from_secs(5); @@ -52,12 +52,27 @@ struct LocalEvents { local_track_unpublished: Mutex>, } +struct RpcState { + pending_acks: HashMap>, + pending_responses: HashMap>>, + handlers: HashMap, +} + +impl RpcState { + fn new() -> Self { + Self { + pending_acks: HashMap::new(), + pending_responses: HashMap::new(), + handlers: HashMap::new(), + } + } +} struct LocalInfo { events: LocalEvents, encryption_type: EncryptionType, + rpc_state: Mutex, all_participants_allowed: Mutex, track_permissions: Mutex>, - session: RwLock>>, } #[derive(Clone)] @@ -92,21 +107,13 @@ impl LocalParticipant { local: Arc::new(LocalInfo { events: LocalEvents::default(), encryption_type, + rpc_state: Mutex::new(RpcState::new()), all_participants_allowed: Mutex::new(true), track_permissions: Mutex::new(vec![]), - session: Default::default(), }), } } - pub(crate) fn set_session(&self, session: Weak) { - *self.local.session.write() = Some(session); - } - - pub(crate) fn session(&self) -> Option> { - self.local.session.read().as_ref().and_then(|s| s.upgrade()) - } - pub(crate) fn internal_track_publications(&self) -> HashMap { self.inner.track_publications.read().clone() } @@ -537,7 +544,7 @@ impl LocalParticipant { self.inner.rtc_engine.publish_data(data, DataPacketKind::Reliable).await.map_err(Into::into) } - pub(crate) async fn publish_rpc_request(&self, rpc_request: RpcRequest) -> RoomResult<()> { + async fn publish_rpc_request(&self, rpc_request: RpcRequest) -> RoomResult<()> { let destination_identities = vec![rpc_request.destination_identity]; let rpc_request_message = proto::RpcRequest { id: rpc_request.id, @@ -557,7 +564,7 @@ impl LocalParticipant { self.inner.rtc_engine.publish_data(data, DataPacketKind::Reliable).await.map_err(Into::into) } - pub(crate) async fn publish_rpc_response(&self, rpc_response: RpcResponse) -> RoomResult<()> { + async fn publish_rpc_response(&self, rpc_response: RpcResponse) -> RoomResult<()> { let destination_identities = vec![rpc_response.destination_identity]; let rpc_response_message = proto::RpcResponse { request_id: rpc_response.request_id, @@ -581,7 +588,7 @@ impl LocalParticipant { self.inner.rtc_engine.publish_data(data, DataPacketKind::Reliable).await.map_err(Into::into) } - pub(crate) async fn publish_rpc_ack(&self, rpc_ack: RpcAck) -> RoomResult<()> { + async fn publish_rpc_ack(&self, rpc_ack: RpcAck) -> RoomResult<()> { let destination_identities = vec![rpc_ack.destination_identity]; let rpc_ack_message = proto::RpcAck { request_id: rpc_ack.request_id, ..Default::default() }; @@ -595,25 +602,6 @@ impl LocalParticipant { self.inner.rtc_engine.publish_data(data, DataPacketKind::Reliable).await.map_err(Into::into) } - #[deprecated(note = "Use `room.register_rpc_method` instead.")] - pub fn register_rpc_method( - &self, - method: String, - handler: impl Fn(RpcInvocationData) -> Pin> + Send>> - + Send - + Sync - + 'static, - ) { - let Some(session) = self.session() else { return }; - session.register_rpc_method(method, handler); - } - - #[deprecated(note = "Use `room.unregister_rpc_method` instead.")] - pub fn unregister_rpc_method(&self, method: String) { - let Some(session) = self.session() else { return }; - session.unregister_rpc_method(method); - } - pub(crate) async fn update_track_subscription_permissions(&self) { let all_participants_allowed = *self.local.all_participants_allowed.lock(); let track_permissions = self @@ -702,7 +690,198 @@ impl LocalParticipant { } pub async fn perform_rpc(&self, data: PerformRpcData) -> Result { - let session = self.session().unwrap(); - session.perform_rpc(data).await + let max_round_trip_latency = Duration::from_millis(2000); + + if data.payload.len() > MAX_PAYLOAD_BYTES { + return Err(RpcError::built_in(RpcErrorCode::RequestPayloadTooLarge, None)); + } + + if let Some(server_info) = + self.inner.rtc_engine.session().signal_client().join_response().server_info + { + if !server_info.version.is_empty() { + let server_version = Version::parse(&server_info.version).unwrap(); + let min_required_version = Version::parse("1.8.0").unwrap(); + if server_version < min_required_version { + return Err(RpcError::built_in(RpcErrorCode::UnsupportedServer, None)); + } + } + } + + let id = create_random_uuid(); + let (ack_tx, ack_rx) = oneshot::channel(); + let (response_tx, response_rx) = oneshot::channel(); + + match self + .publish_rpc_request(RpcRequest { + destination_identity: data.destination_identity.clone(), + id: id.clone(), + method: data.method.clone(), + payload: data.payload.clone(), + response_timeout: data.response_timeout - max_round_trip_latency, + version: 1, + }) + .await + { + Ok(_) => { + let mut rpc_state = self.local.rpc_state.lock(); + rpc_state.pending_acks.insert(id.clone(), ack_tx); + rpc_state.pending_responses.insert(id.clone(), response_tx); + } + Err(e) => { + log::error!("Failed to publish RPC request: {}", e); + return Err(RpcError::built_in(RpcErrorCode::SendFailed, Some(e.to_string()))); + } + } + + // Wait for ack timeout + match tokio::time::timeout(max_round_trip_latency, ack_rx).await { + Err(_) => { + let mut rpc_state = self.local.rpc_state.lock(); + rpc_state.pending_acks.remove(&id); + rpc_state.pending_responses.remove(&id); + return Err(RpcError::built_in(RpcErrorCode::ConnectionTimeout, None)); + } + Ok(_) => { + // Ack received, continue to wait for response + } + } + + // Wait for response timout + let response = match tokio::time::timeout(data.response_timeout, response_rx).await { + Err(_) => { + self.local.rpc_state.lock().pending_responses.remove(&id); + return Err(RpcError::built_in(RpcErrorCode::ResponseTimeout, None)); + } + Ok(result) => result, + }; + + match response { + Err(_) => { + // Something went wrong locally + Err(RpcError::built_in(RpcErrorCode::RecipientDisconnected, None)) + } + Ok(Err(e)) => { + // RPC error from remote, forward it + Err(e) + } + Ok(Ok(payload)) => { + // Successful response + Ok(payload) + } + } + } + + pub fn register_rpc_method( + &self, + method: String, + handler: impl Fn(RpcInvocationData) -> Pin> + Send>> + + Send + + Sync + + 'static, + ) { + self.local.rpc_state.lock().handlers.insert(method, Arc::new(handler)); + } + + pub fn unregister_rpc_method(&self, method: String) { + self.local.rpc_state.lock().handlers.remove(&method); + } + + pub(crate) fn handle_incoming_rpc_ack(&self, request_id: String) { + let mut rpc_state = self.local.rpc_state.lock(); + if let Some(tx) = rpc_state.pending_acks.remove(&request_id) { + let _ = tx.send(()); + } else { + log::error!("Ack received for unexpected RPC request: {}", request_id); + } + } + + pub(crate) fn handle_incoming_rpc_response( + &self, + request_id: String, + payload: Option, + error: Option, + ) { + let mut rpc_state = self.local.rpc_state.lock(); + if let Some(tx) = rpc_state.pending_responses.remove(&request_id) { + let _ = tx.send(match error { + Some(e) => Err(RpcError::from_proto(e)), + None => Ok(payload.unwrap_or_default()), + }); + } else { + log::error!("Response received for unexpected RPC request: {}", request_id); + } + } + + pub(crate) async fn handle_incoming_rpc_request( + &self, + caller_identity: ParticipantIdentity, + request_id: String, + method: String, + payload: String, + response_timeout: Duration, + version: u32, + ) { + if let Err(e) = self + .publish_rpc_ack(RpcAck { + destination_identity: caller_identity.to_string(), + request_id: request_id.clone(), + }) + .await + { + log::error!("Failed to publish RPC ACK: {:?}", e); + } + + let caller_identity_2 = caller_identity.clone(); + let request_id_2 = request_id.clone(); + + let response = if version != 1 { + Err(RpcError::built_in(RpcErrorCode::UnsupportedVersion, None)) + } else { + let handler = self.local.rpc_state.lock().handlers.get(&method).cloned(); + + match handler { + Some(handler) => { + match tokio::task::spawn(async move { + handler(RpcInvocationData { + request_id: request_id.clone(), + caller_identity: caller_identity.clone(), + payload: payload.clone(), + response_timeout, + }) + .await + }) + .await + { + Ok(result) => result, + Err(e) => { + log::error!("RPC method handler returned an error: {:?}", e); + Err(RpcError::built_in(RpcErrorCode::ApplicationError, None)) + } + } + } + None => Err(RpcError::built_in(RpcErrorCode::UnsupportedMethod, None)), + } + }; + + let (payload, error) = match response { + Ok(response_payload) if response_payload.len() <= MAX_PAYLOAD_BYTES => { + (Some(response_payload), None) + } + Ok(_) => (None, Some(RpcError::built_in(RpcErrorCode::ResponsePayloadTooLarge, None))), + Err(e) => (None, Some(e.into())), + }; + + if let Err(e) = self + .publish_rpc_response(RpcResponse { + destination_identity: caller_identity_2.to_string(), + request_id: request_id_2, + payload, + error: error.map(|e| e.to_proto()), + }) + .await + { + log::error!("Failed to publish RPC response: {:?}", e); + } } } diff --git a/livekit/src/room/participant/mod.rs b/livekit/src/room/participant/mod.rs index 53de26c65..30cf0544c 100644 --- a/livekit/src/room/participant/mod.rs +++ b/livekit/src/room/participant/mod.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::sync::Weak; use std::{collections::HashMap, fmt::Debug, sync::Arc}; use livekit_protocol as proto; @@ -30,8 +29,6 @@ pub use local_participant::*; pub use remote_participant::*; pub use rpc::*; -use super::RoomSession; - #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum ConnectionQuality { Excellent, From fdca50dca6a5316681bf9503989b08bee511e7fa Mon Sep 17 00:00:00 2001 From: Daisuke Murase Date: Fri, 21 Mar 2025 08:53:08 -0700 Subject: [PATCH 175/274] Fix minor issues (#608) * this should not be FfiError * message improvement * changeset * fmt --- .nanpa/spiny-saved-tutor.kdl | 1 + Cargo.lock | 4 ++-- livekit-ffi/src/server/requests.rs | 8 ++++++-- livekit-ffi/src/server/room.rs | 5 ++++- 4 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 .nanpa/spiny-saved-tutor.kdl diff --git a/.nanpa/spiny-saved-tutor.kdl b/.nanpa/spiny-saved-tutor.kdl new file mode 100644 index 000000000..749fb0b42 --- /dev/null +++ b/.nanpa/spiny-saved-tutor.kdl @@ -0,0 +1 @@ +patch package="livekit-ffi" type="fixed" "Fix several minor issues" diff --git a/Cargo.lock b/Cargo.lock index 2cdf711ab..2e26cf622 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1605,7 +1605,7 @@ checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "livekit" -version = "0.7.6" +version = "0.7.7" dependencies = [ "chrono", "futures-util", @@ -1655,7 +1655,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.12.16" +version = "0.12.17" dependencies = [ "console-subscriber", "dashmap", diff --git a/livekit-ffi/src/server/requests.rs b/livekit-ffi/src/server/requests.rs index 45e0a712a..45cf98dd0 100644 --- a/livekit-ffi/src/server/requests.rs +++ b/livekit-ffi/src/server/requests.rs @@ -955,8 +955,12 @@ fn on_load_audio_filter_plugin( request: proto::LoadAudioFilterPluginRequest, ) -> FfiResult { let deps: Vec<_> = request.dependencies.iter().map(|d| d).collect(); - let plugin = AudioFilterPlugin::new_with_dependencies(&request.plugin_path, deps) - .map_err(|e| FfiError::InvalidRequest(format!("plugin error: {}", e).into()))?; + let plugin = match AudioFilterPlugin::new_with_dependencies(&request.plugin_path, deps) { + Ok(p) => p, + Err(err) => { + return Ok(proto::LoadAudioFilterPluginResponse { error: Some(err.to_string()) }); + } + }; register_audio_filter_plugin(request.module_id, plugin); diff --git a/livekit-ffi/src/server/room.rs b/livekit-ffi/src/server/room.rs index d8399967b..8e1b058fb 100644 --- a/livekit-ffi/src/server/room.rs +++ b/livekit-ffi/src/server/room.rs @@ -144,7 +144,10 @@ impl FfiRoom { .map_err(|e| e.to_string()); match result { Err(e) | Ok(Err(e)) => { - log::error!("error while initializing audio filter: {}", e); + log::debug!("error while initializing audio filter: {}", e); + log::error!( + "audio filter cannot be enabled: LiveKit Cloud is required" + ); // Skip returning an error here to keep the rtc session alive // But in this case, the filter isn't enabled in the session. } From 91f49fcaf097b4eeb7a361bdcd4f9333e60ae0d5 Mon Sep 17 00:00:00 2001 From: "ilo-nanpa[bot]" <174912985+ilo-nanpa[bot]@users.noreply.github.com> Date: Fri, 21 Mar 2025 15:55:11 +0000 Subject: [PATCH 176/274] nanpa: bump --- .nanpa/spiny-saved-tutor.kdl | 1 - .nanpa/swirl-sage-repay.kdl | 2 -- Cargo.toml | 4 ++-- livekit-ffi/.nanparc | 2 +- livekit-ffi/CHANGELOG.md | 7 +++++++ livekit-ffi/Cargo.toml | 2 +- livekit/.nanparc | 2 +- livekit/CHANGELOG.md | 6 ++++++ livekit/Cargo.toml | 2 +- 9 files changed, 19 insertions(+), 9 deletions(-) delete mode 100644 .nanpa/spiny-saved-tutor.kdl delete mode 100644 .nanpa/swirl-sage-repay.kdl diff --git a/.nanpa/spiny-saved-tutor.kdl b/.nanpa/spiny-saved-tutor.kdl deleted file mode 100644 index 749fb0b42..000000000 --- a/.nanpa/spiny-saved-tutor.kdl +++ /dev/null @@ -1 +0,0 @@ -patch package="livekit-ffi" type="fixed" "Fix several minor issues" diff --git a/.nanpa/swirl-sage-repay.kdl b/.nanpa/swirl-sage-repay.kdl deleted file mode 100644 index 955dab412..000000000 --- a/.nanpa/swirl-sage-repay.kdl +++ /dev/null @@ -1,2 +0,0 @@ -patch package="livekit" type="fixed" "Revert the RPC change, need more robust way" -patch package="livekit-ffi" type="fixed" "Revert the RPC change, need more robust way" diff --git a/Cargo.toml b/Cargo.toml index 186134d3a..e3ee85fe0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,10 +19,10 @@ imgproc = { version = "0.3.12", path = "imgproc" } yuv-sys = { version = "0.3.7", path = "yuv-sys" } libwebrtc = { version = "0.3.10", path = "libwebrtc" } livekit-api = { version = "0.4.2", path = "livekit-api" } -livekit-ffi = { version = "0.12.17", path = "livekit-ffi" } +livekit-ffi = { version = "0.12.18", path = "livekit-ffi" } livekit-protocol = { version = "0.3.9", path = "livekit-protocol" } livekit-runtime = { version = "0.4.0", path = "livekit-runtime" } -livekit = { version = "0.7.7", path = "livekit" } +livekit = { version = "0.7.8", path = "livekit" } soxr-sys = { version = "0.1.0", path = "soxr-sys" } webrtc-sys-build = { version = "0.3.6", path = "webrtc-sys/build" } webrtc-sys = { version = "0.3.7", path = "webrtc-sys" } diff --git a/livekit-ffi/.nanparc b/livekit-ffi/.nanparc index 03c3b04bc..c6285ea15 100644 --- a/livekit-ffi/.nanparc +++ b/livekit-ffi/.nanparc @@ -1,2 +1,2 @@ -version 0.12.17 +version 0.12.18 language rust diff --git a/livekit-ffi/CHANGELOG.md b/livekit-ffi/CHANGELOG.md index 96fe84930..e870f8129 100644 --- a/livekit-ffi/CHANGELOG.md +++ b/livekit-ffi/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [0.12.18] - 2025-03-21 + +### Fixed + +- Fix several minor issues +- Revert the RPC change, need more robust way + ## [0.12.17] - 2025-03-18 ### Fixed diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 8acbef267..0968889ca 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.12.17" +version = "0.12.18" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" diff --git a/livekit/.nanparc b/livekit/.nanparc index ba054a383..c6fb36583 100644 --- a/livekit/.nanparc +++ b/livekit/.nanparc @@ -1,2 +1,2 @@ -version 0.7.7 +version 0.7.8 language rust diff --git a/livekit/CHANGELOG.md b/livekit/CHANGELOG.md index 7f4850e85..f40116614 100644 --- a/livekit/CHANGELOG.md +++ b/livekit/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [0.7.8] - 2025-03-21 + +### Fixed + +- Revert the RPC change, need more robust way + ## [0.7.7] - 2025-03-18 ### Fixed diff --git a/livekit/Cargo.toml b/livekit/Cargo.toml index 66f1849ba..fc2284de5 100644 --- a/livekit/Cargo.toml +++ b/livekit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit" -version = "0.7.7" +version = "0.7.8" edition = "2021" license = "Apache-2.0" description = "Rust Client SDK for LiveKit" From 2e2c5ee752cfa45be55a1ec0d4e7ea91f9da37bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Fri, 28 Mar 2025 16:31:02 +0100 Subject: [PATCH 177/274] fix uint32 overflow (#615) --- .nanpa/int32-overflow.kdl | 1 + libwebrtc/src/native/audio_source.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 .nanpa/int32-overflow.kdl diff --git a/.nanpa/int32-overflow.kdl b/.nanpa/int32-overflow.kdl new file mode 100644 index 000000000..c1729bd0f --- /dev/null +++ b/.nanpa/int32-overflow.kdl @@ -0,0 +1 @@ +patch type="fixed" "fix uint32 overflow" \ No newline at end of file diff --git a/libwebrtc/src/native/audio_source.rs b/libwebrtc/src/native/audio_source.rs index 7956508f5..510279f52 100644 --- a/libwebrtc/src/native/audio_source.rs +++ b/libwebrtc/src/native/audio_source.rs @@ -42,7 +42,7 @@ impl NativeAudioSource { queue_size_ms.try_into().unwrap(), ); - let queue_size_samples = (queue_size_ms * sample_rate / 1000) * num_channels; + let queue_size_samples = queue_size_ms * (sample_rate / 1000) * num_channels; Self { sys_handle, sample_rate, num_channels, queue_size_samples } } From 7cbdddf41a9b5c490a715b015833377efd585905 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Mon, 31 Mar 2025 01:21:41 +0200 Subject: [PATCH 178/274] expose apm stream_delay (#616) --- Cargo.lock | 4 ++-- libwebrtc/src/native/apm.rs | 11 +++++++++++ livekit-ffi/protocol/audio_frame.proto | 10 ++++++++++ livekit-ffi/protocol/ffi.proto | 6 ++++-- livekit-ffi/src/livekit.proto.rs | 22 ++++++++++++++++++++-- livekit-ffi/src/server/requests.rs | 22 ++++++++++++++++++++++ webrtc-sys/include/livekit/apm.h | 2 ++ webrtc-sys/src/apm.cpp | 4 ++++ webrtc-sys/src/apm.rs | 2 ++ 9 files changed, 77 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2e26cf622..b8e51dbcd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1605,7 +1605,7 @@ checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "livekit" -version = "0.7.7" +version = "0.7.8" dependencies = [ "chrono", "futures-util", @@ -1655,7 +1655,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.12.17" +version = "0.12.18" dependencies = [ "console-subscriber", "dashmap", diff --git a/libwebrtc/src/native/apm.rs b/libwebrtc/src/native/apm.rs index 5315210e9..7f36405e2 100644 --- a/libwebrtc/src/native/apm.rs +++ b/libwebrtc/src/native/apm.rs @@ -99,4 +99,15 @@ impl AudioProcessingModule { }) } } + + pub fn set_stream_delay_ms(&mut self, delay_ms: i32) -> Result<(), RtcError> { + if self.sys_handle.pin_mut().set_stream_delay_ms(delay_ms) == 0 { + Ok(()) + } else { + Err(RtcError { + error_type: RtcErrorType::Internal, + message: "Failed to set stream delay".to_string(), + }) + } + } } diff --git a/livekit-ffi/protocol/audio_frame.proto b/livekit-ffi/protocol/audio_frame.proto index ce3c5a93c..6aac005ab 100644 --- a/livekit-ffi/protocol/audio_frame.proto +++ b/livekit-ffi/protocol/audio_frame.proto @@ -128,6 +128,16 @@ message ApmProcessReverseStreamResponse { optional string error = 1; } +message ApmSetStreamDelayRequest { + required uint64 apm_handle = 1; + required int32 delay_ms = 2; +} + +message ApmSetStreamDelayResponse { + optional string error = 1; +} + + // New resampler using SoX (much better quality) message NewSoxResamplerRequest { diff --git a/livekit-ffi/protocol/ffi.proto b/livekit-ffi/protocol/ffi.proto index d6e034c7a..b12105c19 100644 --- a/livekit-ffi/protocol/ffi.proto +++ b/livekit-ffi/protocol/ffi.proto @@ -126,8 +126,9 @@ message FfiRequest { NewApmRequest new_apm = 50; ApmProcessStreamRequest apm_process_stream = 51; ApmProcessReverseStreamRequest apm_process_reverse_stream = 52; + ApmSetStreamDelayRequest apm_set_stream_delay = 53; - // NEXT_ID: 53 + // NEXT_ID: 54 } } @@ -201,8 +202,9 @@ message FfiResponse { NewApmResponse new_apm = 49; ApmProcessStreamResponse apm_process_stream = 50; ApmProcessReverseStreamResponse apm_process_reverse_stream = 51; + ApmSetStreamDelayResponse apm_set_stream_delay = 52; - // NEXT_ID: 52 + // NEXT_ID: 53 } } diff --git a/livekit-ffi/src/livekit.proto.rs b/livekit-ffi/src/livekit.proto.rs index 214799ee7..223669f17 100644 --- a/livekit-ffi/src/livekit.proto.rs +++ b/livekit-ffi/src/livekit.proto.rs @@ -3619,6 +3619,20 @@ pub struct ApmProcessReverseStreamResponse { #[prost(string, optional, tag="1")] pub error: ::core::option::Option<::prost::alloc::string::String>, } +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ApmSetStreamDelayRequest { + #[prost(uint64, required, tag="1")] + pub apm_handle: u64, + #[prost(int32, required, tag="2")] + pub delay_ms: i32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ApmSetStreamDelayResponse { + #[prost(string, optional, tag="1")] + pub error: ::core::option::Option<::prost::alloc::string::String>, +} // New resampler using SoX (much better quality) #[allow(clippy::derive_partial_eq_without_eq)] @@ -4150,7 +4164,7 @@ pub struct RpcMethodInvocationEvent { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiRequest { - #[prost(oneof="ffi_request::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 48, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 49, 50, 51, 52")] + #[prost(oneof="ffi_request::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 48, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 49, 50, 51, 52, 53")] pub message: ::core::option::Option, } /// Nested message and enum types in `FfiRequest`. @@ -4269,13 +4283,15 @@ pub mod ffi_request { ApmProcessStream(super::ApmProcessStreamRequest), #[prost(message, tag="52")] ApmProcessReverseStream(super::ApmProcessReverseStreamRequest), + #[prost(message, tag="53")] + ApmSetStreamDelay(super::ApmSetStreamDelayRequest), } } /// This is the output of livekit_ffi_request function. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiResponse { - #[prost(oneof="ffi_response::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 47, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 48, 49, 50, 51")] + #[prost(oneof="ffi_response::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 47, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 48, 49, 50, 51, 52")] pub message: ::core::option::Option, } /// Nested message and enum types in `FfiResponse`. @@ -4392,6 +4408,8 @@ pub mod ffi_response { ApmProcessStream(super::ApmProcessStreamResponse), #[prost(message, tag="51")] ApmProcessReverseStream(super::ApmProcessReverseStreamResponse), + #[prost(message, tag="52")] + ApmSetStreamDelay(super::ApmSetStreamDelayResponse), } } /// To minimize complexity, participant events are not included in the protocol. diff --git a/livekit-ffi/src/server/requests.rs b/livekit-ffi/src/server/requests.rs index 45cf98dd0..34d0341a4 100644 --- a/livekit-ffi/src/server/requests.rs +++ b/livekit-ffi/src/server/requests.rs @@ -941,6 +941,23 @@ fn on_apm_process_reverse_stream( Ok(proto::ApmProcessReverseStreamResponse { error: None }) } +fn on_apm_set_stream_delay( + server: &'static FfiServer, + request: proto::ApmSetStreamDelayRequest, +) -> FfiResult { + let aec = server + .retrieve_handle::>>(request.apm_handle)? + .clone(); + + let mut aec = aec.lock(); + + if let Err(e) = aec.set_stream_delay_ms(request.delay_ms) { + return Ok(proto::ApmSetStreamDelayResponse { error: Some(e.to_string()) }); + } + + Ok(proto::ApmSetStreamDelayResponse { error: None }) +} + fn on_perform_rpc( server: &'static FfiServer, request: proto::PerformRpcRequest, @@ -1181,6 +1198,11 @@ pub fn handle_request( server, request, )?) } + proto::ffi_request::Message::ApmSetStreamDelay(request) => { + proto::ffi_response::Message::ApmSetStreamDelay(on_apm_set_stream_delay( + server, request, + )?) + } proto::ffi_request::Message::PerformRpc(request) => { proto::ffi_response::Message::PerformRpc(on_perform_rpc(server, request)?) } diff --git a/webrtc-sys/include/livekit/apm.h b/webrtc-sys/include/livekit/apm.h index 180499dd0..5f0e17eca 100644 --- a/webrtc-sys/include/livekit/apm.h +++ b/webrtc-sys/include/livekit/apm.h @@ -60,6 +60,8 @@ class AudioProcessingModule { int sample_rate, int num_channels); + int set_stream_delay_ms(int delay_ms); + private: rtc::scoped_refptr apm_; }; diff --git a/webrtc-sys/src/apm.cpp b/webrtc-sys/src/apm.cpp index 358f7ed4a..0c6bcd1ec 100644 --- a/webrtc-sys/src/apm.cpp +++ b/webrtc-sys/src/apm.cpp @@ -33,6 +33,10 @@ int AudioProcessingModule::process_reverse_stream(const int16_t* src, return apm_->ProcessReverseStream(src, stream_cfg, stream_cfg, dst); } +int AudioProcessingModule::set_stream_delay_ms(int delay_ms) { + return apm_->set_stream_delay_ms(delay_ms); +} + std::unique_ptr create_apm( bool echo_canceller_enabled, bool gain_controller_enabled, diff --git a/webrtc-sys/src/apm.rs b/webrtc-sys/src/apm.rs index 96d7631c5..30c804ee1 100644 --- a/webrtc-sys/src/apm.rs +++ b/webrtc-sys/src/apm.rs @@ -41,6 +41,8 @@ pub mod ffi { num_channels: i32, ) -> i32; + fn set_stream_delay_ms(self: Pin<&mut AudioProcessingModule>, delay: i32) -> i32; + fn create_apm( echo_canceller_enabled: bool, gain_controller_enabled: bool, From 4a55d1d34cb9bd520aa3f0c19d577837701a2d3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Mon, 31 Mar 2025 01:32:20 +0200 Subject: [PATCH 179/274] ffi v0.12.19 (#617) --- Cargo.lock | 2 +- Cargo.toml | 2 +- livekit-ffi/.nanparc | 2 +- livekit-ffi/Cargo.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b8e51dbcd..acd109703 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1655,7 +1655,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.12.18" +version = "0.12.19" dependencies = [ "console-subscriber", "dashmap", diff --git a/Cargo.toml b/Cargo.toml index e3ee85fe0..8a2b06aa0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ imgproc = { version = "0.3.12", path = "imgproc" } yuv-sys = { version = "0.3.7", path = "yuv-sys" } libwebrtc = { version = "0.3.10", path = "libwebrtc" } livekit-api = { version = "0.4.2", path = "livekit-api" } -livekit-ffi = { version = "0.12.18", path = "livekit-ffi" } +livekit-ffi = { version = "0.12.19", path = "livekit-ffi" } livekit-protocol = { version = "0.3.9", path = "livekit-protocol" } livekit-runtime = { version = "0.4.0", path = "livekit-runtime" } livekit = { version = "0.7.8", path = "livekit" } diff --git a/livekit-ffi/.nanparc b/livekit-ffi/.nanparc index c6285ea15..cddabf5b1 100644 --- a/livekit-ffi/.nanparc +++ b/livekit-ffi/.nanparc @@ -1,2 +1,2 @@ -version 0.12.18 +version 0.12.19 language rust diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 0968889ca..7d56ec5e0 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.12.18" +version = "0.12.19" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" From dcaf8df6c3998cc359de879074d970b3ef16425c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Fri, 4 Apr 2025 20:51:46 +0200 Subject: [PATCH 180/274] fix rtc.AudioFrame.from_participant not closing (#619) --- .nanpa/stream-close.kdl | 1 + livekit-ffi/src/server/audio_stream.rs | 11 +++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 .nanpa/stream-close.kdl diff --git a/.nanpa/stream-close.kdl b/.nanpa/stream-close.kdl new file mode 100644 index 000000000..8c59d544d --- /dev/null +++ b/.nanpa/stream-close.kdl @@ -0,0 +1 @@ +patch type="fixed" "fix rtc.AudioStream.from_participant close" \ No newline at end of file diff --git a/livekit-ffi/src/server/audio_stream.rs b/livekit-ffi/src/server/audio_stream.rs index 902cdd281..6ee74fbe7 100644 --- a/livekit-ffi/src/server/audio_stream.rs +++ b/livekit-ffi/src/server/audio_stream.rs @@ -198,6 +198,7 @@ impl FfiAudioStream { }; let track_source = request.track_source(); + let (track_tx, mut track_rx) = mpsc::channel::(1); let (track_finished_tx, _) = broadcast::channel::(1); server.async_runtime.spawn(utils::track_changed_trigger( @@ -220,7 +221,13 @@ impl FfiAudioStream { }; loop { - let track = track_rx.recv().await; + let track = tokio::select! { + track = track_rx.recv() => track, + _ = &mut self_dropped_rx => { + break; + } + }; + if let Some(track) = track { let rtc_track = track.rtc_track(); let MediaStreamTrack::Audio(rtc_track) = rtc_track else { @@ -312,7 +319,7 @@ impl FfiAudioStream { tokio::select! { _ = &mut self_dropped_rx => { let _ = c_tx.send(()); - return + break } _ = &mut done_rx => { continue From 3f54b6de49b9bcf795f2ef660c3e99c769bbaeb1 Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Tue, 8 Apr 2025 08:29:28 +1000 Subject: [PATCH 181/274] Data streams (#603) * Native data streams support in Rust * High-level interface for FFI clients --- Cargo.lock | 17 +- examples/basic_text_stream/Cargo.lock | 2553 +++++++++++++++++ examples/basic_text_stream/Cargo.toml | 15 + examples/basic_text_stream/README.md | 23 + examples/basic_text_stream/src/main.rs | 65 + livekit-ffi/Cargo.toml | 1 + livekit-ffi/generate_proto.sh | 3 +- livekit-ffi/protocol/data_stream.proto | 348 +++ livekit-ffi/protocol/ffi.proto | 65 +- livekit-ffi/protocol/room.proto | 15 + livekit-ffi/src/conversion/data_stream.rs | 150 + livekit-ffi/src/conversion/mod.rs | 1 + livekit-ffi/src/livekit.proto.rs | 732 ++++- livekit-ffi/src/server/data_stream.rs | 310 ++ livekit-ffi/src/server/mod.rs | 27 + livekit-ffi/src/server/participant.rs | 110 +- livekit-ffi/src/server/requests.rs | 163 +- livekit-ffi/src/server/room.rs | 37 +- livekit/Cargo.toml | 4 +- livekit/src/room/data_stream/incoming.rs | 344 +++ livekit/src/room/data_stream/mod.rs | 241 ++ livekit/src/room/data_stream/outgoing.rs | 438 +++ livekit/src/room/mod.rs | 162 +- .../src/room/participant/local_participant.rs | 100 +- livekit/src/room/utils/mod.rs | 3 + livekit/src/room/utils/take_cell.rs | 102 + livekit/src/room/utils/utf8_chunk.rs | 108 + 27 files changed, 6088 insertions(+), 49 deletions(-) create mode 100644 examples/basic_text_stream/Cargo.lock create mode 100644 examples/basic_text_stream/Cargo.toml create mode 100644 examples/basic_text_stream/README.md create mode 100644 examples/basic_text_stream/src/main.rs create mode 100644 livekit-ffi/protocol/data_stream.proto create mode 100644 livekit-ffi/src/conversion/data_stream.rs create mode 100644 livekit-ffi/src/server/data_stream.rs create mode 100644 livekit/src/room/data_stream/incoming.rs create mode 100644 livekit/src/room/data_stream/mod.rs create mode 100644 livekit/src/room/data_stream/outgoing.rs create mode 100644 livekit/src/room/utils/take_cell.rs create mode 100644 livekit/src/room/utils/utf8_chunk.rs diff --git a/Cargo.lock b/Cargo.lock index acd109703..5ce6de37d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -384,6 +384,16 @@ dependencies = [ "tracing", ] +[[package]] +name = "bmrng" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54df9073108f1558f90ae6c5bf5ab9c917c4185f5527b280c87a993cbead0ac" +dependencies = [ + "futures-core", + "tokio", +] + [[package]] name = "bumpalo" version = "3.14.0" @@ -398,9 +408,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "bzip2" @@ -1607,6 +1617,8 @@ checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" name = "livekit" version = "0.7.8" dependencies = [ + "bmrng", + "bytes", "chrono", "futures-util", "lazy_static", @@ -1657,6 +1669,7 @@ dependencies = [ name = "livekit-ffi" version = "0.12.19" dependencies = [ + "bytes", "console-subscriber", "dashmap", "downcast-rs", diff --git a/examples/basic_text_stream/Cargo.lock b/examples/basic_text_stream/Cargo.lock new file mode 100644 index 000000000..53fd5abc0 --- /dev/null +++ b/examples/basic_text_stream/Cargo.lock @@ -0,0 +1,2553 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "anyhow" +version = "1.0.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "basic_text_stream" +version = "0.1.0" +dependencies = [ + "env_logger", + "futures-util", + "livekit", + "log", + "tokio", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bmrng" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54df9073108f1558f90ae6c5bf5ab9c917c4185f5527b280c87a993cbead0ac" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "bumpalo" +version = "3.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c764d619ca78fccbf3069b37bd7af92577f044bb15236036662d79b6559f25b7" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "bzip2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "jobserver", + "libc", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets 0.52.0", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "combine" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cxx" +version = "1.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c15f3b597018782655a05d417f28bac009f6eb60f4b6703eb818998c1aaa16a" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81699747d109bba60bd6f87e7cb24b626824b8427b32f199b95c7faa06ee3dc9" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a7eb4c4fd18505f5a935f9c2ee77780350dcdb56da7cd037634e806141c5c43" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d914fcc6452d133236ee067a9538be25ba6a644a450e1a6c617da84bf029854" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "data-encoding" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "either" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" + +[[package]] +name = "encoding_rs" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "env_filter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3716d7a920fb4fac5d84e9d4bce8ceb321e9414b4409da61b07b75c1e3d0697" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "flate2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets 0.52.0", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "h2" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd5256b483761cd23699d0da46cc6fd2ee3be420bbe6d020ae4a091e70b7e9fd" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "http" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http", + "hyper", + "rustls", + "tokio", + "tokio-rustls", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "jiff" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d699bc6dfc879fb1bf9bdff0d4c56f0884fc6f0d0eb0fba397a6d00cd9a6b85e" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d16e75759ee0aa64c57a56acbf43916987b20c77373cb7e808979e02b93c9f9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "jsonwebtoken" +version = "9.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ae10193d25051e74945f1ea2d0b42e03cc3b890f7e4cc5faa44997d808193f" +dependencies = [ + "base64", + "js-sys", + "ring", + "serde", + "serde_json", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" + +[[package]] +name = "libloading" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +dependencies = [ + "cfg-if", + "windows-targets 0.52.0", +] + +[[package]] +name = "libwebrtc" +version = "0.3.10" +dependencies = [ + "cxx", + "jni", + "js-sys", + "lazy_static", + "livekit-protocol", + "livekit-runtime", + "log", + "parking_lot", + "serde", + "serde_json", + "thiserror", + "tokio", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webrtc-sys", +] + +[[package]] +name = "link-cplusplus" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d240c6f7e1ba3a28b0249f774e6a9dd0175054b52dfbb61b16eb8505c3785c9" +dependencies = [ + "cc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + +[[package]] +name = "livekit" +version = "0.7.8" +dependencies = [ + "bmrng", + "bytes", + "chrono", + "futures-util", + "lazy_static", + "libloading", + "libwebrtc", + "livekit-api", + "livekit-protocol", + "livekit-runtime", + "log", + "parking_lot", + "prost", + "semver", + "serde", + "serde_json", + "thiserror", + "tokio", +] + +[[package]] +name = "livekit-api" +version = "0.4.2" +dependencies = [ + "base64", + "futures-util", + "http", + "jsonwebtoken", + "livekit-protocol", + "livekit-runtime", + "log", + "parking_lot", + "pbjson-types", + "prost", + "rand 0.9.0", + "reqwest", + "scopeguard", + "serde", + "serde_json", + "sha2", + "thiserror", + "tokio", + "tokio-tungstenite", + "url", +] + +[[package]] +name = "livekit-protocol" +version = "0.3.9" +dependencies = [ + "futures-util", + "livekit-runtime", + "parking_lot", + "pbjson", + "pbjson-types", + "prost", + "prost-types", + "serde", + "thiserror", + "tokio", +] + +[[package]] +name = "livekit-runtime" +version = "0.4.0" +dependencies = [ + "tokio", + "tokio-stream", +] + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +dependencies = [ + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.48.0", +] + +[[package]] +name = "multimap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.5", +] + +[[package]] +name = "password-hash" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "pbjson" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1030c719b0ec2a2d25a5df729d6cff1acf3cc230bf766f4f97833591f7577b90" +dependencies = [ + "base64", + "serde", +] + +[[package]] +name = "pbjson-build" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2580e33f2292d34be285c5bc3dba5259542b083cfad6037b6d70345f24dcb735" +dependencies = [ + "heck", + "itertools", + "prost", + "prost-types", +] + +[[package]] +name = "pbjson-types" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18f596653ba4ac51bdecbb4ef6773bc7f56042dc13927910de1684ad3d32aa12" +dependencies = [ + "bytes", + "chrono", + "pbjson", + "pbjson-build", + "prost", + "prost-build", + "serde", +] + +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest", + "hmac", + "password-hash", + "sha2", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "petgraph" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "portable-atomic" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "prettyplease" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c55e02e35260070b6f716a2423c2ff1c3bb1642ddca6f99e1f26d06268a0e2d2" +dependencies = [ + "bytes", + "heck", + "itertools", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn", + "tempfile", + "which", +] + +[[package]] +name = "prost-derive" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "193898f59edcf43c26227dcd4c8427f00d99d61e95dcde58dabd49fa291d470e" +dependencies = [ + "prost", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", + "zerocopy", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.12", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.1", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "regex" +version = "1.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "reqwest" +version = "0.11.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-rustls", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls", + "rustls-native-certs", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-rustls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.12", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustix" +version = "0.38.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +dependencies = [ + "bitflags 2.4.2", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.21.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "ryu" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "scratch" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152" + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" + +[[package]] +name = "socket2" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "2.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67" +dependencies = [ + "cfg-if", + "fastrand", + "rustix", + "windows-sys 0.52.0", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tungstenite" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.8.5", + "sha1", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "walkdir" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" + +[[package]] +name = "web-sys" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webrtc-sys" +version = "0.3.7" +dependencies = [ + "cc", + "cxx", + "cxx-build", + "glob", + "log", + "webrtc-sys-build", +] + +[[package]] +name = "webrtc-sys-build" +version = "0.3.6" +dependencies = [ + "fs2", + "regex", + "reqwest", + "scratch", + "semver", + "zip", +] + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags 2.4.2", +] + +[[package]] +name = "zerocopy" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zip" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +dependencies = [ + "aes", + "byteorder", + "bzip2", + "constant_time_eq", + "crc32fast", + "crossbeam-utils", + "flate2", + "hmac", + "pbkdf2", + "sha1", + "time", + "zstd", +] + +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.9+zstd.1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/examples/basic_text_stream/Cargo.toml b/examples/basic_text_stream/Cargo.toml new file mode 100644 index 000000000..6e7d06a3f --- /dev/null +++ b/examples/basic_text_stream/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "basic_text_stream" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tokio = { version = "1", features = ["full"] } +futures-util = { version = "0.3", default-features = false, features = ["sink"] } +livekit = { path = "../../livekit" } +log = "0.4.26" +env_logger = "0.11.7" + +[workspace] \ No newline at end of file diff --git a/examples/basic_text_stream/README.md b/examples/basic_text_stream/README.md new file mode 100644 index 000000000..02d471c79 --- /dev/null +++ b/examples/basic_text_stream/README.md @@ -0,0 +1,23 @@ +# Basic Text Stream + +Simple example using data streams to send text incrementally to all participants in a room. + +## Usage + +1. Connect to a room as a sender: + +```sh +export LIVEKIT_URL="..." +export LIVEKIT_TOKEN="" +cargo run -- sender +``` + +2. Receive text streams from the first participant: + +```sh +export LIVEKIT_URL="..." +export LIVEKIT_TOKEN="" +cargo run +``` + +3. Optionally run more senders and receivers (each must have a unique token). diff --git a/examples/basic_text_stream/src/main.rs b/examples/basic_text_stream/src/main.rs new file mode 100644 index 000000000..71ed04e03 --- /dev/null +++ b/examples/basic_text_stream/src/main.rs @@ -0,0 +1,65 @@ +use futures_util::TryStreamExt; +use livekit::{Room, RoomEvent, RoomOptions, StreamTextOptions, StreamWriter}; +use std::{env, error::Error, time::Duration}; +use tokio::{sync::mpsc::UnboundedReceiver, time::sleep}; + +static TOPIC: &str = "my-topic"; +static WORDS: &[&str] = &["This", "text", "will", "be", "sent", "incrementally."]; + +#[tokio::main] +async fn main() -> Result<(), Box> { + env_logger::init(); + + let url = env::var("LIVEKIT_URL").expect("LIVEKIT_URL is not set"); + let token = env::var("LIVEKIT_TOKEN").expect("LIVEKIT_TOKEN is not set"); + + let is_sender = env::args().nth(1).map_or(false, |arg| arg == "sender"); + + let (room, rx) = Room::connect(&url, &token, RoomOptions::default()).await?; + println!("Connected to room: {} - {}", room.name(), room.sid().await); + + if is_sender { + run_sender(room).await + } else { + run_receiver(room, rx).await + } +} + +async fn run_sender(room: Room) -> Result<(), Box> { + println!("Running as sender"); + loop { + let options = StreamTextOptions { topic: TOPIC.to_string(), ..Default::default() }; + let writer = room.local_participant().stream_text(options).await?; + println!("Opened new stream"); + + for word in WORDS.iter() { + writer.write(*word).await?; + println!("Sent '{}'", word); + sleep(Duration::from_millis(500)).await; + } + writer.close().await?; + println!("Stream complete"); + } +} + +async fn run_receiver( + _room: Room, + mut rx: UnboundedReceiver, +) -> Result<(), Box> { + println!("Running as receiver"); + println!("Waiting for incoming streams…"); + while let Some(msg) = rx.recv().await { + log::info!("Event: {:?}", msg); + match msg { + RoomEvent::TextStreamOpened { reader, topic, participant_identity } => { + if topic != TOPIC { continue }; + let Some(mut reader) = reader.take() else { continue }; + while let Some(chunk) = reader.try_next().await? { + println!("Chunk received from {}: '{}'", participant_identity, chunk); + } + } + _ => {} + } + } + Ok(()) +} diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 7d56ec5e0..86f9cf9cf 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -34,6 +34,7 @@ dashmap = "5.4" env_logger = "0.10" downcast-rs = "1.2" console-subscriber = { version = "0.1", features = ["parking_lot"], optional = true } +bytes = "1.10.1" [target.'cfg(target_os = "android")'.dependencies] jni = "0.21.1" diff --git a/livekit-ffi/generate_proto.sh b/livekit-ffi/generate_proto.sh index 029ccd3d9..661b351a2 100755 --- a/livekit-ffi/generate_proto.sh +++ b/livekit-ffi/generate_proto.sh @@ -30,4 +30,5 @@ protoc \ $PROTOCOL/audio_frame.proto \ $PROTOCOL/e2ee.proto \ $PROTOCOL/stats.proto \ - $PROTOCOL/rpc.proto + $PROTOCOL/rpc.proto \ + $PROTOCOL/data_stream.proto diff --git a/livekit-ffi/protocol/data_stream.proto b/livekit-ffi/protocol/data_stream.proto new file mode 100644 index 000000000..0b137c447 --- /dev/null +++ b/livekit-ffi/protocol/data_stream.proto @@ -0,0 +1,348 @@ +// Copyright 2025 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto2"; + +package livekit.proto; +option csharp_namespace = "LiveKit.Proto"; + +import "handle.proto"; + +// MARK: - Text stream reader + +// A reader for an incoming stream. +message OwnedTextStreamReader { + required FfiOwnedHandle handle = 1; + required TextStreamInfo info = 2; +} + +// Reads an incoming text stream incrementally. +message TextStreamReaderReadIncrementalRequest { + required uint64 reader_handle = 1; +} +message TextStreamReaderReadIncrementalResponse {} + +// Reads an incoming text stream in its entirety. +message TextStreamReaderReadAllRequest { + required uint64 reader_handle = 1; +} +message TextStreamReaderReadAllResponse { + required uint64 async_id = 1; +} +message TextStreamReaderReadAllCallback { + required uint64 async_id = 1; + oneof result { + string content = 2; + StreamError error = 3; + } +} + +message TextStreamReaderEvent { + required uint64 reader_handle = 1; + oneof detail { + TextStreamReaderChunkReceived chunk_received = 2; + TextStreamReaderEOS eos = 3; + } +} + +message TextStreamReaderChunkReceived { + required string content = 1; +} + +message TextStreamReaderEOS { + optional StreamError error = 1; +} + +// MARK: - Byte stream reader + +// A reader for an incoming stream. +message OwnedByteStreamReader { + required FfiOwnedHandle handle = 1; + required ByteStreamInfo info = 2; +} + +// Reads an incoming byte stream incrementally. +message ByteStreamReaderReadIncrementalRequest { + required uint64 reader_handle = 1; +} +message ByteStreamReaderReadIncrementalResponse {} + +// Reads an incoming byte stream in its entirety. +message ByteStreamReaderReadAllRequest { + required uint64 reader_handle = 1; +} +message ByteStreamReaderReadAllResponse { + required uint64 async_id = 1; +} +message ByteStreamReaderReadAllCallback { + required uint64 async_id = 1; + oneof result { + bytes content = 2; + StreamError error = 3; + } +} + +// Writes data from an incoming stream to a file as it arrives. +message ByteStreamReaderWriteToFileRequest { + required uint64 reader_handle = 1; + + // Directory to write the file in (must be writable by the current process). + // If not provided, the file will be written to the system's temp directory. + optional string directory = 3; + + // Name to use for the written file. + // If not provided, the file's name and extension will be inferred from + // the stream's info. + optional string name_override = 4; +} +message ByteStreamReaderWriteToFileResponse { + required uint64 async_id = 1; +} +message ByteStreamReaderWriteToFileCallback { + required uint64 async_id = 1; + oneof result { + // Path the file was written to. + string file_path = 2; + StreamError error = 3; + } +} + +message ByteStreamReaderEvent { + required uint64 reader_handle = 1; + oneof detail { + ByteStreamReaderChunkReceived chunk_received = 2; + ByteStreamReaderEOS eos = 3; + } +} + +message ByteStreamReaderChunkReceived { + required bytes content = 1; +} + +message ByteStreamReaderEOS { + optional StreamError error = 1; +} + +// MARK: - Send file + +// Sends the contents of a file over a data stream. +message StreamSendFileRequest { + required uint64 local_participant_handle = 1; + + required StreamByteOptions options = 2; + + // Path of the file to send (must be readable by the current process). + required string file_path = 3; +} +message StreamSendFileResponse { + required uint64 async_id = 1; +} +message StreamSendFileCallback { + required uint64 async_id = 1; + oneof result { + ByteStreamInfo info = 2; + StreamError error = 3; + } +} + +// MARK: - Send text + +// Sends text over a data stream. +message StreamSendTextRequest { + required uint64 local_participant_handle = 1; + + required StreamTextOptions options = 2; + + // Text to send. + required string text = 3; +} +message StreamSendTextResponse { + required uint64 async_id = 1; +} +message StreamSendTextCallback { + required uint64 async_id = 1; + oneof result { + TextStreamInfo info = 2; + StreamError error = 3; + } +} + +// MARK: - Byte stream writer + +message OwnedByteStreamWriter { + required FfiOwnedHandle handle = 1; + required ByteStreamInfo info = 2; +} + +// Opens an outgoing stream. +// Call must be balanced with a StreamCloseRequest. +message ByteStreamOpenRequest { + required uint64 local_participant_handle = 1; + + // Options to use for opening the stream. + required StreamByteOptions options = 2; +} +message ByteStreamOpenResponse { + required uint64 async_id = 1; +} +message ByteStreamOpenCallback { + required uint64 async_id = 1; + oneof result { + OwnedByteStreamWriter writer = 2; + StreamError error = 3; + } +} + +// Writes data to a stream writer. +message ByteStreamWriterWriteRequest { + required uint64 writer_handle = 1; + required bytes bytes = 2; +} +message ByteStreamWriterWriteResponse { + required uint64 async_id = 1; +} +message ByteStreamWriterWriteCallback { + required uint64 async_id = 1; + optional StreamError error = 2; +} + +// Closes a stream writer. +message ByteStreamWriterCloseRequest { + required uint64 writer_handle = 1; + optional string reason = 2; +} +message ByteStreamWriterCloseResponse { + required uint64 async_id = 1; +} +message ByteStreamWriterCloseCallback { + required uint64 async_id = 1; + optional StreamError error = 2; +} + +// MARK: - Text stream writer + +message OwnedTextStreamWriter { + required FfiOwnedHandle handle = 1; + required TextStreamInfo info = 2; +} + +// Opens an outgoing text stream. +// Call must be balanced with a TextStreamCloseRequest. +message TextStreamOpenRequest { + required uint64 local_participant_handle = 1; + + // Options to use for opening the stream. + required StreamTextOptions options = 2; +} +message TextStreamOpenResponse { + required uint64 async_id = 1; +} +message TextStreamOpenCallback { + required uint64 async_id = 1; + oneof result { + OwnedTextStreamWriter writer = 2; + StreamError error = 3; + } +} + +// Writes text to a text stream writer. +message TextStreamWriterWriteRequest { + required uint64 writer_handle = 1; + required string text = 2; +} +message TextStreamWriterWriteResponse { + required uint64 async_id = 1; +} +message TextStreamWriterWriteCallback { + required uint64 async_id = 1; + optional StreamError error = 2; +} + +// Closes a text stream writer. +message TextStreamWriterCloseRequest { + required uint64 writer_handle = 1; + optional string reason = 2; +} +message TextStreamWriterCloseResponse { + required uint64 async_id = 1; +} +message TextStreamWriterCloseCallback { + required uint64 async_id = 1; + optional StreamError error = 2; +} + +// Structures + +// Contains a subset of the fields from the stream header. +// Protocol-level fields not relevant to the FFI client are omitted (e.g. encryption info). + +message TextStreamInfo { + enum OperationType { + CREATE = 0; + UPDATE = 1; + DELETE = 2; + REACTION = 3; + } + + required string stream_id = 1; // unique identifier for this data stream + required int64 timestamp = 2; // using int64 for Unix timestamp + required string mime_type = 3; + required string topic = 4; + optional uint64 total_length = 5; // only populated for finite streams, if it's a stream of unknown size this stays empty + map attributes = 6; // user defined attributes map that can carry additional info + + required OperationType operation_type = 7; + optional int32 version = 8; // Optional: Version for updates/edits + optional string reply_to_stream_id = 9; // Optional: Reply to specific message + repeated string attached_stream_ids = 10; // file attachments for text streams + optional bool generated = 11; // true if the text has been generated by an agent from a participant's audio transcription +} +message ByteStreamInfo { + required string stream_id = 1; // unique identifier for this data stream + required int64 timestamp = 2; // using int64 for Unix timestamp + required string mime_type = 3; + required string topic = 4; + optional uint64 total_length = 5; // only populated for finite streams, if it's a stream of unknown size this stays empty + map attributes = 6; // user defined attributes map that can carry additional info + + required string name = 7; +} + +message StreamTextOptions { + required string topic = 1; + map attributes = 2; + repeated string destination_identities = 3; + optional string id = 4; + optional TextStreamInfo.OperationType operation_type = 5; + optional int32 version = 6; + optional string reply_to_stream_id = 7; + repeated string attached_stream_ids = 8; + optional bool generated = 9; + +} +message StreamByteOptions { + required string topic = 1; + map attributes = 2; + repeated string destination_identities = 3; + optional string id = 4; + optional string name = 5; + optional string mime_type = 6; + optional uint64 total_length = 7; +} + +// Error pertaining to a stream. +message StreamError { + // TODO(ladvoc): make this an enum. + required string description = 1; +} diff --git a/livekit-ffi/protocol/ffi.proto b/livekit-ffi/protocol/ffi.proto index b12105c19..7e58123d4 100644 --- a/livekit-ffi/protocol/ffi.proto +++ b/livekit-ffi/protocol/ffi.proto @@ -25,6 +25,7 @@ import "room.proto"; import "video_frame.proto"; import "audio_frame.proto"; import "rpc.proto"; +import "data_stream.proto"; // **How is the livekit-ffi working: // We refer as the ffi server the Rust server that is running the LiveKit client implementation, and we @@ -112,7 +113,7 @@ message FfiRequest { EnableRemoteTrackPublicationRequest enable_remote_track_publication = 42; UpdateRemoteTrackPublicationDimensionRequest update_remote_track_publication_dimension = 43; - // Data Streams + // Data Streams (low level) SendStreamHeaderRequest send_stream_header = 44; SendStreamChunkRequest send_stream_chunk = 45; SendStreamTrailerRequest send_stream_trailer = 46; @@ -128,7 +129,26 @@ message FfiRequest { ApmProcessReverseStreamRequest apm_process_reverse_stream = 52; ApmSetStreamDelayRequest apm_set_stream_delay = 53; - // NEXT_ID: 54 + // Data Streams (high level) + ByteStreamReaderReadIncrementalRequest byte_read_incremental = 54; + ByteStreamReaderReadAllRequest byte_read_all = 55; + ByteStreamReaderWriteToFileRequest byte_write_to_file = 56; + + TextStreamReaderReadIncrementalRequest text_read_incremental = 57; + TextStreamReaderReadAllRequest text_read_all = 58; + + StreamSendFileRequest send_file = 59; + StreamSendTextRequest send_text = 60; + + ByteStreamOpenRequest byte_stream_open = 61; + ByteStreamWriterWriteRequest byte_stream_write = 62; + ByteStreamWriterCloseRequest byte_stream_close = 63; + + TextStreamOpenRequest text_stream_open = 64; + TextStreamWriterWriteRequest text_stream_write = 65; + TextStreamWriterCloseRequest text_stream_close = 66; + + // NEXT_ID: 67 } } @@ -204,7 +224,26 @@ message FfiResponse { ApmProcessReverseStreamResponse apm_process_reverse_stream = 51; ApmSetStreamDelayResponse apm_set_stream_delay = 52; - // NEXT_ID: 53 + // Data Streams (high level) + ByteStreamReaderReadIncrementalResponse byte_read_incremental = 53; + ByteStreamReaderReadAllResponse byte_read_all = 54; + ByteStreamReaderWriteToFileResponse byte_write_to_file = 55; + + TextStreamReaderReadIncrementalResponse text_read_incremental = 56; + TextStreamReaderReadAllResponse text_read_all = 57; + + StreamSendFileResponse send_file = 58; + StreamSendTextResponse send_text = 59; + + ByteStreamOpenResponse byte_stream_open = 60; + ByteStreamWriterWriteResponse byte_stream_write = 61; + ByteStreamWriterCloseResponse byte_stream_close = 62; + + TextStreamOpenResponse text_stream_open = 63; + TextStreamWriterWriteResponse text_stream_write = 64; + TextStreamWriterCloseResponse text_stream_close = 65; + + // NEXT_ID: 66 } } @@ -236,9 +275,29 @@ message FfiEvent { SendChatMessageCallback chat_message = 22; PerformRpcCallback perform_rpc = 23; RpcMethodInvocationEvent rpc_method_invocation = 24; + + // Data Streams (low level) SendStreamHeaderCallback send_stream_header = 25; SendStreamChunkCallback send_stream_chunk = 26; SendStreamTrailerCallback send_stream_trailer = 27; + + // Data Streams (high level) + ByteStreamReaderEvent byte_stream_reader_event = 28; + ByteStreamReaderReadAllCallback byte_stream_reader_read_all = 29; + ByteStreamReaderWriteToFileCallback byte_stream_reader_write_to_file = 30; + + ByteStreamOpenCallback byte_stream_open = 31; + ByteStreamWriterWriteCallback byte_stream_writer_write = 32; + ByteStreamWriterCloseCallback byte_stream_writer_close = 33; + StreamSendFileCallback send_file = 34; + + TextStreamReaderEvent text_stream_reader_event = 35; + TextStreamReaderReadAllCallback text_stream_reader_read_all = 36; + + TextStreamOpenCallback text_stream_open = 37; + TextStreamWriterWriteCallback text_stream_writer_write = 38; + TextStreamWriterCloseCallback text_stream_writer_close = 39; + StreamSendTextCallback send_text = 40; } } diff --git a/livekit-ffi/protocol/room.proto b/livekit-ffi/protocol/room.proto index 051a94e5c..b8fce75f9 100644 --- a/livekit-ffi/protocol/room.proto +++ b/livekit-ffi/protocol/room.proto @@ -23,6 +23,7 @@ import "participant.proto"; import "track.proto"; import "video_frame.proto"; import "stats.proto"; +import "data_stream.proto"; // Connect to a new LiveKit room message ConnectRequest { @@ -367,10 +368,14 @@ message RoomEvent { DataPacketReceived data_packet_received = 27; TranscriptionReceived transcription_received = 28; ChatMessageReceived chat_message = 29; + // Data stream (low level) DataStreamHeaderReceived stream_header_received = 30; DataStreamChunkReceived stream_chunk_received = 31; DataStreamTrailerReceived stream_trailer_received = 32; DataChannelBufferedAmountLowThresholdChanged data_channel_low_threshold_changed = 33; + // Data stream (high level) + ByteStreamOpened byte_stream_opened = 34; + TextStreamOpened text_stream_opened = 35; } } @@ -665,3 +670,13 @@ message DataChannelBufferedAmountLowThresholdChanged { required DataPacketKind kind = 1; required uint64 threshold = 2; } + +message ByteStreamOpened { + required OwnedByteStreamReader reader = 1; + required string participant_identity = 2; +} + +message TextStreamOpened { + required OwnedTextStreamReader reader = 1; + required string participant_identity = 2; +} \ No newline at end of file diff --git a/livekit-ffi/src/conversion/data_stream.rs b/livekit-ffi/src/conversion/data_stream.rs new file mode 100644 index 000000000..d0df96a50 --- /dev/null +++ b/livekit-ffi/src/conversion/data_stream.rs @@ -0,0 +1,150 @@ +// Copyright 2025 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::proto::{self}; +use bytes::Bytes; +use livekit::{ + ByteStreamInfo, OperationType, StreamByteOptions, StreamError, StreamResult, StreamTextOptions, + TextStreamInfo, +}; +use std::path::PathBuf; + +impl From for proto::TextStreamInfo { + fn from(info: TextStreamInfo) -> Self { + Self { + stream_id: info.id, + timestamp: info.timestamp.timestamp_millis(), + mime_type: info.mime_type, + topic: info.topic, + total_length: info.total_length, + attributes: info.attributes, + operation_type: proto::text_stream_info::OperationType::from(info.operation_type) + .into(), + version: Some(info.version), + reply_to_stream_id: info.reply_to_stream_id, + attached_stream_ids: info.attached_stream_ids, + generated: Some(info.generated), + } + } +} + +impl From for proto::ByteStreamInfo { + fn from(info: ByteStreamInfo) -> Self { + Self { + stream_id: info.id, + timestamp: info.timestamp.timestamp_millis(), + mime_type: info.mime_type, + topic: info.topic, + total_length: info.total_length, + attributes: info.attributes, + name: info.name, + } + } +} + +impl From for StreamTextOptions { + fn from(options: proto::StreamTextOptions) -> Self { + let operation_type = options.operation_type().into(); + Self { + topic: options.topic, + attributes: options.attributes, + destination_identities: options + .destination_identities + .into_iter() + .map(|id| id.into()) + .collect(), + id: options.id, + operation_type: Some(operation_type), + version: options.version, + reply_to_stream_id: options.reply_to_stream_id, + attached_stream_ids: options.attached_stream_ids, + generated: options.generated, + } + } +} + +impl From for StreamByteOptions { + fn from(options: proto::StreamByteOptions) -> Self { + Self { + topic: options.topic, + attributes: options.attributes, + destination_identities: options + .destination_identities + .into_iter() + .map(|id| id.into()) + .collect(), + id: options.id, + name: options.name, + mime_type: options.mime_type, + total_length: options.total_length, + } + } +} + +impl From> for proto::byte_stream_reader_read_all_callback::Result { + fn from(result: StreamResult) -> Self { + match result { + Ok(content) => Self::Content(content.to_vec()), + Err(error) => Self::Error(error.into()), + } + } +} + +impl From> + for proto::byte_stream_reader_write_to_file_callback::Result +{ + fn from(result: Result) -> Self { + match result { + Ok(path) => Self::FilePath(path.to_string_lossy().to_string()), + Err(error) => Self::Error(error.into()), + } + } +} + +impl From> for proto::text_stream_reader_read_all_callback::Result { + fn from(result: StreamResult) -> Self { + match result { + Ok(content) => Self::Content(content), + Err(error) => Self::Error(error.into()), + } + } +} + +impl From for proto::text_stream_info::OperationType { + fn from(value: OperationType) -> Self { + match value { + OperationType::Create => Self::Create, + OperationType::Update => Self::Update, + OperationType::Delete => Self::Delete, + OperationType::Reaction => Self::Reaction, + } + } +} + +impl From for OperationType { + fn from(value: proto::text_stream_info::OperationType) -> Self { + match value { + proto::text_stream_info::OperationType::Create => Self::Create, + proto::text_stream_info::OperationType::Update => Self::Update, + proto::text_stream_info::OperationType::Delete => Self::Delete, + proto::text_stream_info::OperationType::Reaction => Self::Reaction, + } + } +} + +impl From for proto::StreamError { + fn from(error: StreamError) -> Self { + Self { description: error.to_string() } + } +} diff --git a/livekit-ffi/src/conversion/mod.rs b/livekit-ffi/src/conversion/mod.rs index 64820b3ad..0c9f9f061 100644 --- a/livekit-ffi/src/conversion/mod.rs +++ b/livekit-ffi/src/conversion/mod.rs @@ -13,6 +13,7 @@ // limitations under the License. pub mod audio_frame; +pub mod data_stream; pub mod participant; pub mod resampler; pub mod room; diff --git a/livekit-ffi/src/livekit.proto.rs b/livekit-ffi/src/livekit.proto.rs index 223669f17..be0e5c1bc 100644 --- a/livekit-ffi/src/livekit.proto.rs +++ b/livekit-ffi/src/livekit.proto.rs @@ -2146,6 +2146,624 @@ impl VideoSourceType { } } } +// MARK: - Text stream reader + +/// A reader for an incoming stream. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct OwnedTextStreamReader { + #[prost(message, required, tag="1")] + pub handle: FfiOwnedHandle, + #[prost(message, required, tag="2")] + pub info: TextStreamInfo, +} +/// Reads an incoming text stream incrementally. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TextStreamReaderReadIncrementalRequest { + #[prost(uint64, required, tag="1")] + pub reader_handle: u64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TextStreamReaderReadIncrementalResponse { +} +/// Reads an incoming text stream in its entirety. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TextStreamReaderReadAllRequest { + #[prost(uint64, required, tag="1")] + pub reader_handle: u64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TextStreamReaderReadAllResponse { + #[prost(uint64, required, tag="1")] + pub async_id: u64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TextStreamReaderReadAllCallback { + #[prost(uint64, required, tag="1")] + pub async_id: u64, + #[prost(oneof="text_stream_reader_read_all_callback::Result", tags="2, 3")] + pub result: ::core::option::Option, +} +/// Nested message and enum types in `TextStreamReaderReadAllCallback`. +pub mod text_stream_reader_read_all_callback { + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Result { + #[prost(string, tag="2")] + Content(::prost::alloc::string::String), + #[prost(message, tag="3")] + Error(super::StreamError), + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TextStreamReaderEvent { + #[prost(uint64, required, tag="1")] + pub reader_handle: u64, + #[prost(oneof="text_stream_reader_event::Detail", tags="2, 3")] + pub detail: ::core::option::Option, +} +/// Nested message and enum types in `TextStreamReaderEvent`. +pub mod text_stream_reader_event { + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Detail { + #[prost(message, tag="2")] + ChunkReceived(super::TextStreamReaderChunkReceived), + #[prost(message, tag="3")] + Eos(super::TextStreamReaderEos), + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TextStreamReaderChunkReceived { + #[prost(string, required, tag="1")] + pub content: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TextStreamReaderEos { + #[prost(message, optional, tag="1")] + pub error: ::core::option::Option, +} +// MARK: - Byte stream reader + +/// A reader for an incoming stream. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct OwnedByteStreamReader { + #[prost(message, required, tag="1")] + pub handle: FfiOwnedHandle, + #[prost(message, required, tag="2")] + pub info: ByteStreamInfo, +} +/// Reads an incoming byte stream incrementally. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ByteStreamReaderReadIncrementalRequest { + #[prost(uint64, required, tag="1")] + pub reader_handle: u64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ByteStreamReaderReadIncrementalResponse { +} +/// Reads an incoming byte stream in its entirety. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ByteStreamReaderReadAllRequest { + #[prost(uint64, required, tag="1")] + pub reader_handle: u64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ByteStreamReaderReadAllResponse { + #[prost(uint64, required, tag="1")] + pub async_id: u64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ByteStreamReaderReadAllCallback { + #[prost(uint64, required, tag="1")] + pub async_id: u64, + #[prost(oneof="byte_stream_reader_read_all_callback::Result", tags="2, 3")] + pub result: ::core::option::Option, +} +/// Nested message and enum types in `ByteStreamReaderReadAllCallback`. +pub mod byte_stream_reader_read_all_callback { + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Result { + #[prost(bytes, tag="2")] + Content(::prost::alloc::vec::Vec), + #[prost(message, tag="3")] + Error(super::StreamError), + } +} +/// Writes data from an incoming stream to a file as it arrives. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ByteStreamReaderWriteToFileRequest { + #[prost(uint64, required, tag="1")] + pub reader_handle: u64, + /// Directory to write the file in (must be writable by the current process). + /// If not provided, the file will be written to the system's temp directory. + #[prost(string, optional, tag="3")] + pub directory: ::core::option::Option<::prost::alloc::string::String>, + /// Name to use for the written file. + /// If not provided, the file's name and extension will be inferred from + /// the stream's info. + #[prost(string, optional, tag="4")] + pub name_override: ::core::option::Option<::prost::alloc::string::String>, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ByteStreamReaderWriteToFileResponse { + #[prost(uint64, required, tag="1")] + pub async_id: u64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ByteStreamReaderWriteToFileCallback { + #[prost(uint64, required, tag="1")] + pub async_id: u64, + #[prost(oneof="byte_stream_reader_write_to_file_callback::Result", tags="2, 3")] + pub result: ::core::option::Option, +} +/// Nested message and enum types in `ByteStreamReaderWriteToFileCallback`. +pub mod byte_stream_reader_write_to_file_callback { + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Result { + /// Path the file was written to. + #[prost(string, tag="2")] + FilePath(::prost::alloc::string::String), + #[prost(message, tag="3")] + Error(super::StreamError), + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ByteStreamReaderEvent { + #[prost(uint64, required, tag="1")] + pub reader_handle: u64, + #[prost(oneof="byte_stream_reader_event::Detail", tags="2, 3")] + pub detail: ::core::option::Option, +} +/// Nested message and enum types in `ByteStreamReaderEvent`. +pub mod byte_stream_reader_event { + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Detail { + #[prost(message, tag="2")] + ChunkReceived(super::ByteStreamReaderChunkReceived), + #[prost(message, tag="3")] + Eos(super::ByteStreamReaderEos), + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ByteStreamReaderChunkReceived { + #[prost(bytes="vec", required, tag="1")] + pub content: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ByteStreamReaderEos { + #[prost(message, optional, tag="1")] + pub error: ::core::option::Option, +} +// MARK: - Send file + +/// Sends the contents of a file over a data stream. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct StreamSendFileRequest { + #[prost(uint64, required, tag="1")] + pub local_participant_handle: u64, + #[prost(message, required, tag="2")] + pub options: StreamByteOptions, + /// Path of the file to send (must be readable by the current process). + #[prost(string, required, tag="3")] + pub file_path: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct StreamSendFileResponse { + #[prost(uint64, required, tag="1")] + pub async_id: u64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct StreamSendFileCallback { + #[prost(uint64, required, tag="1")] + pub async_id: u64, + #[prost(oneof="stream_send_file_callback::Result", tags="2, 3")] + pub result: ::core::option::Option, +} +/// Nested message and enum types in `StreamSendFileCallback`. +pub mod stream_send_file_callback { + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Result { + #[prost(message, tag="2")] + Info(super::ByteStreamInfo), + #[prost(message, tag="3")] + Error(super::StreamError), + } +} +// MARK: - Send text + +/// Sends text over a data stream. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct StreamSendTextRequest { + #[prost(uint64, required, tag="1")] + pub local_participant_handle: u64, + #[prost(message, required, tag="2")] + pub options: StreamTextOptions, + /// Text to send. + #[prost(string, required, tag="3")] + pub text: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct StreamSendTextResponse { + #[prost(uint64, required, tag="1")] + pub async_id: u64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct StreamSendTextCallback { + #[prost(uint64, required, tag="1")] + pub async_id: u64, + #[prost(oneof="stream_send_text_callback::Result", tags="2, 3")] + pub result: ::core::option::Option, +} +/// Nested message and enum types in `StreamSendTextCallback`. +pub mod stream_send_text_callback { + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Result { + #[prost(message, tag="2")] + Info(super::TextStreamInfo), + #[prost(message, tag="3")] + Error(super::StreamError), + } +} +// MARK: - Byte stream writer + +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct OwnedByteStreamWriter { + #[prost(message, required, tag="1")] + pub handle: FfiOwnedHandle, + #[prost(message, required, tag="2")] + pub info: ByteStreamInfo, +} +/// Opens an outgoing stream. +/// Call must be balanced with a StreamCloseRequest. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ByteStreamOpenRequest { + #[prost(uint64, required, tag="1")] + pub local_participant_handle: u64, + /// Options to use for opening the stream. + #[prost(message, required, tag="2")] + pub options: StreamByteOptions, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ByteStreamOpenResponse { + #[prost(uint64, required, tag="1")] + pub async_id: u64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ByteStreamOpenCallback { + #[prost(uint64, required, tag="1")] + pub async_id: u64, + #[prost(oneof="byte_stream_open_callback::Result", tags="2, 3")] + pub result: ::core::option::Option, +} +/// Nested message and enum types in `ByteStreamOpenCallback`. +pub mod byte_stream_open_callback { + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Result { + #[prost(message, tag="2")] + Writer(super::OwnedByteStreamWriter), + #[prost(message, tag="3")] + Error(super::StreamError), + } +} +/// Writes data to a stream writer. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ByteStreamWriterWriteRequest { + #[prost(uint64, required, tag="1")] + pub writer_handle: u64, + #[prost(bytes="vec", required, tag="2")] + pub bytes: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ByteStreamWriterWriteResponse { + #[prost(uint64, required, tag="1")] + pub async_id: u64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ByteStreamWriterWriteCallback { + #[prost(uint64, required, tag="1")] + pub async_id: u64, + #[prost(message, optional, tag="2")] + pub error: ::core::option::Option, +} +/// Closes a stream writer. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ByteStreamWriterCloseRequest { + #[prost(uint64, required, tag="1")] + pub writer_handle: u64, + #[prost(string, optional, tag="2")] + pub reason: ::core::option::Option<::prost::alloc::string::String>, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ByteStreamWriterCloseResponse { + #[prost(uint64, required, tag="1")] + pub async_id: u64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ByteStreamWriterCloseCallback { + #[prost(uint64, required, tag="1")] + pub async_id: u64, + #[prost(message, optional, tag="2")] + pub error: ::core::option::Option, +} +// MARK: - Text stream writer + +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct OwnedTextStreamWriter { + #[prost(message, required, tag="1")] + pub handle: FfiOwnedHandle, + #[prost(message, required, tag="2")] + pub info: TextStreamInfo, +} +/// Opens an outgoing text stream. +/// Call must be balanced with a TextStreamCloseRequest. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TextStreamOpenRequest { + #[prost(uint64, required, tag="1")] + pub local_participant_handle: u64, + /// Options to use for opening the stream. + #[prost(message, required, tag="2")] + pub options: StreamTextOptions, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TextStreamOpenResponse { + #[prost(uint64, required, tag="1")] + pub async_id: u64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TextStreamOpenCallback { + #[prost(uint64, required, tag="1")] + pub async_id: u64, + #[prost(oneof="text_stream_open_callback::Result", tags="2, 3")] + pub result: ::core::option::Option, +} +/// Nested message and enum types in `TextStreamOpenCallback`. +pub mod text_stream_open_callback { + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Result { + #[prost(message, tag="2")] + Writer(super::OwnedTextStreamWriter), + #[prost(message, tag="3")] + Error(super::StreamError), + } +} +/// Writes text to a text stream writer. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TextStreamWriterWriteRequest { + #[prost(uint64, required, tag="1")] + pub writer_handle: u64, + #[prost(string, required, tag="2")] + pub text: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TextStreamWriterWriteResponse { + #[prost(uint64, required, tag="1")] + pub async_id: u64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TextStreamWriterWriteCallback { + #[prost(uint64, required, tag="1")] + pub async_id: u64, + #[prost(message, optional, tag="2")] + pub error: ::core::option::Option, +} +/// Closes a text stream writer. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TextStreamWriterCloseRequest { + #[prost(uint64, required, tag="1")] + pub writer_handle: u64, + #[prost(string, optional, tag="2")] + pub reason: ::core::option::Option<::prost::alloc::string::String>, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TextStreamWriterCloseResponse { + #[prost(uint64, required, tag="1")] + pub async_id: u64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TextStreamWriterCloseCallback { + #[prost(uint64, required, tag="1")] + pub async_id: u64, + #[prost(message, optional, tag="2")] + pub error: ::core::option::Option, +} +// Structures + +// Contains a subset of the fields from the stream header. +// Protocol-level fields not relevant to the FFI client are omitted (e.g. encryption info). + +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TextStreamInfo { + /// unique identifier for this data stream + #[prost(string, required, tag="1")] + pub stream_id: ::prost::alloc::string::String, + /// using int64 for Unix timestamp + #[prost(int64, required, tag="2")] + pub timestamp: i64, + #[prost(string, required, tag="3")] + pub mime_type: ::prost::alloc::string::String, + #[prost(string, required, tag="4")] + pub topic: ::prost::alloc::string::String, + /// only populated for finite streams, if it's a stream of unknown size this stays empty + #[prost(uint64, optional, tag="5")] + pub total_length: ::core::option::Option, + /// user defined attributes map that can carry additional info + #[prost(map="string, string", tag="6")] + pub attributes: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + #[prost(enumeration="text_stream_info::OperationType", required, tag="7")] + pub operation_type: i32, + /// Optional: Version for updates/edits + #[prost(int32, optional, tag="8")] + pub version: ::core::option::Option, + /// Optional: Reply to specific message + #[prost(string, optional, tag="9")] + pub reply_to_stream_id: ::core::option::Option<::prost::alloc::string::String>, + /// file attachments for text streams + #[prost(string, repeated, tag="10")] + pub attached_stream_ids: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + /// true if the text has been generated by an agent from a participant's audio transcription + #[prost(bool, optional, tag="11")] + pub generated: ::core::option::Option, +} +/// Nested message and enum types in `TextStreamInfo`. +pub mod text_stream_info { + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] + #[repr(i32)] + pub enum OperationType { + Create = 0, + Update = 1, + Delete = 2, + Reaction = 3, + } + impl OperationType { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + OperationType::Create => "CREATE", + OperationType::Update => "UPDATE", + OperationType::Delete => "DELETE", + OperationType::Reaction => "REACTION", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "CREATE" => Some(Self::Create), + "UPDATE" => Some(Self::Update), + "DELETE" => Some(Self::Delete), + "REACTION" => Some(Self::Reaction), + _ => None, + } + } + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ByteStreamInfo { + /// unique identifier for this data stream + #[prost(string, required, tag="1")] + pub stream_id: ::prost::alloc::string::String, + /// using int64 for Unix timestamp + #[prost(int64, required, tag="2")] + pub timestamp: i64, + #[prost(string, required, tag="3")] + pub mime_type: ::prost::alloc::string::String, + #[prost(string, required, tag="4")] + pub topic: ::prost::alloc::string::String, + /// only populated for finite streams, if it's a stream of unknown size this stays empty + #[prost(uint64, optional, tag="5")] + pub total_length: ::core::option::Option, + /// user defined attributes map that can carry additional info + #[prost(map="string, string", tag="6")] + pub attributes: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + #[prost(string, required, tag="7")] + pub name: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct StreamTextOptions { + #[prost(string, required, tag="1")] + pub topic: ::prost::alloc::string::String, + #[prost(map="string, string", tag="2")] + pub attributes: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + #[prost(string, repeated, tag="3")] + pub destination_identities: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(string, optional, tag="4")] + pub id: ::core::option::Option<::prost::alloc::string::String>, + #[prost(enumeration="text_stream_info::OperationType", optional, tag="5")] + pub operation_type: ::core::option::Option, + #[prost(int32, optional, tag="6")] + pub version: ::core::option::Option, + #[prost(string, optional, tag="7")] + pub reply_to_stream_id: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, repeated, tag="8")] + pub attached_stream_ids: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(bool, optional, tag="9")] + pub generated: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct StreamByteOptions { + #[prost(string, required, tag="1")] + pub topic: ::prost::alloc::string::String, + #[prost(map="string, string", tag="2")] + pub attributes: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + #[prost(string, repeated, tag="3")] + pub destination_identities: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(string, optional, tag="4")] + pub id: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="5")] + pub name: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="6")] + pub mime_type: ::core::option::Option<::prost::alloc::string::String>, + #[prost(uint64, optional, tag="7")] + pub total_length: ::core::option::Option, +} +/// Error pertaining to a stream. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct StreamError { + /// TODO(ladvoc): make this an enum. + #[prost(string, required, tag="1")] + pub description: ::prost::alloc::string::String, +} /// Connect to a new LiveKit room #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -2664,7 +3282,7 @@ pub struct OwnedBuffer { pub struct RoomEvent { #[prost(uint64, required, tag="1")] pub room_handle: u64, - #[prost(oneof="room_event::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33")] + #[prost(oneof="room_event::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35")] pub message: ::core::option::Option, } /// Nested message and enum types in `RoomEvent`. @@ -2730,6 +3348,7 @@ pub mod room_event { TranscriptionReceived(super::TranscriptionReceived), #[prost(message, tag="29")] ChatMessage(super::ChatMessageReceived), + /// Data stream (low level) #[prost(message, tag="30")] StreamHeaderReceived(super::DataStreamHeaderReceived), #[prost(message, tag="31")] @@ -2738,6 +3357,11 @@ pub mod room_event { StreamTrailerReceived(super::DataStreamTrailerReceived), #[prost(message, tag="33")] DataChannelLowThresholdChanged(super::DataChannelBufferedAmountLowThresholdChanged), + /// Data stream (high level) + #[prost(message, tag="34")] + ByteStreamOpened(super::ByteStreamOpened), + #[prost(message, tag="35")] + TextStreamOpened(super::TextStreamOpened), } } #[allow(clippy::derive_partial_eq_without_eq)] @@ -3275,6 +3899,22 @@ pub struct DataChannelBufferedAmountLowThresholdChanged { #[prost(uint64, required, tag="2")] pub threshold: u64, } +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ByteStreamOpened { + #[prost(message, required, tag="1")] + pub reader: OwnedByteStreamReader, + #[prost(string, required, tag="2")] + pub participant_identity: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TextStreamOpened { + #[prost(message, required, tag="1")] + pub reader: OwnedTextStreamReader, + #[prost(string, required, tag="2")] + pub participant_identity: ::prost::alloc::string::String, +} #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] pub enum IceTransportType { @@ -4164,7 +4804,7 @@ pub struct RpcMethodInvocationEvent { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiRequest { - #[prost(oneof="ffi_request::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 48, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 49, 50, 51, 52, 53")] + #[prost(oneof="ffi_request::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 48, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66")] pub message: ::core::option::Option, } /// Nested message and enum types in `FfiRequest`. @@ -4264,7 +4904,7 @@ pub mod ffi_request { EnableRemoteTrackPublication(super::EnableRemoteTrackPublicationRequest), #[prost(message, tag="43")] UpdateRemoteTrackPublicationDimension(super::UpdateRemoteTrackPublicationDimensionRequest), - /// Data Streams + /// Data Streams (low level) #[prost(message, tag="44")] SendStreamHeader(super::SendStreamHeaderRequest), #[prost(message, tag="45")] @@ -4285,13 +4925,40 @@ pub mod ffi_request { ApmProcessReverseStream(super::ApmProcessReverseStreamRequest), #[prost(message, tag="53")] ApmSetStreamDelay(super::ApmSetStreamDelayRequest), + /// Data Streams (high level) + #[prost(message, tag="54")] + ByteReadIncremental(super::ByteStreamReaderReadIncrementalRequest), + #[prost(message, tag="55")] + ByteReadAll(super::ByteStreamReaderReadAllRequest), + #[prost(message, tag="56")] + ByteWriteToFile(super::ByteStreamReaderWriteToFileRequest), + #[prost(message, tag="57")] + TextReadIncremental(super::TextStreamReaderReadIncrementalRequest), + #[prost(message, tag="58")] + TextReadAll(super::TextStreamReaderReadAllRequest), + #[prost(message, tag="59")] + SendFile(super::StreamSendFileRequest), + #[prost(message, tag="60")] + SendText(super::StreamSendTextRequest), + #[prost(message, tag="61")] + ByteStreamOpen(super::ByteStreamOpenRequest), + #[prost(message, tag="62")] + ByteStreamWrite(super::ByteStreamWriterWriteRequest), + #[prost(message, tag="63")] + ByteStreamClose(super::ByteStreamWriterCloseRequest), + #[prost(message, tag="64")] + TextStreamOpen(super::TextStreamOpenRequest), + #[prost(message, tag="65")] + TextStreamWrite(super::TextStreamWriterWriteRequest), + #[prost(message, tag="66")] + TextStreamClose(super::TextStreamWriterCloseRequest), } } /// This is the output of livekit_ffi_request function. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiResponse { - #[prost(oneof="ffi_response::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 47, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 48, 49, 50, 51, 52")] + #[prost(oneof="ffi_response::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 47, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65")] pub message: ::core::option::Option, } /// Nested message and enum types in `FfiResponse`. @@ -4410,6 +5077,33 @@ pub mod ffi_response { ApmProcessReverseStream(super::ApmProcessReverseStreamResponse), #[prost(message, tag="52")] ApmSetStreamDelay(super::ApmSetStreamDelayResponse), + /// Data Streams (high level) + #[prost(message, tag="53")] + ByteReadIncremental(super::ByteStreamReaderReadIncrementalResponse), + #[prost(message, tag="54")] + ByteReadAll(super::ByteStreamReaderReadAllResponse), + #[prost(message, tag="55")] + ByteWriteToFile(super::ByteStreamReaderWriteToFileResponse), + #[prost(message, tag="56")] + TextReadIncremental(super::TextStreamReaderReadIncrementalResponse), + #[prost(message, tag="57")] + TextReadAll(super::TextStreamReaderReadAllResponse), + #[prost(message, tag="58")] + SendFile(super::StreamSendFileResponse), + #[prost(message, tag="59")] + SendText(super::StreamSendTextResponse), + #[prost(message, tag="60")] + ByteStreamOpen(super::ByteStreamOpenResponse), + #[prost(message, tag="61")] + ByteStreamWrite(super::ByteStreamWriterWriteResponse), + #[prost(message, tag="62")] + ByteStreamClose(super::ByteStreamWriterCloseResponse), + #[prost(message, tag="63")] + TextStreamOpen(super::TextStreamOpenResponse), + #[prost(message, tag="64")] + TextStreamWrite(super::TextStreamWriterWriteResponse), + #[prost(message, tag="65")] + TextStreamClose(super::TextStreamWriterCloseResponse), } } /// To minimize complexity, participant events are not included in the protocol. @@ -4418,7 +5112,7 @@ pub mod ffi_response { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiEvent { - #[prost(oneof="ffi_event::Message", tags="1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27")] + #[prost(oneof="ffi_event::Message", tags="1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40")] pub message: ::core::option::Option, } /// Nested message and enum types in `FfiEvent`. @@ -4472,12 +5166,40 @@ pub mod ffi_event { PerformRpc(super::PerformRpcCallback), #[prost(message, tag="24")] RpcMethodInvocation(super::RpcMethodInvocationEvent), + /// Data Streams (low level) #[prost(message, tag="25")] SendStreamHeader(super::SendStreamHeaderCallback), #[prost(message, tag="26")] SendStreamChunk(super::SendStreamChunkCallback), #[prost(message, tag="27")] SendStreamTrailer(super::SendStreamTrailerCallback), + /// Data Streams (high level) + #[prost(message, tag="28")] + ByteStreamReaderEvent(super::ByteStreamReaderEvent), + #[prost(message, tag="29")] + ByteStreamReaderReadAll(super::ByteStreamReaderReadAllCallback), + #[prost(message, tag="30")] + ByteStreamReaderWriteToFile(super::ByteStreamReaderWriteToFileCallback), + #[prost(message, tag="31")] + ByteStreamOpen(super::ByteStreamOpenCallback), + #[prost(message, tag="32")] + ByteStreamWriterWrite(super::ByteStreamWriterWriteCallback), + #[prost(message, tag="33")] + ByteStreamWriterClose(super::ByteStreamWriterCloseCallback), + #[prost(message, tag="34")] + SendFile(super::StreamSendFileCallback), + #[prost(message, tag="35")] + TextStreamReaderEvent(super::TextStreamReaderEvent), + #[prost(message, tag="36")] + TextStreamReaderReadAll(super::TextStreamReaderReadAllCallback), + #[prost(message, tag="37")] + TextStreamOpen(super::TextStreamOpenCallback), + #[prost(message, tag="38")] + TextStreamWriterWrite(super::TextStreamWriterWriteCallback), + #[prost(message, tag="39")] + TextStreamWriterClose(super::TextStreamWriterCloseCallback), + #[prost(message, tag="40")] + SendText(super::StreamSendTextCallback), } } /// Stop all rooms synchronously (Do we need async here?). diff --git a/livekit-ffi/src/server/data_stream.rs b/livekit-ffi/src/server/data_stream.rs new file mode 100644 index 000000000..7723d7fcd --- /dev/null +++ b/livekit-ffi/src/server/data_stream.rs @@ -0,0 +1,310 @@ +// Copyright 2025 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use futures_util::StreamExt; +use livekit::{ + ByteStreamReader, ByteStreamWriter, StreamReader, StreamWriter, TextStreamReader, + TextStreamWriter, +}; + +use super::{FfiHandle, FfiServer}; +use crate::{proto, FfiHandleId, FfiResult}; + +/// FFI wrapper around [ByteStreamReader]. +pub struct FfiByteStreamReader { + pub handle_id: FfiHandleId, + pub inner: ByteStreamReader, +} + +/// FFI wrapper around [TextStreamReader]. +pub struct FfiTextStreamReader { + pub handle_id: FfiHandleId, + pub inner: TextStreamReader, +} +/// FFI wrapper around [ByteStreamWriter]. +pub struct FfiByteStreamWriter { + pub handle_id: FfiHandleId, + inner: ByteStreamWriter, +} + +/// FFI wrapper around [TextStreamWriter]. +pub struct FfiTextStreamWriter { + pub handle_id: FfiHandleId, + inner: TextStreamWriter, +} + +impl FfiHandle for FfiByteStreamReader {} +impl FfiHandle for FfiTextStreamReader {} +impl FfiHandle for FfiByteStreamWriter {} +impl FfiHandle for FfiTextStreamWriter {} + +impl FfiByteStreamReader { + pub fn read_incremental( + self, + server: &'static FfiServer, + _request: proto::ByteStreamReaderReadIncrementalRequest, + ) -> FfiResult { + let handle = server.async_runtime.spawn(async move { + let mut stream = self.inner; + while let Some(result) = stream.next().await { + match result { + Ok(bytes) => { + let detail = + proto::ByteStreamReaderChunkReceived { content: bytes.to_vec() }; + let event = proto::ByteStreamReaderEvent { + reader_handle: self.handle_id, + detail: Some(proto::byte_stream_reader_event::Detail::ChunkReceived( + detail, + )), + }; + let _ = server + .send_event(proto::ffi_event::Message::ByteStreamReaderEvent(event)); + } + Err(err) => { + let detail = proto::ByteStreamReaderEos { error: Some(err.into()) }; + let event = proto::ByteStreamReaderEvent { + reader_handle: self.handle_id, + detail: Some(proto::byte_stream_reader_event::Detail::Eos(detail)), + }; + let _ = server + .send_event(proto::ffi_event::Message::ByteStreamReaderEvent(event)); + return; + } + } + } + + let detail = proto::ByteStreamReaderEos { error: None }; + let event = proto::ByteStreamReaderEvent { + reader_handle: self.handle_id, + detail: Some(proto::byte_stream_reader_event::Detail::Eos(detail)), + }; + let _ = server.send_event(proto::ffi_event::Message::ByteStreamReaderEvent(event)); + }); + server.watch_panic(handle); + Ok(proto::ByteStreamReaderReadIncrementalResponse {}) + } + + pub fn read_all( + self, + server: &'static FfiServer, + _request: proto::ByteStreamReaderReadAllRequest, + ) -> FfiResult { + let async_id = server.next_id(); + let handle = server.async_runtime.spawn(async move { + let result = self.inner.read_all().await.into(); + let callback = + proto::ByteStreamReaderReadAllCallback { async_id, result: Some(result) }; + let _ = server.send_event(proto::ffi_event::Message::ByteStreamReaderReadAll(callback)); + }); + server.watch_panic(handle); + Ok(proto::ByteStreamReaderReadAllResponse { async_id }) + } + + pub fn write_to_file( + self, + server: &'static FfiServer, + request: proto::ByteStreamReaderWriteToFileRequest, + ) -> FfiResult { + let async_id = server.next_id(); + + let handle = server.async_runtime.spawn(async move { + let result = self + .inner + .write_to_file(request.directory, request.name_override.as_deref()) + .await + .into(); + let callback = + proto::ByteStreamReaderWriteToFileCallback { async_id, result: Some(result) }; + let _ = + server.send_event(proto::ffi_event::Message::ByteStreamReaderWriteToFile(callback)); + }); + server.watch_panic(handle); + + Ok(proto::ByteStreamReaderWriteToFileResponse { async_id }) + } +} + +impl FfiTextStreamReader { + pub fn read_incremental( + self, + server: &'static FfiServer, + _request: proto::TextStreamReaderReadIncrementalRequest, + ) -> FfiResult { + let handle = server.async_runtime.spawn(async move { + let mut stream = self.inner; + while let Some(result) = stream.next().await { + match result { + Ok(text) => { + let detail = proto::TextStreamReaderChunkReceived { content: text }; + let event = proto::TextStreamReaderEvent { + reader_handle: self.handle_id, + detail: Some(proto::text_stream_reader_event::Detail::ChunkReceived( + detail, + )), + }; + let _ = server + .send_event(proto::ffi_event::Message::TextStreamReaderEvent(event)); + } + Err(err) => { + let detail = proto::TextStreamReaderEos { error: Some(err.into()) }; + let event = proto::TextStreamReaderEvent { + reader_handle: self.handle_id, + detail: Some(proto::text_stream_reader_event::Detail::Eos(detail)), + }; + let _ = server + .send_event(proto::ffi_event::Message::TextStreamReaderEvent(event)); + return; + } + } + } + + let detail = proto::TextStreamReaderEos { error: None }; + let event = proto::TextStreamReaderEvent { + reader_handle: self.handle_id, + detail: Some(proto::text_stream_reader_event::Detail::Eos(detail)), + }; + let _ = server.send_event(proto::ffi_event::Message::TextStreamReaderEvent(event)); + }); + server.watch_panic(handle); + Ok(proto::TextStreamReaderReadIncrementalResponse {}) + } + + pub fn read_all( + self, + server: &'static FfiServer, + _request: proto::TextStreamReaderReadAllRequest, + ) -> FfiResult { + let async_id = server.next_id(); + let handle = server.async_runtime.spawn(async move { + let result = self.inner.read_all().await.into(); + let callback = + proto::TextStreamReaderReadAllCallback { async_id, result: Some(result) }; + let _ = server.send_event(proto::ffi_event::Message::TextStreamReaderReadAll(callback)); + }); + server.watch_panic(handle); + Ok(proto::TextStreamReaderReadAllResponse { async_id }) + } +} + +impl FfiByteStreamWriter { + pub fn from_writer( + server: &'static FfiServer, + writer: ByteStreamWriter, + ) -> proto::OwnedByteStreamWriter { + let handle_id = server.next_id(); + let info = writer.info().clone(); + let writer = Self { handle_id, inner: writer }; + server.store_handle(handle_id, writer); + proto::OwnedByteStreamWriter { + handle: proto::FfiOwnedHandle { id: handle_id }, + info: info.into(), + } + } + + pub fn write( + &self, + server: &'static FfiServer, + request: proto::ByteStreamWriterWriteRequest, + ) -> FfiResult { + let async_id = server.next_id(); + let inner = self.inner.clone(); + let handle = server.async_runtime.spawn(async move { + let result = inner.write(&request.bytes).await; + let callback = proto::ByteStreamWriterWriteCallback { + async_id, + error: result.map_err(|e| e.into()).err(), + }; + let _ = server.send_event(proto::ffi_event::Message::ByteStreamWriterWrite(callback)); + }); + server.watch_panic(handle); + Ok(proto::ByteStreamWriterWriteResponse { async_id }) + } + + pub fn close( + self, + server: &'static FfiServer, + request: proto::ByteStreamWriterCloseRequest, + ) -> FfiResult { + let async_id = server.next_id(); + let handle = server.async_runtime.spawn(async move { + let result = match request.reason { + Some(reason) => self.inner.close_with_reason(&reason).await, + None => self.inner.close().await, + }; + let callback = proto::ByteStreamWriterCloseCallback { + async_id, + error: result.map_err(|e| e.into()).err(), + }; + let _ = server.send_event(proto::ffi_event::Message::ByteStreamWriterClose(callback)); + }); + server.watch_panic(handle); + Ok(proto::ByteStreamWriterCloseResponse { async_id }) + } +} + +impl FfiTextStreamWriter { + pub fn from_writer( + server: &'static FfiServer, + writer: TextStreamWriter, + ) -> proto::OwnedTextStreamWriter { + let handle_id = server.next_id(); + let info = writer.info().clone(); + let writer = Self { handle_id, inner: writer }; + server.store_handle(handle_id, writer); + proto::OwnedTextStreamWriter { + handle: proto::FfiOwnedHandle { id: handle_id }, + info: info.into(), + } + } + + pub fn write( + &self, + server: &'static FfiServer, + request: proto::TextStreamWriterWriteRequest, + ) -> FfiResult { + let async_id = server.next_id(); + let inner = self.inner.clone(); + let handle = server.async_runtime.spawn(async move { + let result = inner.write(&request.text).await; + let callback = proto::TextStreamWriterWriteCallback { + async_id, + error: result.map_err(|e| e.into()).err(), + }; + let _ = server.send_event(proto::ffi_event::Message::TextStreamWriterWrite(callback)); + }); + server.watch_panic(handle); + Ok(proto::TextStreamWriterWriteResponse { async_id }) + } + + pub fn close( + self, + server: &'static FfiServer, + request: proto::TextStreamWriterCloseRequest, + ) -> FfiResult { + let async_id = server.next_id(); + let handle = server.async_runtime.spawn(async move { + let result = match request.reason { + Some(reason) => self.inner.close_with_reason(&reason).await, + None => self.inner.close().await, + }; + let callback = proto::TextStreamWriterCloseCallback { + async_id, + error: result.map_err(|e| e.into()).err(), + }; + let _ = server.send_event(proto::ffi_event::Message::TextStreamWriterClose(callback)); + }); + server.watch_panic(handle); + Ok(proto::TextStreamWriterCloseResponse { async_id }) + } +} diff --git a/livekit-ffi/src/server/mod.rs b/livekit-ffi/src/server/mod.rs index 81cad9abb..0913012b2 100644 --- a/livekit-ffi/src/server/mod.rs +++ b/livekit-ffi/src/server/mod.rs @@ -36,6 +36,7 @@ pub mod audio_plugin; pub mod audio_source; pub mod audio_stream; pub mod colorcvt; +pub mod data_stream; pub mod logger; pub mod participant; pub mod requests; @@ -138,6 +139,11 @@ impl FfiServer { log::info!("initializing ffi server v{}", env!("CARGO_PKG_VERSION")); // TODO: Move this log } + /// Returns whether the server has been setup. + pub fn is_setup(&self) -> bool { + self.config.lock().is_some() + } + pub async fn dispose(&'static self) { self.logger.set_capture_logs(false); log::info!("disposing ffi server"); @@ -204,6 +210,27 @@ impl FfiServer { Ok(handle) } + pub fn take_handle(&self, id: FfiHandleId) -> FfiResult + where + T: FfiHandle, + { + if id == INVALID_HANDLE { + return Err(FfiError::InvalidRequest("handle is invalid".into())); + } + + let (_, handle) = self + .ffi_handles + .remove(&id) + .ok_or(FfiError::InvalidRequest("handle not found".into()))?; + + let handle = handle.downcast::().map_err(|_| { + let tyname = std::any::type_name::(); + let msg = format!("handle is not a {}", tyname); + FfiError::InvalidRequest(msg.into()) + })?; + Ok(*handle) + } + pub fn drop_handle(&self, id: FfiHandleId) -> bool { let existed = self.ffi_handles.remove(&id).is_some(); self.handle_dropped_txs.remove(&id); diff --git a/livekit-ffi/src/server/participant.rs b/livekit-ffi/src/server/participant.rs index e6d789876..5b04954b1 100644 --- a/livekit-ffi/src/server/participant.rs +++ b/livekit-ffi/src/server/participant.rs @@ -20,8 +20,11 @@ use tokio::sync::oneshot; use crate::{ proto, - server::room::RoomInner, - server::{FfiHandle, FfiServer}, + server::{ + data_stream::{FfiByteStreamWriter, FfiTextStreamWriter}, + room::RoomInner, + FfiHandle, FfiServer, + }, FfiError, FfiHandleId, FfiResult, }; @@ -35,6 +38,16 @@ pub struct FfiParticipant { impl FfiHandle for FfiParticipant {} impl FfiParticipant { + fn guard_local_participant(&self) -> FfiResult { + let local = match &self.participant { + Participant::Local(local) => local.clone(), + Participant::Remote(_) => { + Err(FfiError::InvalidRequest("Expected local participant".into()))? + } + }; + Ok(local) + } + pub fn perform_rpc( &self, server: &'static FfiServer, @@ -42,12 +55,7 @@ impl FfiParticipant { ) -> FfiResult { let async_id = server.next_id(); - let local = match &self.participant { - Participant::Local(local) => local.clone(), - Participant::Remote(_) => { - return Err(FfiError::InvalidRequest("Expected local participant".into())) - } - }; + let local = self.guard_local_participant()?; let handle = server.async_runtime.spawn(async move { let result = local @@ -128,6 +136,92 @@ impl FfiParticipant { Ok(proto::UnregisterRpcMethodResponse {}) } + + pub fn send_file( + &self, + server: &'static FfiServer, + request: proto::StreamSendFileRequest, + ) -> FfiResult { + let async_id = server.next_id(); + let local = self.guard_local_participant()?; + + let handle = server.async_runtime.spawn(async move { + let result = match local.send_file(&request.file_path, request.options.into()).await { + Ok(info) => proto::stream_send_file_callback::Result::Info(info.into()), + Err(err) => proto::stream_send_file_callback::Result::Error(err.into()), + }; + let callback = proto::StreamSendFileCallback { async_id, result: Some(result) }; + let _ = server.send_event(proto::ffi_event::Message::SendFile(callback)); + }); + server.watch_panic(handle); + Ok(proto::StreamSendFileResponse { async_id }) + } + + pub fn send_text( + &self, + server: &'static FfiServer, + request: proto::StreamSendTextRequest, + ) -> FfiResult { + let async_id = server.next_id(); + let local = self.guard_local_participant()?; + + let handle = server.async_runtime.spawn(async move { + let result = match local.send_text(&request.text, request.options.into()).await { + Ok(info) => proto::stream_send_text_callback::Result::Info(info.into()), + Err(err) => proto::stream_send_text_callback::Result::Error(err.into()), + }; + let callback = proto::StreamSendTextCallback { async_id, result: Some(result) }; + let _ = server.send_event(proto::ffi_event::Message::SendText(callback)); + }); + server.watch_panic(handle); + Ok(proto::StreamSendTextResponse { async_id }) + } + + pub fn stream_bytes( + &self, + server: &'static FfiServer, + request: proto::ByteStreamOpenRequest, + ) -> FfiResult { + let async_id = server.next_id(); + let local = self.guard_local_participant()?; + + let handle = server.async_runtime.spawn(async move { + let result = match local.stream_bytes(request.options.into()).await { + Ok(writer) => { + let ffi_writer = FfiByteStreamWriter::from_writer(server, writer); + proto::byte_stream_open_callback::Result::Writer(ffi_writer) + } + Err(err) => proto::byte_stream_open_callback::Result::Error(err.into()), + }; + let callback = proto::ByteStreamOpenCallback { async_id, result: Some(result) }; + let _ = server.send_event(proto::ffi_event::Message::ByteStreamOpen(callback)); + }); + server.watch_panic(handle); + Ok(proto::ByteStreamOpenResponse { async_id }) + } + + pub fn stream_text( + &self, + server: &'static FfiServer, + request: proto::TextStreamOpenRequest, + ) -> FfiResult { + let async_id = server.next_id(); + let local = self.guard_local_participant()?; + + let handle = server.async_runtime.spawn(async move { + let result = match local.stream_text(request.options.into()).await { + Ok(writer) => { + let ffi_writer = FfiTextStreamWriter::from_writer(server, writer); + proto::text_stream_open_callback::Result::Writer(ffi_writer) + } + Err(err) => proto::text_stream_open_callback::Result::Error(err.into()), + }; + let callback = proto::TextStreamOpenCallback { async_id, result: Some(result) }; + let _ = server.send_event(proto::ffi_event::Message::TextStreamOpen(callback)); + }); + server.watch_panic(handle); + Ok(proto::TextStreamOpenResponse { async_id }) + } } async fn forward_rpc_method_invocation( diff --git a/livekit-ffi/src/server/requests.rs b/livekit-ffi/src/server/requests.rs index 34d0341a4..c45dba855 100644 --- a/livekit-ffi/src/server/requests.rs +++ b/livekit-ffi/src/server/requests.rs @@ -24,7 +24,7 @@ use livekit::{ use parking_lot::Mutex; use super::{ - audio_source, audio_stream, colorcvt, + audio_source, audio_stream, colorcvt, data_stream, participant::FfiParticipant, resampler, room::{self, FfiPublication, FfiTrack}, @@ -1051,6 +1051,116 @@ fn on_set_track_subscription_permissions( Ok(ffi_participant.room.set_track_subscription_permissions(server, set_permissions)) } +fn on_byte_stream_reader_read_incremental( + server: &'static FfiServer, + request: proto::ByteStreamReaderReadIncrementalRequest, +) -> FfiResult { + let reader = server.take_handle::(request.reader_handle)?; + reader.read_incremental(server, request) +} + +fn on_byte_stream_reader_read_all( + server: &'static FfiServer, + request: proto::ByteStreamReaderReadAllRequest, +) -> FfiResult { + let reader = server.take_handle::(request.reader_handle)?; + reader.read_all(server, request) +} + +fn on_byte_stream_reader_write_to_file( + server: &'static FfiServer, + request: proto::ByteStreamReaderWriteToFileRequest, +) -> FfiResult { + let reader = server.take_handle::(request.reader_handle)?; + reader.write_to_file(server, request) +} + +fn on_text_stream_reader_read_incremental( + server: &'static FfiServer, + request: proto::TextStreamReaderReadIncrementalRequest, +) -> FfiResult { + let reader = server.take_handle::(request.reader_handle)?; + reader.read_incremental(server, request) +} + +fn on_text_stream_reader_read_all( + server: &'static FfiServer, + request: proto::TextStreamReaderReadAllRequest, +) -> FfiResult { + let reader = server.take_handle::(request.reader_handle)?; + reader.read_all(server, request) +} + +fn on_send_file( + server: &'static FfiServer, + request: proto::StreamSendFileRequest, +) -> FfiResult { + let ffi_participant = + server.retrieve_handle::(request.local_participant_handle)?.clone(); + ffi_participant.send_file(server, request) +} + +fn on_send_text( + server: &'static FfiServer, + request: proto::StreamSendTextRequest, +) -> FfiResult { + let ffi_participant = + server.retrieve_handle::(request.local_participant_handle)?.clone(); + ffi_participant.send_text(server, request) +} + +fn on_byte_stream_open( + server: &'static FfiServer, + request: proto::ByteStreamOpenRequest, +) -> FfiResult { + let ffi_participant = + server.retrieve_handle::(request.local_participant_handle)?.clone(); + ffi_participant.stream_bytes(server, request) +} + +fn on_byte_stream_write( + server: &'static FfiServer, + request: proto::ByteStreamWriterWriteRequest, +) -> FfiResult { + let writer = + server.retrieve_handle::(request.writer_handle)?; + writer.write(server, request) +} + +fn on_byte_stream_close( + server: &'static FfiServer, + request: proto::ByteStreamWriterCloseRequest, +) -> FfiResult { + let writer = server.take_handle::(request.writer_handle)?; + writer.close(server, request) +} + +fn on_text_stream_open( + server: &'static FfiServer, + request: proto::TextStreamOpenRequest, +) -> FfiResult { + let ffi_participant = + server.retrieve_handle::(request.local_participant_handle)?.clone(); + ffi_participant.stream_text(server, request) +} + +fn on_text_stream_write( + server: &'static FfiServer, + request: proto::TextStreamWriterWriteRequest, +) -> FfiResult { + let writer = + server.retrieve_handle::(request.writer_handle)?; + writer.write(server, request) +} + +fn on_text_stream_close( + server: &'static FfiServer, + request: proto::TextStreamWriterCloseRequest, +) -> FfiResult { + let writer = server.take_handle::(request.writer_handle)?; + writer.close(server, request) +} + #[allow(clippy::field_reassign_with_default)] // Avoid uggly format pub fn handle_request( server: &'static FfiServer, @@ -1188,11 +1298,9 @@ pub fn handle_request( proto::ffi_request::Message::NewApm(new_apm) => { proto::ffi_response::Message::NewApm(on_new_apm(server, new_apm)?) } - proto::ffi_request::Message::ApmProcessStream(request) => { proto::ffi_response::Message::ApmProcessStream(on_apm_process_stream(server, request)?) } - proto::ffi_request::Message::ApmProcessReverseStream(request) => { proto::ffi_response::Message::ApmProcessReverseStream(on_apm_process_reverse_stream( server, request, @@ -1247,6 +1355,55 @@ pub fn handle_request( on_set_data_channel_buffered_amount_low_threshold(server, request)?, ) } + proto::ffi_request::Message::ByteReadIncremental(request) => { + proto::ffi_response::Message::ByteReadIncremental( + on_byte_stream_reader_read_incremental(server, request)?, + ) + } + proto::ffi_request::Message::ByteReadAll(request) => { + proto::ffi_response::Message::ByteReadAll(on_byte_stream_reader_read_all( + server, request, + )?) + } + proto::ffi_request::Message::ByteWriteToFile(request) => { + proto::ffi_response::Message::ByteWriteToFile(on_byte_stream_reader_write_to_file( + server, request, + )?) + } + proto::ffi_request::Message::TextReadIncremental(request) => { + proto::ffi_response::Message::TextReadIncremental( + on_text_stream_reader_read_incremental(server, request)?, + ) + } + proto::ffi_request::Message::TextReadAll(request) => { + proto::ffi_response::Message::TextReadAll(on_text_stream_reader_read_all( + server, request, + )?) + } + proto::ffi_request::Message::SendFile(request) => { + proto::ffi_response::Message::SendFile(on_send_file(server, request)?) + } + proto::ffi_request::Message::SendText(request) => { + proto::ffi_response::Message::SendText(on_send_text(server, request)?) + } + proto::ffi_request::Message::ByteStreamOpen(request) => { + proto::ffi_response::Message::ByteStreamOpen(on_byte_stream_open(server, request)?) + } + proto::ffi_request::Message::ByteStreamWrite(request) => { + proto::ffi_response::Message::ByteStreamWrite(on_byte_stream_write(server, request)?) + } + proto::ffi_request::Message::ByteStreamClose(request) => { + proto::ffi_response::Message::ByteStreamClose(on_byte_stream_close(server, request)?) + } + proto::ffi_request::Message::TextStreamOpen(request) => { + proto::ffi_response::Message::TextStreamOpen(on_text_stream_open(server, request)?) + } + proto::ffi_request::Message::TextStreamWrite(request) => { + proto::ffi_response::Message::TextStreamWrite(on_text_stream_write(server, request)?) + } + proto::ffi_request::Message::TextStreamClose(request) => { + proto::ffi_response::Message::TextStreamClose(on_text_stream_close(server, request)?) + } proto::ffi_request::Message::LoadAudioFilterPlugin(request) => { proto::ffi_response::Message::LoadAudioFilterPlugin(on_load_audio_filter_plugin( server, request, diff --git a/livekit-ffi/src/server/room.rs b/livekit-ffi/src/server/room.rs index 8e1b058fb..7a2437280 100644 --- a/livekit-ffi/src/server/room.rs +++ b/livekit-ffi/src/server/room.rs @@ -16,8 +16,8 @@ use std::collections::HashMap; use std::time::Duration; use std::{collections::HashSet, slice, sync::Arc}; -use livekit::ChatMessage; use livekit::{prelude::*, registered_audio_filter_plugins}; +use livekit::{ChatMessage, StreamReader}; use livekit_protocol as lk_proto; use parking_lot::Mutex; use tokio::sync::{broadcast, mpsc, oneshot, Mutex as AsyncMutex}; @@ -26,6 +26,7 @@ use tokio::task::JoinHandle; use super::FfiDataBuffer; use crate::{ proto, + server::data_stream::{FfiByteStreamReader, FfiTextStreamReader}, server::participant::FfiParticipant, server::{FfiHandle, FfiServer}, FfiError, FfiHandleId, FfiResult, @@ -704,6 +705,8 @@ impl RoomInner { proto::SendChatMessageResponse { async_id } } + // Data Streams (low level) + pub fn send_stream_header( self: &Arc, server: &'static FfiServer, @@ -1301,6 +1304,38 @@ async fn forward_event( state: proto::EncryptionState::from(state).into(), })); } + RoomEvent::ByteStreamOpened { reader, topic: _, participant_identity } => { + let Some(reader) = reader.take() else { return }; + let handle_id = server.next_id(); + let info = reader.info().clone(); + let ffi_reader = FfiByteStreamReader { handle_id, inner: reader }; + server.store_handle(ffi_reader.handle_id, ffi_reader); + + let _ = + send_event(proto::room_event::Message::ByteStreamOpened(proto::ByteStreamOpened { + reader: proto::OwnedByteStreamReader { + handle: proto::FfiOwnedHandle { id: handle_id }, + info: info.into(), + }, + participant_identity: participant_identity.0, + })); + } + RoomEvent::TextStreamOpened { reader, topic: _, participant_identity } => { + let Some(reader) = reader.take() else { return }; + let handle_id = server.next_id(); + let info = reader.info().clone(); + let ffi_reader = FfiTextStreamReader { handle_id, inner: reader }; + server.store_handle(ffi_reader.handle_id, ffi_reader); + + let _ = + send_event(proto::room_event::Message::TextStreamOpened(proto::TextStreamOpened { + reader: proto::OwnedTextStreamReader { + handle: proto::FfiOwnedHandle { id: handle_id }, + info: info.into(), + }, + participant_identity: participant_identity.0, + })); + } RoomEvent::StreamHeaderReceived { header, participant_identity } => { let _ = send_event(proto::room_event::Message::StreamHeaderReceived( proto::DataStreamHeaderReceived { header: header.into(), participant_identity }, diff --git a/livekit/Cargo.toml b/livekit/Cargo.toml index fc2284de5..4beee83b9 100644 --- a/livekit/Cargo.toml +++ b/livekit/Cargo.toml @@ -34,7 +34,7 @@ livekit-protocol = { workspace = true } prost = "0.12" serde = { version = "1", features = ["derive"] } serde_json = "1.0" -tokio = { version = "1", default-features = false, features = ["sync", "macros"] } +tokio = { version = "1", default-features = false, features = ["sync", "macros", "fs"] } parking_lot = { version = "0.12" } futures-util = { version = "0.3", default-features = false, features = ["sink"] } thiserror = "1.0" @@ -43,3 +43,5 @@ log = "0.4" chrono = "0.4.38" semver = "1.0" libloading = { version = "0.8.6" } +bytes = "1.10.1" +bmrng = "0.5.2" diff --git a/livekit/src/room/data_stream/incoming.rs b/livekit/src/room/data_stream/incoming.rs new file mode 100644 index 000000000..93bfad39b --- /dev/null +++ b/livekit/src/room/data_stream/incoming.rs @@ -0,0 +1,344 @@ +// Copyright 2025 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::{ + AnyStreamInfo, ByteStreamInfo, StreamError, StreamProgress, StreamResult, TextStreamInfo, +}; +use crate::TakeCell; +use bytes::{Bytes, BytesMut}; +use futures_util::{Stream, StreamExt}; +use livekit_protocol::data_stream as proto; +use parking_lot::Mutex; +use std::{ + collections::HashMap, + fmt::Debug, + pin::Pin, + sync::Arc, + task::{Context, Poll}, +}; +use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; + +/// Reader for an incoming data stream. +/// +/// The stream being read from is kept open as long as its reader exists; +/// dropping the reader will close the stream. +/// +pub trait StreamReader: Stream> { + /// Type of output this reader produces. + type Output; + + /// Information about the underlying data stream. + type Info; + + /// Returns a reference to the stream info. + fn info(&self) -> &Self::Info; + + /// Reads all incoming chunks from the byte stream, concatenating them + /// into a single value which is returned once the stream closes normally. + /// + /// Returns the data consisting of all concatenated chunks. + /// + fn read_all(self) -> impl std::future::Future> + Send; +} + +impl TakeCell +where + T: StreamReader, +{ + /// Takes the reader out of the cell if its info matches the given predicate. + /// + /// Use this method to conditionally handle incoming streams based on info fields + /// such as topic or attributes. + /// + /// This method will only take the reader if the provided predicate returns `true` when called with the reader's info. + /// If the predicate returns `false` or the reader has already been taken, this method returns `None`. + /// + pub fn take_if(&self, predicate: impl FnOnce(&T::Info) -> bool) -> Option { + self.take_if_raw(|reader| predicate(reader.info())) + } +} + +/// Reader for an incoming byte data stream. +pub struct ByteStreamReader { + info: ByteStreamInfo, + chunk_rx: UnboundedReceiver>, +} + +/// Reader for an incoming text data stream. +pub struct TextStreamReader { + info: TextStreamInfo, + chunk_rx: UnboundedReceiver>, +} + +impl StreamReader for ByteStreamReader { + type Output = Bytes; + type Info = ByteStreamInfo; + + fn info(&self) -> &ByteStreamInfo { + &self.info + } + + async fn read_all(mut self) -> StreamResult { + let mut buffer = BytesMut::new(); + while let Some(result) = self.next().await { + match result { + Ok(bytes) => buffer.extend_from_slice(&bytes), + Err(e) => return Err(e), + } + } + Ok(buffer.freeze()) + } +} + +impl ByteStreamReader { + /// Reads incoming chunks from the byte stream, writing them to a file as they are received. + /// + /// Parameters: + /// - directory: The directory to write the file in. The system temporary directory is used if not specified. + /// - name_override: The name to use for the written file, overriding stream name. + /// + /// Returns: The path of the written file on disk. + /// + pub async fn write_to_file( + mut self, + directory: Option>, + name_override: Option<&str>, + ) -> StreamResult { + let directory = + directory.map(|d| d.as_ref().to_path_buf()).unwrap_or_else(|| std::env::temp_dir()); + let name = name_override.unwrap_or_else(|| &self.info.name); + let file_path = directory.join(name); + + let mut file = tokio::fs::File::create(&file_path).await.map_err(StreamError::Io)?; + + while let Some(result) = self.next().await { + let bytes = result?; + tokio::io::AsyncWriteExt::write_all(&mut file, &bytes) + .await + .map_err(StreamError::Io)?; + } + tokio::io::AsyncWriteExt::flush(&mut file).await.map_err(StreamError::Io)?; + + Ok(file_path) + } +} + +impl Stream for ByteStreamReader { + type Item = StreamResult; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = self.get_mut(); + match Pin::new(&mut this.chunk_rx).poll_recv(cx) { + Poll::Ready(Some(Ok(chunk))) => Poll::Ready(Some(Ok(chunk))), + Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(e))), + Poll::Ready(None) => Poll::Ready(None), + Poll::Pending => Poll::Pending, + } + } +} + +impl StreamReader for TextStreamReader { + type Output = String; + type Info = TextStreamInfo; + + fn info(&self) -> &TextStreamInfo { + &self.info + } + + async fn read_all(mut self) -> StreamResult { + let mut result = String::new(); + while let Some(chunk) = self.next().await { + match chunk { + Ok(text) => result.push_str(&text), + Err(e) => return Err(e), + } + } + Ok(result) + } +} + +impl Stream for TextStreamReader { + type Item = StreamResult; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = self.get_mut(); + match Pin::new(&mut this.chunk_rx).poll_recv(cx) { + Poll::Ready(Some(Ok(chunk))) => match String::from_utf8(chunk.into()) { + Ok(content) => Poll::Ready(Some(Ok(content))), + Err(e) => { + this.chunk_rx.close(); + Poll::Ready(Some(Err(StreamError::from(e)))) + } + }, + Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(e))), + Poll::Ready(None) => Poll::Ready(None), + Poll::Pending => Poll::Pending, + } + } +} + +impl Debug for ByteStreamReader { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ByteStreamReader") + .field("id", &self.info.id()) + .field("topic", &self.info.topic) + .finish() + } +} + +impl Debug for TextStreamReader { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("TextStreamReader") + .field("id", &self.info.id()) + .field("topic", &self.info.topic) + .finish() + } +} + +pub(crate) enum AnyStreamReader { + Byte(ByteStreamReader), + Text(TextStreamReader), +} + +impl AnyStreamReader { + /// Creates a stream reader for the stream with the given info. + pub(super) fn from(info: AnyStreamInfo) -> (Self, UnboundedSender>) { + let (chunk_tx, chunk_rx) = mpsc::unbounded_channel(); + let reader = match info { + AnyStreamInfo::Byte(info) => Self::Byte(ByteStreamReader { info, chunk_rx }), + AnyStreamInfo::Text(info) => Self::Text(TextStreamReader { info, chunk_rx }), + }; + return (reader, chunk_tx); + } +} +struct Descriptor { + progress: StreamProgress, + chunk_tx: UnboundedSender>, + // TODO(ladvoc): keep track of open time. +} + +#[derive(Clone)] +pub(crate) struct IncomingStreamManager { + inner: Arc>, + open_tx: UnboundedSender<(AnyStreamReader, String)>, +} + +#[derive(Default)] +struct ManagerInner { + open_streams: HashMap, +} + +impl IncomingStreamManager { + pub fn new() -> (Self, UnboundedReceiver<(AnyStreamReader, String)>) { + let (open_tx, open_rx) = mpsc::unbounded_channel(); + (Self { inner: Arc::new(Mutex::new(Default::default())), open_tx }, open_rx) + } + + /// Handles an incoming header packet. + pub fn handle_header(&self, header: proto::Header, identity: String) { + let Ok(info) = + AnyStreamInfo::try_from(header).inspect_err(|e| log::error!("Invalid header: {}", e)) + else { + return; + }; + + let id = info.id().to_owned(); + let bytes_total = info.total_length(); + + let mut inner = self.inner.lock(); + if inner.open_streams.contains_key(&id) { + log::error!("Stream '{}' already open", id); + return; + } + + let (reader, chunk_tx) = AnyStreamReader::from(info); + let _ = self.open_tx.send((reader, identity)); + + let descriptor = + Descriptor { progress: StreamProgress { bytes_total, ..Default::default() }, chunk_tx }; + inner.open_streams.insert(id, descriptor); + } + + /// Handles an incoming chunk packet. + pub fn handle_chunk(&self, chunk: proto::Chunk) { + let id = chunk.stream_id; + let mut inner = self.inner.lock(); + let Some(descriptor) = inner.open_streams.get_mut(&id) else { + return; + }; + + if descriptor.progress.chunk_index != chunk.chunk_index { + inner.close_stream_with_error(&id, StreamError::MissedChunk); + return; + } + + descriptor.progress.chunk_index += 1; + descriptor.progress.bytes_processed += chunk.content.len() as u64; + + if match descriptor.progress.bytes_total { + Some(total) => descriptor.progress.bytes_processed > total as u64, + None => false, + } { + inner.close_stream_with_error(&id, StreamError::LengthExceeded); + return; + } + inner.yield_chunk(&id, Bytes::from(chunk.content)); + // TODO: also yield progress + } + + /// Handles an incoming trailer packet. + pub fn handle_trailer(&self, trailer: proto::Trailer) { + let id = trailer.stream_id; + let mut inner = self.inner.lock(); + let Some(descriptor) = inner.open_streams.get_mut(&id) else { + return; + }; + + if !match descriptor.progress.bytes_total { + Some(total) => descriptor.progress.bytes_processed >= total as u64, + None => true, + } { + inner.close_stream_with_error(&id, StreamError::Incomplete); + return; + } + if !trailer.reason.is_empty() { + inner.close_stream_with_error(&id, StreamError::AbnormalEnd(trailer.reason)); + return; + } + inner.close_stream(&id); + } +} + +impl ManagerInner { + fn yield_chunk(&mut self, id: &str, chunk: Bytes) { + let Some(descriptor) = self.open_streams.get_mut(id) else { + return; + }; + if descriptor.chunk_tx.send(Ok(chunk)).is_err() { + // Reader has been dropped, close the stream. + self.close_stream(id); + } + } + + fn close_stream(&mut self, id: &str) { + // Dropping the sender closes the channel. + self.open_streams.remove(id); + } + + fn close_stream_with_error(&mut self, id: &str, error: StreamError) { + if let Some(descriptor) = self.open_streams.remove(id) { + let _ = descriptor.chunk_tx.send(Err(error)); + } + } +} diff --git a/livekit/src/room/data_stream/mod.rs b/livekit/src/room/data_stream/mod.rs new file mode 100644 index 000000000..d71a88719 --- /dev/null +++ b/livekit/src/room/data_stream/mod.rs @@ -0,0 +1,241 @@ +// Copyright 2025 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use chrono::{DateTime, Utc}; +use livekit_protocol::{data_stream as proto, enum_dispatch}; +use std::collections::HashMap; +use thiserror::Error; + +mod incoming; +mod outgoing; + +pub use incoming::*; +pub use outgoing::*; + +/// Result type for data stream operations. +pub type StreamResult = Result; + +/// Error type for data stream operations. +#[derive(Debug, Error)] +pub enum StreamError { + // TODO(ladvoc): standardize error cases and expose over FFI. + #[error("stream has already been closed")] + AlreadyClosed, + + #[error("stream closed abnormally: {0}")] + AbnormalEnd(String), + + #[error("UTF-8 decoding error: {0}")] + Utf8(#[from] std::string::FromUtf8Error), + + #[error("incoming header was invalid")] + InvalidHeader, + + #[error("expected chunk index to be exactly one more than the previous")] + MissedChunk, + + #[error("read length exceeded total length specified in stream header")] + LengthExceeded, + + #[error("stream data is incomplete")] + Incomplete, + + #[error("unable to send packet")] + SendFailed, + + #[error("I/O error: {0}")] + Io(#[from] std::io::Error), + + #[error("internal error")] + Internal, +} + +/// Progress of a data stream. +#[derive(Clone, Copy, Default, Debug, Hash, Eq, PartialEq)] +struct StreamProgress { + chunk_index: u64, + /// Number of bytes read or written so far. + bytes_processed: u64, + /// Total number of bytes expected to be read or written for finite streams. + bytes_total: Option, +} + +impl StreamProgress { + /// Returns the completion percentage for finite streams. + #[allow(dead_code)] + fn percentage(&self) -> Option { + self.bytes_total.map(|total| self.bytes_processed as f32 / total as f32) + } +} + +/// Information about a byte data stream. +#[derive(Clone, Debug)] +pub struct ByteStreamInfo { + /// Unique identifier of the stream. + pub id: String, + /// Topic name used to route the stream to the appropriate handler. + pub topic: String, + /// When the stream was created. + pub timestamp: DateTime, + /// Total expected size in bytes, if known. + pub total_length: Option, + /// Additional attributes as needed for your application. + pub attributes: HashMap, + /// The MIME type of the stream data. + pub mime_type: String, + /// The name of the file being sent. + pub name: String, +} + +/// Information about a text data stream. +#[derive(Clone, Debug)] +pub struct TextStreamInfo { + /// Unique identifier of the stream. + pub id: String, + /// Topic name used to route the stream to the appropriate handler. + pub topic: String, + /// When the stream was created. + pub timestamp: DateTime, + /// Total expected size in bytes, if known. + pub total_length: Option, + /// Additional attributes as needed for your application. + pub attributes: HashMap, + /// The MIME type of the stream data. + pub mime_type: String, + pub operation_type: OperationType, + pub version: i32, + pub reply_to_stream_id: Option, + pub attached_stream_ids: Vec, + pub generated: bool, +} + +/// Operation type for text streams. +#[derive(Clone, Copy, Default, Debug, Hash, Eq, PartialEq)] +pub enum OperationType { + #[default] + Create, + Update, + Delete, + Reaction, +} + +// MARK: - Protocol type conversion + +impl TryFrom for AnyStreamInfo { + type Error = StreamError; + + fn try_from(mut header: proto::Header) -> Result { + let Some(content_header) = header.content_header.take() else { + Err(StreamError::InvalidHeader)? + }; + let info = match content_header { + proto::header::ContentHeader::ByteHeader(byte_header) => { + Self::Byte(ByteStreamInfo::from_headers(header, byte_header)) + } + proto::header::ContentHeader::TextHeader(text_header) => { + Self::Text(TextStreamInfo::from_headers(header, text_header)) + } + }; + Ok(info) + } +} + +impl ByteStreamInfo { + pub(crate) fn from_headers(header: proto::Header, byte_header: proto::ByteHeader) -> Self { + Self { + id: header.stream_id, + topic: header.topic, + timestamp: DateTime::::from_timestamp_millis(header.timestamp) + .unwrap_or_else(|| Utc::now()), + total_length: header.total_length, + attributes: header.attributes, + mime_type: header.mime_type, + name: byte_header.name, + } + } +} + +impl TextStreamInfo { + pub(crate) fn from_headers(header: proto::Header, text_header: proto::TextHeader) -> Self { + Self { + id: header.stream_id, + topic: header.topic, + timestamp: DateTime::::from_timestamp_millis(header.timestamp) + .unwrap_or_else(|| Utc::now()), + total_length: header.total_length, + attributes: header.attributes, + mime_type: header.mime_type, + operation_type: text_header.operation_type().into(), + version: text_header.version, + reply_to_stream_id: (!text_header.reply_to_stream_id.is_empty()) + .then_some(text_header.reply_to_stream_id), + attached_stream_ids: text_header.attached_stream_ids, + generated: text_header.generated, + } + } +} + +impl From for OperationType { + fn from(op_type: proto::OperationType) -> Self { + match op_type { + proto::OperationType::Create => OperationType::Create, + proto::OperationType::Update => OperationType::Update, + proto::OperationType::Delete => OperationType::Delete, + proto::OperationType::Reaction => OperationType::Reaction, + } + } +} +// MARK: - Dispatch + +#[derive(Clone, Debug)] +pub(crate) enum AnyStreamInfo { + Byte(ByteStreamInfo), + Text(TextStreamInfo), +} + +impl AnyStreamInfo { + enum_dispatch!( + [Byte, Text]; + pub fn id(self: &Self) -> &str; + pub fn total_length(self: &Self) -> Option; + ); +} + +#[rustfmt::skip] +macro_rules! stream_info { + () => { + fn id(&self) -> &str { &self.id } + fn total_length(&self) -> Option { self.total_length } + }; +} + +impl ByteStreamInfo { + stream_info!(); +} + +impl TextStreamInfo { + stream_info!(); +} + +impl From for AnyStreamInfo { + fn from(info: ByteStreamInfo) -> Self { + Self::Byte(info) + } +} + +impl From for AnyStreamInfo { + fn from(info: TextStreamInfo) -> Self { + Self::Text(info) + } +} diff --git a/livekit/src/room/data_stream/outgoing.rs b/livekit/src/room/data_stream/outgoing.rs new file mode 100644 index 000000000..7984f4e32 --- /dev/null +++ b/livekit/src/room/data_stream/outgoing.rs @@ -0,0 +1,438 @@ +// Copyright 2025 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::{ + ByteStreamInfo, OperationType, StreamError, StreamProgress, StreamResult, TextStreamInfo, +}; +use crate::{ + id::ParticipantIdentity, rtc_engine::EngineError, utils::utf8_chunk::Utf8AwareChunkExt, +}; +use bmrng::unbounded::{UnboundedRequestReceiver, UnboundedRequestSender}; +use chrono::Utc; +use libwebrtc::native::create_random_uuid; +use livekit_protocol as proto; +use std::{collections::HashMap, path::Path, sync::Arc}; +use tokio::{io::AsyncReadExt, sync::Mutex}; + +/// Writer for an open data stream. +pub trait StreamWriter<'a> { + /// Type of input this writer accepts. + type Input: 'a; + + /// Information about the underlying data stream. + type Info; + + /// Returns a reference to the stream info. + fn info(&self) -> &Self::Info; + + /// Writes to the stream. + fn write( + &self, + input: Self::Input, + ) -> impl std::future::Future> + Send; + + /// Closes the stream normally. + fn close(self) -> impl std::future::Future> + Send; + + /// Closes the stream abnormally, specifying the reason for closure. + fn close_with_reason( + self, + reason: &str, + ) -> impl std::future::Future> + Send; +} + +#[derive(Clone)] +/// Writer for an open byte data stream. +pub struct ByteStreamWriter { + info: Arc, + stream: Arc>, +} + +#[derive(Clone)] +/// Writer for an open text data stream. +pub struct TextStreamWriter { + info: Arc, + stream: Arc>, +} + +impl<'a> StreamWriter<'a> for ByteStreamWriter { + type Input = &'a [u8]; + type Info = ByteStreamInfo; + + fn info(&self) -> &Self::Info { + &self.info + } + + async fn write(&self, bytes: &[u8]) -> StreamResult<()> { + let mut stream = self.stream.lock().await; + for chunk in bytes.chunks(CHUNK_SIZE) { + stream.write_chunk(chunk).await?; + } + Ok(()) + } + + async fn close(self) -> StreamResult<()> { + self.stream.lock().await.close(None).await + } + + async fn close_with_reason(self, reason: &str) -> StreamResult<()> { + self.stream.lock().await.close(Some(reason)).await + } +} + +impl ByteStreamWriter { + /// Writes the contents of the file incrementally. + async fn write_file_contents(&self, path: impl AsRef) -> StreamResult<()> { + let mut stream = self.stream.lock().await; + let mut file = tokio::fs::File::open(path).await?; + let mut buffer = vec![0; 8192]; // 8KB + loop { + let bytes_read = file.read(&mut buffer).await?; + if bytes_read == 0 { + break; + } + stream.write_chunk(&buffer[..bytes_read]).await?; + } + Ok(()) + } +} + +impl<'a> StreamWriter<'a> for TextStreamWriter { + type Input = &'a str; + type Info = TextStreamInfo; + + fn info(&self) -> &Self::Info { + &self.info + } + + async fn write(&self, text: &str) -> StreamResult<()> { + let mut stream = self.stream.lock().await; + for chunk in text.as_bytes().utf8_aware_chunks(CHUNK_SIZE) { + stream.write_chunk(chunk).await?; + } + Ok(()) + } + + async fn close(self) -> StreamResult<()> { + self.stream.lock().await.close(None).await + } + + async fn close_with_reason(self, reason: &str) -> StreamResult<()> { + self.stream.lock().await.close(Some(reason)).await + } +} + +struct RawStreamOpenOptions { + header: proto::data_stream::Header, + destination_identities: Vec, + packet_tx: UnboundedRequestSender>, +} + +struct RawStream { + id: String, + progress: StreamProgress, + is_closed: bool, + /// Request channel for sending packets. + packet_tx: UnboundedRequestSender>, +} + +impl RawStream { + async fn open(options: RawStreamOpenOptions) -> StreamResult { + let id = options.header.stream_id.to_string(); + let bytes_total = options.header.total_length; + + let packet = Self::create_header_packet(options.header, options.destination_identities); + Self::send_packet(&options.packet_tx, packet).await?; + + Ok(Self { + id, + progress: StreamProgress { bytes_total, ..Default::default() }, + is_closed: false, + packet_tx: options.packet_tx, + }) + } + + async fn write_chunk(&mut self, bytes: &[u8]) -> StreamResult<()> { + let packet = Self::create_chunk_packet(&self.id, self.progress.chunk_index, bytes); + Self::send_packet(&self.packet_tx, packet).await?; + self.progress.bytes_processed += bytes.len() as u64; + self.progress.chunk_index += 1; + Ok(()) + } + + async fn close(&mut self, reason: Option<&str>) -> StreamResult<()> { + if self.is_closed { + Err(StreamError::AlreadyClosed)? + } + let packet = Self::create_trailer_packet(&self.id, reason); + Self::send_packet(&self.packet_tx, packet).await?; + self.is_closed = true; + Ok(()) + } + + async fn send_packet( + tx: &UnboundedRequestSender>, + packet: proto::DataPacket, + ) -> StreamResult<()> { + tx.send_receive(packet) + .await + .map_err(|_| StreamError::Internal)? // request channel closed + .map_err(|_| StreamError::SendFailed) // data channel error + } + + fn create_header_packet( + header: proto::data_stream::Header, + destination_identities: Vec, + ) -> proto::DataPacket { + proto::DataPacket { + kind: proto::data_packet::Kind::Reliable.into(), + participant_identity: String::new(), // populate later + destination_identities: destination_identities.into_iter().map(|id| id.0).collect(), + value: Some(livekit_protocol::data_packet::Value::StreamHeader(header)), + } + } + + fn create_chunk_packet(id: &str, chunk_index: u64, content: &[u8]) -> proto::DataPacket { + let chunk = proto::data_stream::Chunk { + stream_id: id.to_string(), + chunk_index, + content: content.to_vec(), + ..Default::default() + }; + proto::DataPacket { + kind: proto::data_packet::Kind::Reliable.into(), + participant_identity: String::new(), // populate later + value: Some(livekit_protocol::data_packet::Value::StreamChunk(chunk)), + ..Default::default() + } + } + + fn create_trailer_packet(id: &str, reason: Option<&str>) -> proto::DataPacket { + let trailer = proto::data_stream::Trailer { + stream_id: id.to_string(), + reason: reason.unwrap_or_default().to_owned(), + ..Default::default() + }; + proto::DataPacket { + kind: proto::data_packet::Kind::Reliable.into(), + participant_identity: String::new(), // populate later + value: Some(livekit_protocol::data_packet::Value::StreamTrailer(trailer)), + ..Default::default() + } + } +} + +impl Drop for RawStream { + /// Close stream normally if not already closed. + fn drop(&mut self) { + if self.is_closed { + return; + } + let packet = Self::create_trailer_packet(&self.id, None); + let packet_tx = self.packet_tx.clone(); + tokio::spawn(async move { Self::send_packet(&packet_tx, packet).await }); + } +} + +/// Options used when opening an outgoing byte data stream. +#[derive(Clone, Default, Debug, Eq, PartialEq)] +pub struct StreamByteOptions { + pub topic: String, + pub attributes: HashMap, + pub destination_identities: Vec, + pub id: Option, + pub mime_type: Option, + pub name: Option, + pub total_length: Option, +} + +/// Options used when opening an outgoing text data stream. +#[derive(Clone, Default, Debug, Eq, PartialEq)] +pub struct StreamTextOptions { + pub topic: String, + pub attributes: HashMap, + pub destination_identities: Vec, + pub id: Option, + pub operation_type: Option, + pub version: Option, + pub reply_to_stream_id: Option, + pub attached_stream_ids: Vec, + pub generated: Option, +} + +#[derive(Clone)] +pub(crate) struct OutgoingStreamManager { + /// Request channel for sending packets. + packet_tx: UnboundedRequestSender>, +} + +impl OutgoingStreamManager { + pub fn new() -> (Self, UnboundedRequestReceiver>) { + let (packet_tx, packet_rx) = bmrng::unbounded_channel(); + let manager = Self { packet_tx }; + (manager, packet_rx) + } + + pub async fn stream_text(&self, options: StreamTextOptions) -> StreamResult { + let text_header = proto::data_stream::TextHeader { + operation_type: options.operation_type.unwrap_or_default() as i32, + version: options.version.unwrap_or_default(), + reply_to_stream_id: options.reply_to_stream_id.unwrap_or_default(), + attached_stream_ids: options.attached_stream_ids, + generated: options.generated.unwrap_or_default(), + }; + let header = proto::data_stream::Header { + stream_id: options.id.unwrap_or_else(|| create_random_uuid()), + timestamp: Utc::now().timestamp_millis(), + topic: options.topic, + mime_type: TEXT_MIME_TYPE.to_owned(), + total_length: None, + encryption_type: proto::encryption::Type::None.into(), + attributes: options.attributes, + content_header: Some(proto::data_stream::header::ContentHeader::TextHeader( + text_header.clone(), + )), + }; + let open_options = RawStreamOpenOptions { + header: header.clone(), + destination_identities: options.destination_identities, + packet_tx: self.packet_tx.clone(), + }; + let writer = TextStreamWriter { + info: Arc::new(TextStreamInfo::from_headers(header, text_header)), + stream: Arc::new(Mutex::new(RawStream::open(open_options).await?)), + }; + Ok(writer) + } + + pub async fn stream_bytes(&self, options: StreamByteOptions) -> StreamResult { + let byte_header = proto::data_stream::ByteHeader { name: options.name.unwrap_or_default() }; + let header = proto::data_stream::Header { + stream_id: options.id.unwrap_or_else(|| create_random_uuid()), + timestamp: Utc::now().timestamp_millis(), + topic: options.topic, + mime_type: options.mime_type.unwrap_or_else(|| BYTE_MIME_TYPE.to_owned()), + total_length: options.total_length, + encryption_type: proto::encryption::Type::None.into(), + attributes: options.attributes, + content_header: Some(proto::data_stream::header::ContentHeader::ByteHeader( + byte_header.clone(), + )), + }; + + let open_options = RawStreamOpenOptions { + header: header.clone(), + destination_identities: options.destination_identities, + packet_tx: self.packet_tx.clone(), + }; + let writer = ByteStreamWriter { + info: Arc::new(ByteStreamInfo::from_headers(header, byte_header)), + stream: Arc::new(Mutex::new(RawStream::open(open_options).await?)), + }; + Ok(writer) + } + + pub async fn send_text( + &self, + text: &str, + options: StreamTextOptions, + ) -> StreamResult { + let text_header = proto::data_stream::TextHeader { + operation_type: options.operation_type.unwrap_or_default() as i32, + version: options.version.unwrap_or_default(), + reply_to_stream_id: options.reply_to_stream_id.unwrap_or_default(), + attached_stream_ids: options.attached_stream_ids, + generated: options.generated.unwrap_or_default(), + }; + let header = proto::data_stream::Header { + stream_id: options.id.unwrap_or_else(|| create_random_uuid()), + timestamp: Utc::now().timestamp_millis(), + topic: options.topic, + mime_type: TEXT_MIME_TYPE.to_owned(), + total_length: Some(text.bytes().len() as u64), + encryption_type: proto::encryption::Type::None.into(), + attributes: options.attributes, + content_header: Some(proto::data_stream::header::ContentHeader::TextHeader( + text_header.clone(), + )), + }; + let open_options = RawStreamOpenOptions { + header: header.clone(), + destination_identities: options.destination_identities, + packet_tx: self.packet_tx.clone(), + }; + let writer = TextStreamWriter { + info: Arc::new(TextStreamInfo::from_headers(header, text_header)), + stream: Arc::new(Mutex::new(RawStream::open(open_options).await?)), + }; + + let info = (*writer.info).clone(); + writer.write(text).await?; + writer.close().await?; + + Ok(info) + } + + pub async fn send_file( + &self, + path: impl AsRef, + options: StreamByteOptions, + ) -> StreamResult { + let file_size = tokio::fs::metadata(path.as_ref()) + .await + .map(|metadata| metadata.len()) + .map_err(|e| StreamError::from(e))?; + let name = + path.as_ref().file_name().and_then(|n| n.to_str()).unwrap_or_default().to_owned(); + + let byte_header = proto::data_stream::ByteHeader { name }; + let header = proto::data_stream::Header { + stream_id: options.id.unwrap_or_else(|| create_random_uuid()), + timestamp: Utc::now().timestamp_millis(), + topic: options.topic, + mime_type: options.mime_type.unwrap_or_else(|| BYTE_MIME_TYPE.to_owned()), + total_length: Some(file_size as u64), // not overridable + encryption_type: proto::encryption::Type::None.into(), + attributes: options.attributes, + content_header: Some(proto::data_stream::header::ContentHeader::ByteHeader( + byte_header.clone(), + )), + }; + + let open_options = RawStreamOpenOptions { + header: header.clone(), + destination_identities: options.destination_identities, + packet_tx: self.packet_tx.clone(), + }; + let writer = ByteStreamWriter { + info: Arc::new(ByteStreamInfo::from_headers(header, byte_header)), + stream: Arc::new(Mutex::new(RawStream::open(open_options).await?)), + }; + + let info = (*writer.info).clone(); + writer.write_file_contents(path).await?; + writer.close().await?; + + Ok(info) + } +} + +/// Maximum number of bytes to send in a single chunk. +static CHUNK_SIZE: usize = 15000; + +// Default MIME type to use for byte streams. +static BYTE_MIME_TYPE: &str = "application/octet-stream"; + +/// Default MIME type to use for text streams. +static TEXT_MIME_TYPE: &str = "text/plain"; diff --git a/livekit/src/room/mod.rs b/livekit/src/room/mod.rs index 938743f09..06c6350f8 100644 --- a/livekit/src/room/mod.rs +++ b/livekit/src/room/mod.rs @@ -12,8 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::{collections::HashMap, fmt::Debug, sync::Arc, time::Duration}; - +use bmrng::unbounded::UnboundedRequestReceiver; use libwebrtc::{ native::frame_cryptor::EncryptionState, prelude::{ @@ -30,10 +29,17 @@ use livekit_runtime::JoinHandle; use parking_lot::RwLock; pub use proto::DisconnectReason; use proto::{promise::Promise, SignalTarget}; +use std::{collections::HashMap, fmt::Debug, sync::Arc, time::Duration}; use thiserror::Error; -use tokio::sync::{mpsc, oneshot, Mutex as AsyncMutex}; +use tokio::sync::{ + broadcast, + mpsc::{self, UnboundedReceiver}, + oneshot, Mutex as AsyncMutex, +}; +pub use utils::take_cell::TakeCell; pub use self::{ + data_stream::*, e2ee::{manager::E2eeManager, E2eeOptions}, participant::ParticipantKind, }; @@ -47,6 +53,7 @@ use crate::{ }, }; +pub mod data_stream; pub mod e2ee; pub mod id; pub mod options; @@ -168,14 +175,27 @@ pub enum RoomEvent { message: ChatMessage, participant: Option, }, + ByteStreamOpened { + reader: TakeCell, + topic: String, + participant_identity: ParticipantIdentity, + }, + TextStreamOpened { + reader: TakeCell, + topic: String, + participant_identity: ParticipantIdentity, + }, + #[deprecated(note = "Use high-level data streams API instead.")] StreamHeaderReceived { header: proto::data_stream::Header, participant_identity: String, }, + #[deprecated(note = "Use high-level data streams API instead.")] StreamChunkReceived { chunk: proto::data_stream::Chunk, participant_identity: String, }, + #[deprecated(note = "Use high-level data streams API instead.")] StreamTrailerReceived { trailer: proto::data_stream::Trailer, participant_identity: String, @@ -324,6 +344,14 @@ pub struct RoomOptions { pub rtc_config: RtcConfiguration, pub join_retries: u32, pub sdk_options: RoomSdkOptions, + pub preregistration: Option, +} + +#[derive(Debug, Clone)] +#[non_exhaustive] +pub struct PreRegistration { + text_stream_topics: Vec, + byte_stream_topics: Vec, } impl Default for RoomOptions { @@ -343,6 +371,7 @@ impl Default for RoomOptions { }, join_retries: 3, sdk_options: RoomSdkOptions::default(), + preregistration: None, } } } @@ -390,7 +419,16 @@ pub(crate) struct RoomSession { local_participant: LocalParticipant, remote_participants: RwLock>, e2ee_manager: E2eeManager, - room_task: AsyncMutex, oneshot::Sender<()>)>>, + incoming_stream_manager: IncomingStreamManager, + outgoing_stream_manager: OutgoingStreamManager, + handle: AsyncMutex>, +} + +struct Handle { + room_handle: JoinHandle<()>, + incoming_stream_handle: JoinHandle<()>, + outgoing_stream_handle: JoinHandle<()>, + close_tx: broadcast::Sender<()>, } impl Debug for RoomSession { @@ -516,6 +554,11 @@ impl Room { } }); + let (incoming_stream_manager, open_rx) = IncomingStreamManager::new(); + let (outgoing_stream_manager, packet_rx) = OutgoingStreamManager::new(); + + let identity = local_participant.identity().clone(); + let room_info = join_response.room.unwrap(); let inner = Arc::new(RoomSession { sid: Promise::new(), @@ -533,8 +576,11 @@ impl Room { local_participant, dispatcher: dispatcher.clone(), e2ee_manager: e2ee_manager.clone(), - room_task: Default::default(), + incoming_stream_manager, + outgoing_stream_manager, + handle: Default::default(), }); + inner.local_participant.set_session(Arc::downgrade(&inner)); e2ee_manager.on_state_changed({ let dispatcher = dispatcher.clone(); @@ -588,9 +634,25 @@ impl Room { inner.dispatcher.dispatch(&RoomEvent::Connected { participants_with_tracks }); inner.update_connection_state(ConnectionState::Connected); - let (close_tx, close_rx) = oneshot::channel(); - let room_task = livekit_runtime::spawn(inner.clone().room_task(engine_events, close_rx)); - inner.room_task.lock().await.replace((room_task, close_tx)); + let (close_tx, close_rx) = broadcast::channel(1); + + let incoming_stream_handle = livekit_runtime::spawn(incoming_data_stream_task( + open_rx, + dispatcher.clone(), + close_rx.resubscribe(), + )); + let outgoing_stream_handle = livekit_runtime::spawn(outgoing_data_stream_task( + packet_rx, + identity, + rtc_engine.clone(), + close_rx.resubscribe(), + )); + + let room_handle = livekit_runtime::spawn(inner.clone().room_task(engine_events, close_rx)); + + let handle = + Handle { room_handle, incoming_stream_handle, outgoing_stream_handle, close_tx }; + inner.handle.lock().await.replace(handle); Ok((Self { inner }, events)) } @@ -655,7 +717,7 @@ impl RoomSession { async fn room_task( self: Arc, mut engine_events: EngineEvents, - mut close_receiver: oneshot::Receiver<()>, + mut close_rx: broadcast::Receiver<()>, ) { loop { tokio::select! { @@ -680,7 +742,7 @@ impl RoomSession { task.await; }, - _ = &mut close_receiver => { + _ = close_rx.recv() => { break; } } @@ -777,18 +839,18 @@ impl RoomSession { } async fn close(&self, reason: DisconnectReason) -> RoomResult<()> { - if let Some((room_task, close_tx)) = self.room_task.lock().await.take() { - self.rtc_engine.close(reason).await; - self.e2ee_manager.cleanup(); + let Some(handle) = self.handle.lock().await.take() else { Err(RoomError::AlreadyClosed)? }; - let _ = close_tx.send(()); - let _ = room_task.await; + self.rtc_engine.close(reason).await; + self.e2ee_manager.cleanup(); - self.dispatcher.clear(); - Ok(()) - } else { - Err(RoomError::AlreadyClosed) - } + let _ = handle.close_tx.send(()); + let _ = handle.incoming_stream_handle.await; + let _ = handle.outgoing_stream_handle.await; + let _ = handle.room_handle.await; + + self.dispatcher.clear(); + Ok(()) } /// Change the connection state and emit an event @@ -1296,6 +1358,9 @@ impl RoomSession { header: proto::data_stream::Header, participant_identity: String, ) { + self.incoming_stream_manager.handle_header(header.clone(), participant_identity.clone()); + + // For backwards compatibly let event = RoomEvent::StreamHeaderReceived { header, participant_identity }; self.dispatcher.dispatch(&event); } @@ -1305,6 +1370,9 @@ impl RoomSession { chunk: proto::data_stream::Chunk, participant_identity: String, ) { + self.incoming_stream_manager.handle_chunk(chunk.clone()); + + // For backwards compatibly let event = RoomEvent::StreamChunkReceived { chunk, participant_identity }; self.dispatcher.dispatch(&event); } @@ -1314,6 +1382,9 @@ impl RoomSession { trailer: proto::data_stream::Trailer, participant_identity: String, ) { + self.incoming_stream_manager.handle_trailer(trailer.clone()); + + // For backwards compatibly let event = RoomEvent::StreamTrailerReceived { trailer, participant_identity }; self.dispatcher.dispatch(&event); } @@ -1492,6 +1563,57 @@ impl RoomSession { } } +/// Receives stream readers for newly-opened streams and dispatches room events. +async fn incoming_data_stream_task( + mut open_rx: UnboundedReceiver<(AnyStreamReader, String)>, + dispatcher: Dispatcher, + mut close_rx: broadcast::Receiver<()>, +) { + loop { + tokio::select! { + Some((reader, identity)) = open_rx.recv() => { + match reader { + AnyStreamReader::Byte(reader) => dispatcher.dispatch(&RoomEvent::ByteStreamOpened { + topic: reader.info().topic.clone(), + reader: TakeCell::new(reader), + participant_identity: ParticipantIdentity(identity) + }), + AnyStreamReader::Text(reader) => dispatcher.dispatch(&RoomEvent::TextStreamOpened { + topic: reader.info().topic.clone(), + reader: TakeCell::new(reader), + participant_identity: ParticipantIdentity(identity) + }), + } + }, + _ = close_rx.recv() => { + break; + } + } + } +} + +/// Receives packets from the outgoing stream manager and send them. +async fn outgoing_data_stream_task( + mut packet_rx: UnboundedRequestReceiver>, + participant_identity: ParticipantIdentity, + engine: Arc, + mut close_rx: broadcast::Receiver<()>, +) { + loop { + tokio::select! { + Ok((mut packet, responder)) = packet_rx.recv() => { + // Set packet's participant identity field + packet.participant_identity = participant_identity.0.clone(); + let result = engine.publish_data(packet, DataPacketKind::Reliable).await; + let _ = responder.respond(result); + }, + _ = close_rx.recv() => { + break; + } + } + } +} + fn unpack_stream_id(stream_id: &str) -> Option<(&str, &str)> { let split: Vec<&str> = stream_id.split('|').collect(); if split.len() == 2 { diff --git a/livekit/src/room/participant/local_participant.rs b/livekit/src/room/participant/local_participant.rs index 67605ba29..d51c74771 100644 --- a/livekit/src/room/participant/local_participant.rs +++ b/livekit/src/room/participant/local_participant.rs @@ -12,25 +12,35 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::{collections::HashMap, fmt::Debug, pin::Pin, sync::Arc, time::Duration}; +use std::{ + collections::HashMap, + fmt::Debug, + future::Future, + path::Path, + pin::Pin, + sync::{Arc, Weak}, + time::Duration, +}; use super::{ConnectionQuality, ParticipantInner, ParticipantKind, ParticipantTrackPermission}; use crate::{ + data_stream::{ + ByteStreamInfo, ByteStreamWriter, StreamByteOptions, StreamResult, StreamTextOptions, + TextStreamInfo, TextStreamWriter, + }, e2ee::EncryptionType, options::{self, compute_video_encodings, video_layers_from_encodings, TrackPublishOptions}, prelude::*, room::participant::rpc::{RpcError, RpcErrorCode, RpcInvocationData, MAX_PAYLOAD_BYTES}, rtc_engine::{EngineError, RtcEngine}, - ChatMessage, DataPacket, RpcAck, RpcRequest, RpcResponse, SipDTMF, Transcription, + ChatMessage, DataPacket, RoomSession, RpcAck, RpcRequest, RpcResponse, SipDTMF, Transcription, }; use chrono::Utc; -use futures_util::Future; - use libwebrtc::{native::create_random_uuid, rtp_parameters::RtpEncodingParameters}; use livekit_api::signal_client::SignalError; use livekit_protocol as proto; use livekit_runtime::timeout; -use parking_lot::Mutex; +use parking_lot::{Mutex, RwLock}; use proto::request_response::Reason; use semver::Version; use tokio::sync::oneshot; @@ -73,6 +83,7 @@ struct LocalInfo { rpc_state: Mutex, all_participants_allowed: Mutex, track_permissions: Mutex>, + session: RwLock>>, } #[derive(Clone)] @@ -110,10 +121,19 @@ impl LocalParticipant { rpc_state: Mutex::new(RpcState::new()), all_participants_allowed: Mutex::new(true), track_permissions: Mutex::new(vec![]), + session: Default::default(), }), } } + pub(crate) fn set_session(&self, session: Weak) { + *self.local.session.write() = Some(session); + } + + pub(crate) fn session(&self) -> Option> { + self.local.session.read().as_ref().and_then(|s| s.upgrade()) + } + pub(crate) fn internal_track_publications(&self) -> HashMap { self.inner.track_publications.read().clone() } @@ -884,4 +904,74 @@ impl LocalParticipant { log::error!("Failed to publish RPC response: {:?}", e); } } + + /// Send text to participants in the room. + /// + /// This method sends a complete text string to participants in the room as a text stream. + /// The text is sent in a single operation, and the method returns information about the + /// stream used to send the text. + /// + /// # Arguments + /// + /// * `text` - The text content to send. + /// * `options` - Configuration options for the text stream, including topic and + /// destination participants. + /// + pub async fn send_text( + &self, + text: &str, + options: StreamTextOptions, + ) -> StreamResult { + self.session().unwrap().outgoing_stream_manager.send_text(text, options).await + } + + /// Send a file on disk to participants in the room. + /// + /// This method reads a file from the specified path and sends its contents + /// to participants in the room as a byte stream, and the method returns information + /// the stream used to send the file. + /// + /// # Arguments + /// + /// * `path` - Path to the file to be sent. + /// * `options` - Configuration options for the byte stream, including topic and + /// destination participants. + /// + pub async fn send_file( + &self, + path: impl AsRef, + options: StreamByteOptions, + ) -> StreamResult { + self.session().unwrap().outgoing_stream_manager.send_file(path, options).await + } + + /// Stream text incrementally to participants in the room. + /// + /// This method allows sending text data in chunks as it becomes available. + /// Unlike `send_text`, which sends the entire text at once, this method returns + /// a writer that can be used to send text incrementally. + /// + /// # Arguments + /// + /// * `options` - Configuration options for the text stream, including topic and + /// destination participants. + /// + pub async fn stream_text(&self, options: StreamTextOptions) -> StreamResult { + self.session().unwrap().outgoing_stream_manager.stream_text(options).await + } + + /// Stream bytes incrementally to participants in the room. + /// + /// This method allows sending binary data in chunks as it becomes available. + /// Unlike `send_file`, which sends the entire file at once, this method returns + /// a writer that can be used to send binary data incrementally. + /// + /// # Arguments + /// + /// * `options` - Configuration options for the byte stream, including topic and + /// destination participants. + /// + pub async fn stream_bytes(&self, options: StreamByteOptions) -> StreamResult { + self.session().unwrap().outgoing_stream_manager.stream_bytes(options).await + } } diff --git a/livekit/src/room/utils/mod.rs b/livekit/src/room/utils/mod.rs index 21615865e..0cc43b69b 100644 --- a/livekit/src/room/utils/mod.rs +++ b/livekit/src/room/utils/mod.rs @@ -1,5 +1,8 @@ use std::collections::HashMap; +pub mod take_cell; +pub mod utf8_chunk; + pub fn calculate_changed_attributes( old_attributes: HashMap, new_attributes: HashMap, diff --git a/livekit/src/room/utils/take_cell.rs b/livekit/src/room/utils/take_cell.rs new file mode 100644 index 000000000..c22967ea1 --- /dev/null +++ b/livekit/src/room/utils/take_cell.rs @@ -0,0 +1,102 @@ +// Copyright 2025 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use parking_lot::RwLock; +use std::{fmt::Debug, sync::Arc}; + +/// A thread-safe cell that holds a value which can be taken exactly once. +/// After taking the value, subsequent attempts to take it will return `None`. +pub struct TakeCell { + value: Arc>>, +} + +impl TakeCell { + pub(crate) fn new(value: T) -> Self { + Self { value: Arc::new(RwLock::new(Some(value))) } + } + + /// Take ownership of the value in the cell if it matches some predicate. + /// + /// This method will only take the value if the provided predicate returns `true` when called with the current value. + /// If the predicate returns `false` or the value has already been taken, this method returns `None`. + pub(crate) fn take_if_raw(&self, predicate: impl FnOnce(&T) -> bool) -> Option { + if self.value.read().as_ref().map_or(false, |v| predicate(v)) { + self.take() + } else { + None + } + } + + /// Take ownership of the value in the cell. If the value has, + /// already been taken, the result is `None`. + pub fn take(&self) -> Option { + self.value.write().take() + } + + /// Returns whether or not the value has been taken. + pub fn is_taken(&self) -> bool { + self.value.read().is_none() + } +} + +impl Debug for TakeCell +where + T: Debug, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let inner = self.value.read(); + f.debug_struct("TakeCell") + .field( + "value", + &inner.as_ref().map(|v| v as &dyn Debug).unwrap_or(&"" as &dyn Debug), + ) + .finish() + } +} + +impl Clone for TakeCell { + fn clone(&self) -> Self { + Self { value: self.value.clone() } + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_take() { + let cell = TakeCell::new(1); + assert_eq!(cell.is_taken(), false); + assert_eq!(cell.take(), Some(1)); + assert_eq!(cell.take(), None); + assert_eq!(cell.is_taken(), true); + } + + #[test] + fn test_take_if_raw() { + let cell = TakeCell::new(1); + assert_eq!(cell.take_if_raw(|value| *value == 2), None); + assert_eq!(cell.take_if_raw(|value| *value == 1), Some(1)); + assert_eq!(cell.take_if_raw(|value| *value == 1), None); + } + + #[test] + fn test_debug() { + let cell = TakeCell::new(1); + assert_eq!(format!("{:?}", cell), "TakeCell { value: 1 }"); + + cell.take(); + assert_eq!(format!("{:?}", cell), "TakeCell { value: \"\" }"); + } +} diff --git a/livekit/src/room/utils/utf8_chunk.rs b/livekit/src/room/utils/utf8_chunk.rs new file mode 100644 index 000000000..fd2ba2f0d --- /dev/null +++ b/livekit/src/room/utils/utf8_chunk.rs @@ -0,0 +1,108 @@ +// Copyright 2025 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub struct Utf8AwareChunks<'a> { + bytes: &'a [u8], + chunk_size: usize, +} + +impl<'a> Utf8AwareChunks<'a> { + fn new(bytes: &'a [u8], chunk_size: usize) -> Self { + if chunk_size < 4 { + panic!("chunk_size must be at least 4 due to utf8 encoding rules"); + } + Utf8AwareChunks { bytes, chunk_size } + } +} + +impl<'a> Iterator for Utf8AwareChunks<'a> { + type Item = &'a [u8]; + + /// Uses the same algorithm as in the LiveKit JS SDK. + fn next(&mut self) -> Option { + if self.bytes.is_empty() { + return None; + } + + if self.bytes.len() <= self.chunk_size { + let chunk = self.bytes; + self.bytes = &[]; + return Some(chunk); + } + + let mut k = self.chunk_size; + while k > 0 { + let byte = self.bytes[k]; + if (byte & 0xc0) != 0x80 { + break; + } + k -= 1; + } + + let chunk = &self.bytes[..k]; + self.bytes = &self.bytes[k..]; + Some(chunk) + } +} + +pub trait Utf8AwareChunkExt { + /// Splits the bytes into chunks of the specified size, ensuring that + /// UTF-8 character boundaries are respected. + fn utf8_aware_chunks(&self, chunk_size: usize) -> Utf8AwareChunks; +} + +impl Utf8AwareChunkExt for [u8] { + fn utf8_aware_chunks(&self, chunk_size: usize) -> Utf8AwareChunks { + Utf8AwareChunks::new(self, chunk_size) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_string_chunking() { + let test_string = "Hello, World!".as_bytes(); + let chunks: Vec<&[u8]> = test_string.utf8_aware_chunks(4).collect(); + assert_eq!( + chunks, + [&[72, 101, 108, 108], &[111, 44, 32, 87], &[111, 114, 108, 100], &[33][..]] + ); + } + + #[test] + fn test_empty_string_chunking() { + let empty_string = "".as_bytes(); + let chunks: Vec<&[u8]> = empty_string.utf8_aware_chunks(5).collect(); + assert!(chunks.is_empty()); + } + + #[test] + fn test_single_character_string_chunking() { + let single_char = "X".as_bytes(); + let chunks: Vec<&[u8]> = single_char.utf8_aware_chunks(5).collect(); + assert_eq!(chunks, [&[88]]); + } + + #[test] + fn test_mixed_string_chunking() { + let mixed_string = "Hello 👋".as_bytes(); + let chunks: Vec<&[u8]> = mixed_string.utf8_aware_chunks(4).collect(); + assert_eq!( + chunks, + [&[0x48, 0x65, 0x6C, 0x6C], &[0x6F, 0x20][..], &[0xF0, 0x9F, 0x91, 0x8B]] + ); + } +} From 7199a5636bb0b95dbc660ae780cba5edbe65b3d8 Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Tue, 8 Apr 2025 20:11:35 +1000 Subject: [PATCH 182/274] Data streams (#603) --- .nanpa/data-streams.kdl | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .nanpa/data-streams.kdl diff --git a/.nanpa/data-streams.kdl b/.nanpa/data-streams.kdl new file mode 100644 index 000000000..201e9b630 --- /dev/null +++ b/.nanpa/data-streams.kdl @@ -0,0 +1,3 @@ +patch type="added" package="livekit" "High-level data streams API" +patch type="deprecated" package="livekit" "Low-level data stream packet events" +patch type="added" package="livekit-ffi" "FFI support for the new high-level data streams API" \ No newline at end of file From da5c713107d573f28e82d69d91f509dbf6df79e7 Mon Sep 17 00:00:00 2001 From: "ilo-nanpa[bot]" <174912985+ilo-nanpa[bot]@users.noreply.github.com> Date: Tue, 8 Apr 2025 10:15:13 +0000 Subject: [PATCH 183/274] nanpa: bump --- .nanpa/data-streams.kdl | 3 --- Cargo.toml | 4 ++-- livekit-ffi/.nanparc | 2 +- livekit-ffi/CHANGELOG.md | 6 ++++++ livekit-ffi/Cargo.toml | 2 +- livekit/.nanparc | 2 +- livekit/CHANGELOG.md | 10 ++++++++++ livekit/Cargo.toml | 2 +- 8 files changed, 22 insertions(+), 9 deletions(-) delete mode 100644 .nanpa/data-streams.kdl diff --git a/.nanpa/data-streams.kdl b/.nanpa/data-streams.kdl deleted file mode 100644 index 201e9b630..000000000 --- a/.nanpa/data-streams.kdl +++ /dev/null @@ -1,3 +0,0 @@ -patch type="added" package="livekit" "High-level data streams API" -patch type="deprecated" package="livekit" "Low-level data stream packet events" -patch type="added" package="livekit-ffi" "FFI support for the new high-level data streams API" \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 8a2b06aa0..549851c1f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,10 +19,10 @@ imgproc = { version = "0.3.12", path = "imgproc" } yuv-sys = { version = "0.3.7", path = "yuv-sys" } libwebrtc = { version = "0.3.10", path = "libwebrtc" } livekit-api = { version = "0.4.2", path = "livekit-api" } -livekit-ffi = { version = "0.12.19", path = "livekit-ffi" } +livekit-ffi = { version = "0.12.20", path = "livekit-ffi" } livekit-protocol = { version = "0.3.9", path = "livekit-protocol" } livekit-runtime = { version = "0.4.0", path = "livekit-runtime" } -livekit = { version = "0.7.8", path = "livekit" } +livekit = { version = "0.7.9", path = "livekit" } soxr-sys = { version = "0.1.0", path = "soxr-sys" } webrtc-sys-build = { version = "0.3.6", path = "webrtc-sys/build" } webrtc-sys = { version = "0.3.7", path = "webrtc-sys" } diff --git a/livekit-ffi/.nanparc b/livekit-ffi/.nanparc index cddabf5b1..393005a9d 100644 --- a/livekit-ffi/.nanparc +++ b/livekit-ffi/.nanparc @@ -1,2 +1,2 @@ -version 0.12.19 +version 0.12.20 language rust diff --git a/livekit-ffi/CHANGELOG.md b/livekit-ffi/CHANGELOG.md index e870f8129..b778cc1ad 100644 --- a/livekit-ffi/CHANGELOG.md +++ b/livekit-ffi/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [0.12.20] - 2025-04-08 + +### Added + +- FFI support for the new high-level data streams API + ## [0.12.18] - 2025-03-21 ### Fixed diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 86f9cf9cf..4a9209069 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.12.19" +version = "0.12.20" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" diff --git a/livekit/.nanparc b/livekit/.nanparc index c6fb36583..1373b1eb9 100644 --- a/livekit/.nanparc +++ b/livekit/.nanparc @@ -1,2 +1,2 @@ -version 0.7.8 +version 0.7.9 language rust diff --git a/livekit/CHANGELOG.md b/livekit/CHANGELOG.md index f40116614..db344d4d1 100644 --- a/livekit/CHANGELOG.md +++ b/livekit/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## [0.7.9] - 2025-04-08 + +### Added + +- High-level data streams API + +### Deprecated + +- Low-level data stream packet events + ## [0.7.8] - 2025-03-21 ### Fixed diff --git a/livekit/Cargo.toml b/livekit/Cargo.toml index 4beee83b9..20afd54d7 100644 --- a/livekit/Cargo.toml +++ b/livekit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit" -version = "0.7.8" +version = "0.7.9" edition = "2021" license = "Apache-2.0" description = "Rust Client SDK for LiveKit" From e6b3a14a5557b5dfd3890b64816289f9776438d3 Mon Sep 17 00:00:00 2001 From: Daisuke Murase Date: Thu, 10 Apr 2025 10:57:02 -0700 Subject: [PATCH 184/274] Initial HTTP_PROXY support for SignalClient (#623) * test impl * add changesets --- .nanpa/rule-wound-snarl.kdl | 2 + Cargo.lock | 6 +- examples/Cargo.lock | 218 ++++++++++++----- examples/basic_room/Cargo.toml | 4 +- livekit-api/Cargo.toml | 12 +- .../src/signal_client/signal_stream.rs | 221 ++++++++++++++++++ 6 files changed, 399 insertions(+), 64 deletions(-) create mode 100644 .nanpa/rule-wound-snarl.kdl diff --git a/.nanpa/rule-wound-snarl.kdl b/.nanpa/rule-wound-snarl.kdl new file mode 100644 index 000000000..f35fa7fc0 --- /dev/null +++ b/.nanpa/rule-wound-snarl.kdl @@ -0,0 +1,2 @@ +patch package="livekit-ffi" type="added" "Initial HTTP_PROXY support for SignalClient" +patch package="livekit-api" type="added" "Initial HTTP_PROXY support for SignalClient" diff --git a/Cargo.lock b/Cargo.lock index 5ce6de37d..51a546978 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1615,7 +1615,7 @@ checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "livekit" -version = "0.7.8" +version = "0.7.9" dependencies = [ "bmrng", "bytes", @@ -1655,19 +1655,21 @@ dependencies = [ "prost 0.12.3", "rand 0.9.0", "reqwest", + "rustls-native-certs", "scopeguard", "serde", "serde_json", "sha2", "thiserror", "tokio", + "tokio-rustls", "tokio-tungstenite", "url", ] [[package]] name = "livekit-ffi" -version = "0.12.19" +version = "0.12.20" dependencies = [ "bytes", "console-subscriber", diff --git a/examples/Cargo.lock b/examples/Cargo.lock index 06ad21b8f..95cb4972e 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "ab_glyph" @@ -64,7 +64,7 @@ dependencies = [ "once_cell", "serde", "version_check", - "zerocopy", + "zerocopy 0.7.31", ] [[package]] @@ -356,7 +356,7 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.100", ] [[package]] @@ -561,6 +561,16 @@ dependencies = [ "tracing", ] +[[package]] +name = "bmrng" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54df9073108f1558f90ae6c5bf5ab9c917c4185f5527b280c87a993cbead0ac" +dependencies = [ + "futures-core", + "tokio", +] + [[package]] name = "bumpalo" version = "3.14.0" @@ -584,7 +594,7 @@ checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.100", ] [[package]] @@ -595,9 +605,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "bzip2" @@ -951,7 +961,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.41", + "syn 2.0.100", ] [[package]] @@ -968,7 +978,7 @@ checksum = "5c6888cd161769d65134846d4d4981d5a6654307cc46ec83fb917e530aea5f84" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.100", ] [[package]] @@ -1041,7 +1051,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading 0.8.1", + "libloading 0.7.4", ] [[package]] @@ -1170,7 +1180,7 @@ checksum = "c2ad8cef1d801a4686bfd8919f0b30eac4c8e48968c437a6405ded4fb5272d2b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.100", ] [[package]] @@ -1467,7 +1477,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.100", ] [[package]] @@ -1529,10 +1539,22 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + [[package]] name = "gif" version = "0.12.0" @@ -2094,9 +2116,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "libc" -version = "0.2.151" +version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" [[package]] name = "libloading" @@ -2110,12 +2132,12 @@ dependencies = [ [[package]] name = "libloading" -version = "0.8.1" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-sys 0.48.0", + "windows-targets 0.52.0", ] [[package]] @@ -2142,7 +2164,7 @@ dependencies = [ [[package]] name = "libwebrtc" -version = "0.3.7" +version = "0.3.10" dependencies = [ "cxx", "jni", @@ -2185,11 +2207,14 @@ checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "livekit" -version = "0.6.0" +version = "0.7.9" dependencies = [ + "bmrng", + "bytes", "chrono", "futures-util", "lazy_static", + "libloading 0.8.6", "libwebrtc", "livekit-api", "livekit-protocol", @@ -2206,7 +2231,7 @@ dependencies = [ [[package]] name = "livekit-api" -version = "0.4.0" +version = "0.4.2" dependencies = [ "async-tungstenite", "base64", @@ -2217,21 +2242,25 @@ dependencies = [ "livekit-runtime", "log", "parking_lot", + "pbjson-types", "prost 0.12.3", + "rand 0.9.0", "reqwest", + "rustls-native-certs", "scopeguard", "serde", "serde_json", "sha2", "thiserror", "tokio", + "tokio-rustls", "tokio-tungstenite", "url", ] [[package]] name = "livekit-protocol" -version = "0.3.5" +version = "0.3.9" dependencies = [ "futures-util", "livekit-runtime", @@ -2247,7 +2276,7 @@ dependencies = [ [[package]] name = "livekit-runtime" -version = "0.3.0" +version = "0.4.0" dependencies = [ "tokio", "tokio-stream", @@ -2382,7 +2411,7 @@ checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", "log", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.48.0", ] @@ -2604,7 +2633,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.100", ] [[package]] @@ -2710,7 +2739,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.100", ] [[package]] @@ -2788,7 +2817,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" dependencies = [ "base64ct", - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -2874,7 +2903,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.100", ] [[package]] @@ -2974,7 +3003,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" dependencies = [ "proc-macro2", - "syn 2.0.41", + "syn 2.0.100", ] [[package]] @@ -2989,9 +3018,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.70" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" dependencies = [ "unicode-ident", ] @@ -3039,7 +3068,7 @@ dependencies = [ "prost 0.12.3", "prost-types 0.12.3", "regex", - "syn 2.0.41", + "syn 2.0.100", "tempfile", "which", ] @@ -3067,7 +3096,7 @@ dependencies = [ "itertools 0.11.0", "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.100", ] [[package]] @@ -3099,13 +3128,19 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.33" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + [[package]] name = "rand" version = "0.8.5" @@ -3113,8 +3148,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", + "zerocopy 0.8.24", ] [[package]] @@ -3124,7 +3170,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", ] [[package]] @@ -3133,7 +3189,16 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.11", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.2", ] [[package]] @@ -3192,7 +3257,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" dependencies = [ - "getrandom", + "getrandom 0.2.11", "libredox 0.0.1", "thiserror", ] @@ -3298,7 +3363,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" dependencies = [ "cc", - "getrandom", + "getrandom 0.2.11", "libc", "spin", "untrusted", @@ -3325,7 +3390,7 @@ dependencies = [ "livekit", "livekit-api", "log", - "rand", + "rand 0.8.5", "serde_json", "tokio", ] @@ -3540,7 +3605,7 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.100", ] [[package]] @@ -3741,9 +3806,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.41" +version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", @@ -3816,7 +3881,7 @@ checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.100", ] [[package]] @@ -3946,7 +4011,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.100", ] [[package]] @@ -3990,6 +4055,7 @@ dependencies = [ "log", "native-tls", "rustls", + "rustls-native-certs", "tokio", "tokio-native-tls", "tokio-rustls", @@ -4067,7 +4133,7 @@ dependencies = [ "indexmap 1.9.3", "pin-project", "pin-project-lite", - "rand", + "rand 0.8.5", "slab", "tokio", "tokio-util", @@ -4107,7 +4173,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.100", ] [[package]] @@ -4161,7 +4227,7 @@ dependencies = [ "httparse", "log", "native-tls", - "rand", + "rand 0.8.5", "rustls", "sha1", "thiserror", @@ -4182,7 +4248,7 @@ dependencies = [ "httparse", "log", "native-tls", - "rand", + "rand 0.8.5", "sha1", "thiserror", "url", @@ -4321,6 +4387,15 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasm-bindgen" version = "0.2.89" @@ -4342,7 +4417,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.100", "wasm-bindgen-shared", ] @@ -4376,7 +4451,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.100", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4504,7 +4579,7 @@ checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" [[package]] name = "webrtc-sys" -version = "0.3.5" +version = "0.3.7" dependencies = [ "cc", "cxx", @@ -4516,7 +4591,7 @@ dependencies = [ [[package]] name = "webrtc-sys-build" -version = "0.3.5" +version = "0.3.6" dependencies = [ "fs2", "regex", @@ -4602,7 +4677,7 @@ dependencies = [ "js-sys", "khronos-egl", "libc", - "libloading 0.8.1", + "libloading 0.8.6", "log", "metal", "naga", @@ -4980,6 +5055,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.4.1", +] + [[package]] name = "x11-dl" version = "2.21.0" @@ -5031,7 +5115,16 @@ version = "0.7.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c4061bedbb353041c12f413700357bec76df2c7e2ca8e4df8bac24c6bf68e3d" dependencies = [ - "zerocopy-derive", + "zerocopy-derive 0.7.31", +] + +[[package]] +name = "zerocopy" +version = "0.8.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" +dependencies = [ + "zerocopy-derive 0.8.24", ] [[package]] @@ -5042,7 +5135,18 @@ checksum = "b3c129550b3e6de3fd0ba67ba5c81818f9805e58b8d7fee80a3a59d2c9fc601a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.100", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", ] [[package]] diff --git a/examples/basic_room/Cargo.toml b/examples/basic_room/Cargo.toml index 1b4c70dec..eab96d4cb 100644 --- a/examples/basic_room/Cargo.toml +++ b/examples/basic_room/Cargo.toml @@ -6,6 +6,6 @@ edition = "2021" [dependencies] tokio = { version = "1", features = ["full"] } env_logger = "0.10" -livekit = { path = "../../livekit", features = ["native-tls"]} -livekit-api = { path = "../../livekit-api"} +livekit = { path = "../../livekit", features = ["rustls-tls-native-roots"]} +livekit-api = { path = "../../livekit-api", features = ["rustls-tls-native-roots"]} log = "0.4" diff --git a/livekit-api/Cargo.toml b/livekit-api/Cargo.toml index 877e9ef05..e90083fb5 100644 --- a/livekit-api/Cargo.toml +++ b/livekit-api/Cargo.toml @@ -16,7 +16,8 @@ signal-client-tokio = [ "dep:futures-util", "dep:reqwest", "dep:livekit-runtime", - "livekit-runtime/tokio" + "livekit-runtime/tokio", + "dep:base64" ] signal-client-async = [ @@ -57,6 +58,9 @@ native-tls-vendored = [ rustls-tls-native-roots = [ "tokio-tungstenite?/rustls-tls-native-roots", "reqwest?/rustls-tls-native-roots", + "tokio-tungstenite?/__rustls-tls", + "dep:tokio-rustls", + "dep:rustls-native-certs" ] rustls-tls-webpki-roots = [ "tokio-tungstenite?/rustls-tls-webpki-roots", @@ -77,7 +81,7 @@ pbjson-types = "0.6" # webhooks serde_json = { version = "1.0", optional = true } -base64 = { version = "0.21", optional = true } +base64 = { version = "0.21", optional = true, features = ["std"] } # access_token & services jsonwebtoken = { version = "9", default-features = false, optional = true } @@ -86,7 +90,9 @@ jsonwebtoken = { version = "9", default-features = false, optional = true } livekit-runtime = { path = "../livekit-runtime", version = "0.4.0", optional = true} tokio-tungstenite = { version = "0.20", optional = true } async-tungstenite = { version = "0.25.0", features = [ "async-std-runtime", "async-native-tls"], optional = true } -tokio = { version = "1", default-features = false, features = ["sync", "macros", "signal"], optional = true } +tokio = { version = "1", default-features = false, features = ["sync", "macros", "signal", "io-util", "net"], optional = true } +tokio-rustls = { version = "0.24", optional = true } +rustls-native-certs = { version = "0.6", optional = true } futures-util = { version = "0.3", default-features = false, features = [ "sink" ], optional = true } # This dependency must be kept in sync with reqwest's version diff --git a/livekit-api/src/signal_client/signal_stream.rs b/livekit-api/src/signal_client/signal_stream.rs index c5ab27951..e4506d9fe 100644 --- a/livekit-api/src/signal_client/signal_stream.rs +++ b/livekit-api/src/signal_client/signal_stream.rs @@ -19,9 +19,19 @@ use futures_util::{ use livekit_protocol as proto; use livekit_runtime::{JoinHandle, TcpStream}; use prost::Message as ProtoMessage; +use std::{env, io}; use tokio::sync::{mpsc, oneshot}; +#[cfg(feature = "signal-client-tokio")] +use base64; + +#[cfg(feature = "signal-client-tokio")] +use tokio::{ + io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}, + net::TcpStream as TokioTcpStream, +}; + #[cfg(feature = "signal-client-tokio")] use tokio_tungstenite::{ connect_async, @@ -96,6 +106,217 @@ impl SignalStream { log::info!("connecting to {}", url); } + #[cfg(feature = "signal-client-tokio")] + let ws_stream = { + // Check for HTTP_PROXY or HTTPS_PROXY environment variables + let proxy_env = if url.scheme() == "wss" { + env::var("HTTPS_PROXY").or_else(|_| env::var("https_proxy")) + } else { + env::var("HTTP_PROXY").or_else(|_| env::var("http_proxy")) + }; + + // Connect directly or through proxy + let ws_stream = if let Ok(proxy_url) = proxy_env { + if !proxy_url.is_empty() { + log::info!("Using proxy: {}", proxy_url); + let proxy_url = url::Url::parse(&proxy_url).map_err(|e| { + WsError::Io(io::Error::new( + io::ErrorKind::InvalidInput, + format!("Invalid proxy URL: {}", e), + )) + })?; + + let host = url.host_str().ok_or_else(|| { + WsError::Io(io::Error::new( + io::ErrorKind::InvalidInput, + "Target URL has no host", + )) + })?; + + let port = url.port_or_known_default().ok_or_else(|| { + WsError::Io(io::Error::new( + io::ErrorKind::InvalidInput, + "Target URL has no port and no default for scheme", + )) + })?; + + let proxy_host = proxy_url.host_str().ok_or_else(|| { + WsError::Io(io::Error::new( + io::ErrorKind::InvalidInput, + "Proxy URL has no host", + )) + })?; + + let proxy_port = proxy_url.port_or_known_default().unwrap_or(80); + let proxy_addr = format!("{}:{}", proxy_host, proxy_port); + + let mut proxy_stream = + TokioTcpStream::connect(proxy_addr).await.map_err(WsError::Io)?; + + let mut proxy_auth_header = None; + if let Some(password) = proxy_url.password() { + let auth = format!("{}:{}", proxy_url.username(), password); + let auth = format!("Basic {}", base64::encode(auth)); + proxy_auth_header = Some(auth); + } + + // Send CONNECT request + let target = format!("{}:{}", host, port); + let mut connect_req = + format!("CONNECT {} HTTP/1.1\r\nHost: {}\r\n", target, target); + + // Add proxy authorization if needed + if let Some(auth) = proxy_auth_header { + connect_req.push_str(&format!("Proxy-Authorization: {}\r\n", auth)); + } + + // Finalize request + connect_req.push_str("\r\n"); + + log::debug!("Sending CONNECT request to proxy"); + proxy_stream.write_all(connect_req.as_bytes()).await.map_err(WsError::Io)?; + + // Read and parse response + let mut response = Vec::new(); + let mut buf = [0u8; 4096]; + let mut headers_complete = false; + + while !headers_complete { + let n = proxy_stream.read(&mut buf).await.map_err(WsError::Io)?; + if n == 0 { + return Err(WsError::Io(io::Error::new( + io::ErrorKind::UnexpectedEof, + "Proxy connection closed while reading response", + )) + .into()); + } + + response.extend_from_slice(&buf[..n]); + + // Check if we've received the end of headers (double CRLF) + if response.windows(4).any(|w| w == b"\r\n\r\n") { + headers_complete = true; + } + } + + // Parse status line + let response_str = String::from_utf8_lossy(&response); + let status_line = response_str.lines().next().ok_or_else(|| { + WsError::Io(io::Error::new( + io::ErrorKind::InvalidData, + "Invalid proxy response", + )) + })?; + + // Check status code + if !status_line.contains("200") { + return Err(WsError::Io(io::Error::new( + io::ErrorKind::ConnectionRefused, + format!("Proxy connection failed: {}", status_line), + )) + .into()); + } + + log::debug!("Proxy connection established to {}", target); + + // Create MaybeTlsStream based on original URL scheme + let stream = if url.scheme() == "wss" { + // Only enable proxy TLS support when rustls-tls-native-roots is enabled + #[cfg(feature = "rustls-tls-native-roots")] + { + // For WSS, we need to establish TLS over the proxy connection + use std::sync::Arc; + use tokio_rustls::{rustls, TlsConnector}; + + // Load native root certificates + let mut root_store = rustls::RootCertStore::empty(); + match rustls_native_certs::load_native_certs() { + Ok(certs) => { + let roots: Vec = certs + .into_iter() + .map(|cert| rustls::Certificate(cert.0)) + .collect(); + + for root in roots { + root_store.add(&root).map_err(|e| { + WsError::Io(io::Error::new( + io::ErrorKind::Other, + format!( + "Failed to parse root certificate: {:?}", + e + ), + )) + })?; + } + } + Err(e) => { + return Err(WsError::Io(io::Error::new( + io::ErrorKind::Other, + format!("Could not load native root certificates: {}", e), + )) + .into()); + } + } + + let tls_config = rustls::ClientConfig::builder() + .with_safe_defaults() + .with_root_certificates(root_store) + .with_no_client_auth(); + + let server_name = rustls::ServerName::try_from(host).map_err(|_| { + WsError::Io(io::Error::new( + io::ErrorKind::InvalidInput, + format!("Invalid DNS name: {}", host), + )) + })?; + + let connector = TlsConnector::from(Arc::new(tls_config)); + let tls_stream = connector + .connect(server_name, proxy_stream) + .await + .map_err(|e| { + WsError::Io(io::Error::new( + io::ErrorKind::Other, + format!("TLS connection error: {}", e), + )) + })?; + + MaybeTlsStream::Rustls(tls_stream) + } + + #[cfg(not(feature = "rustls-tls-native-roots"))] + { + // For non-rustls-tls-native-roots builds, don't support proxy for WSS + return Err(WsError::Io(io::Error::new( + io::ErrorKind::Other, + "WSS over proxy requires rustls-tls-native-roots feature", + )) + .into()); + } + } else { + // For plain WS, just use the proxy stream directly + MaybeTlsStream::Plain(proxy_stream) + }; + + // Now perform WebSocket handshake over the established connection + let (ws_stream, _) = + tokio_tungstenite::client_async_with_config(url, stream, None).await?; + ws_stream + } else { + // No proxy specified, connect directly + let (ws_stream, _) = connect_async(url).await?; + ws_stream + } + } else { + // Non-tokio build or no proxy - connect directly + let (ws_stream, _) = connect_async(url).await?; + ws_stream + }; + + ws_stream + }; + + #[cfg(not(feature = "signal-client-tokio"))] let (ws_stream, _) = connect_async(url).await?; let (ws_writer, ws_reader) = ws_stream.split(); From 21eeeb71fcb880a63f1a583e9a4f8d208bcaf597 Mon Sep 17 00:00:00 2001 From: Daisuke Murase Date: Thu, 10 Apr 2025 11:19:24 -0700 Subject: [PATCH 185/274] bump ffi version (#625) * bump ffi version * update other files --- Cargo.toml | 2 +- livekit-ffi/.nanparc | 2 +- livekit-ffi/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 549851c1f..3b4ad22ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ imgproc = { version = "0.3.12", path = "imgproc" } yuv-sys = { version = "0.3.7", path = "yuv-sys" } libwebrtc = { version = "0.3.10", path = "libwebrtc" } livekit-api = { version = "0.4.2", path = "livekit-api" } -livekit-ffi = { version = "0.12.20", path = "livekit-ffi" } +livekit-ffi = { version = "0.12.21", path = "livekit-ffi" } livekit-protocol = { version = "0.3.9", path = "livekit-protocol" } livekit-runtime = { version = "0.4.0", path = "livekit-runtime" } livekit = { version = "0.7.9", path = "livekit" } diff --git a/livekit-ffi/.nanparc b/livekit-ffi/.nanparc index 393005a9d..36229697a 100644 --- a/livekit-ffi/.nanparc +++ b/livekit-ffi/.nanparc @@ -1,2 +1,2 @@ -version 0.12.20 +version 0.12.21 language rust diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 4a9209069..0a50efeed 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.12.20" +version = "0.12.21" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" From e8a7a6a154ec2a9bab44daec6b757cff66813007 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Tue, 15 Apr 2025 17:24:30 +0200 Subject: [PATCH 186/274] fix soxr: remove FE_INVALID optimization (#627) --- Cargo.lock | 2 +- soxr-sys/src/rint.h | 62 ++++++++++++++++++++++----------------------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 51a546978..95c4d3f30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1669,7 +1669,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.12.20" +version = "0.12.21" dependencies = [ "bytes", "console-subscriber", diff --git a/soxr-sys/src/rint.h b/soxr-sys/src/rint.h index 2f1dfbed6..1d8c8c9f9 100644 --- a/soxr-sys/src/rint.h +++ b/soxr-sys/src/rint.h @@ -16,19 +16,19 @@ #define rint16D(a,b) __asm__ __volatile__("fistps %0": "=m"(a): "t"(b): "st") #define rint32F rint32D #define rint16F rint16D - #define FE_INVALID 1 - static __inline int fe_test_invalid(void) { - int status_word; - __asm__ __volatile__("fnstsw %%ax": "=a"(status_word)); - return status_word & FE_INVALID; - } - static __inline int fe_clear_invalid(void) { - int32_t status[7]; - __asm__ __volatile__("fnstenv %0": "=m"(status)); - status[1] &= ~FE_INVALID; - __asm__ __volatile__("fldenv %0": : "m"(*status)); - return 0; - } + //#define FE_INVALID 1 + // static __inline int fe_test_invalid(void) { + // int status_word; + // __asm__ __volatile__("fnstsw %%ax": "=a"(status_word)); + // return status_word & FE_INVALID; + // } + // static __inline int fe_clear_invalid(void) { + // int32_t status[7]; + // __asm__ __volatile__("fnstenv %0": "=m"(status)); + // status[1] &= ~FE_INVALID; + // __asm__ __volatile__("fldenv %0": : "m"(*status)); + // return 0; + // } #elif defined _MSC_VER && defined _M_IX86 #define FPU_RINT32 #define FPU_RINT16 @@ -42,19 +42,19 @@ #define rint32F(y,x) rint32f(&(y),x) #define rint16D(y,x) rint16d(&(y),x) #define rint16F(y,x) rint16f(&(y),x) - #define FE_INVALID 1 - static __inline int fe_test_invalid(void) { - short status_word; - __asm fnstsw status_word - return status_word & FE_INVALID; - } - static __inline int fe_clear_invalid(void) { - int32_t status[7]; - __asm fnstenv status - status[1] &= ~FE_INVALID; - __asm fldenv status - return 0; - } + //#define FE_INVALID 1 + // static __inline int fe_test_invalid(void) { + // short status_word; + // __asm fnstsw status_word + // return status_word & FE_INVALID; + // } + // static __inline int fe_clear_invalid(void) { + // int32_t status[7]; + // __asm fnstenv status + // status[1] &= ~FE_INVALID; + // __asm fldenv status + // return 0; + // } #elif defined _MSC_VER && defined _M_X64 #include #include @@ -70,17 +70,17 @@ #define rint32F(y,x) rint32f(&(y),x) #define rint16D(y,x) rint16d(&(y),x) #define rint16F(y,x) rint16d(&(y),(double)(x)) - #define FE_INVALID 1 - #define fe_test_invalid() (_statusfp() & _SW_INVALID) - #define fe_clear_invalid _clearfp /* Note: clears all. */ + //#define FE_INVALID 1 + // #define fe_test_invalid() (_statusfp() & _SW_INVALID) + // #define fe_clear_invalid _clearfp /* Note: clears all. */ #elif HAVE_LRINT && LONG_MAX == 2147483647L && HAVE_FENV_H #include #include #define FPU_RINT32 #define rint32D(y,x) ((y)=lrint(x)) #define rint32F(y,x) ((y)=lrintf(x)) - #define fe_test_invalid() fetestexcept(FE_INVALID) - #define fe_clear_invalid() feclearexcept(FE_INVALID) + // #define fe_test_invalid() fetestexcept(FE_INVALID) + // #define fe_clear_invalid() feclearexcept(FE_INVALID) #endif #if !defined FPU_RINT32 From 0773bcec4e24812b382e71166b2ab359bf1a9384 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Tue, 15 Apr 2025 18:01:04 +0200 Subject: [PATCH 187/274] ffi v0.12.22 (#628) --- Cargo.lock | 2 +- Cargo.toml | 2 +- livekit-ffi/.nanparc | 2 +- livekit-ffi/Cargo.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 95c4d3f30..36b112358 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1669,7 +1669,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.12.21" +version = "0.12.22" dependencies = [ "bytes", "console-subscriber", diff --git a/Cargo.toml b/Cargo.toml index 3b4ad22ca..96aa7094a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ imgproc = { version = "0.3.12", path = "imgproc" } yuv-sys = { version = "0.3.7", path = "yuv-sys" } libwebrtc = { version = "0.3.10", path = "libwebrtc" } livekit-api = { version = "0.4.2", path = "livekit-api" } -livekit-ffi = { version = "0.12.21", path = "livekit-ffi" } +livekit-ffi = { version = "0.12.22", path = "livekit-ffi" } livekit-protocol = { version = "0.3.9", path = "livekit-protocol" } livekit-runtime = { version = "0.4.0", path = "livekit-runtime" } livekit = { version = "0.7.9", path = "livekit" } diff --git a/livekit-ffi/.nanparc b/livekit-ffi/.nanparc index 36229697a..b1b05a40d 100644 --- a/livekit-ffi/.nanparc +++ b/livekit-ffi/.nanparc @@ -1,2 +1,2 @@ -version 0.12.21 +version 0.12.22 language rust diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 0a50efeed..2ce094824 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.12.21" +version = "0.12.22" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" From de3a3e12b048489e3c3d8a99be0ef0ae02b7bbda Mon Sep 17 00:00:00 2001 From: lukasIO Date: Wed, 7 May 2025 11:37:06 +0200 Subject: [PATCH 188/274] Bump protocol to v1.37.1 and add audio_features handling (#634) --- livekit-api/src/services/egress.rs | 4 + livekit-api/src/services/sip.rs | 4 +- livekit-ffi/protocol/participant.proto | 1 + livekit-ffi/protocol/track.proto | 11 + livekit-ffi/src/conversion/participant.rs | 1 + livekit-ffi/src/conversion/room.rs | 1 + livekit-ffi/src/conversion/track.rs | 37 + livekit-ffi/src/livekit.proto.rs | 47 + livekit-protocol/protocol | 2 +- livekit-protocol/src/livekit.rs | 239 +- livekit-protocol/src/livekit.serde.rs | 2168 ++++++++++++++++-- livekit/src/prelude.rs | 2 + livekit/src/proto.rs | 1 + livekit/src/room/data_stream/outgoing.rs | 4 +- livekit/src/room/participant/mod.rs | 1 + livekit/src/room/publication/local.rs | 6 +- livekit/src/room/publication/mod.rs | 22 +- livekit/src/room/publication/remote.rs | 6 +- livekit/src/room/track/mod.rs | 10 +- livekit/src/room/track/remote_audio_track.rs | 2 +- 20 files changed, 2303 insertions(+), 266 deletions(-) diff --git a/livekit-api/src/services/egress.rs b/livekit-api/src/services/egress.rs index 7c2fd08ce..a3b9d954d 100644 --- a/livekit-api/src/services/egress.rs +++ b/livekit-api/src/services/egress.rs @@ -153,6 +153,7 @@ impl EgressClient { image_outputs, output: None, // Deprecated await_start_signal: options.await_start_signal, + ..Default::default() }, self.base .auth_header(VideoGrants { room_record: true, ..Default::default() }, None)?, @@ -184,6 +185,7 @@ impl EgressClient { stream_outputs, segment_outputs, image_outputs, + ..Default::default() }, self.base .auth_header(VideoGrants { room_record: true, ..Default::default() }, None)?, @@ -215,6 +217,7 @@ impl EgressClient { segment_outputs, image_outputs, output: None, // Deprecated + ..Default::default() }, self.base .auth_header(VideoGrants { room_record: true, ..Default::default() }, None)?, @@ -244,6 +247,7 @@ impl EgressClient { } }, track_id: track_id.to_string(), + ..Default::default() }, self.base .auth_header(VideoGrants { room_record: true, ..Default::default() }, None)?, diff --git a/livekit-api/src/services/sip.rs b/livekit-api/src/services/sip.rs index 01e4d431a..6fa7aacf1 100644 --- a/livekit-api/src/services/sip.rs +++ b/livekit-api/src/services/sip.rs @@ -354,9 +354,7 @@ impl SIPClient { hide_phone_number: options.hide_phone_number, rule: Some(proto::SipDispatchRule { rule: Some(rule.to_owned()) }), - // TODO: support these attributes - room_preset: Default::default(), - room_config: Default::default(), + ..Default::default() }, self.base.auth_header( Default::default(), diff --git a/livekit-ffi/protocol/participant.proto b/livekit-ffi/protocol/participant.proto index 3f1e2720c..ea7eb41a1 100644 --- a/livekit-ffi/protocol/participant.proto +++ b/livekit-ffi/protocol/participant.proto @@ -70,4 +70,5 @@ enum DisconnectReason { USER_REJECTED = 12; // SIP protocol failure or unexpected response SIP_TRUNK_FAILURE = 13; + CONNECTION_TIMEOUT = 14; } \ No newline at end of file diff --git a/livekit-ffi/protocol/track.proto b/livekit-ffi/protocol/track.proto index 88a62c236..0e6889e2e 100644 --- a/livekit-ffi/protocol/track.proto +++ b/livekit-ffi/protocol/track.proto @@ -89,6 +89,7 @@ message TrackPublicationInfo { required bool muted = 9; required bool remote = 10; required EncryptionType encryption_type = 11; + repeated AudioTrackFeature audio_features = 12; } message OwnedTrackPublication { @@ -147,3 +148,13 @@ message ParticipantTrackPermission { message SetTrackSubscriptionPermissionsResponse { } + +enum AudioTrackFeature { + TF_STEREO = 0; + TF_NO_DTX = 1; + TF_AUTO_GAIN_CONTROL = 2; + TF_ECHO_CANCELLATION = 3; + TF_NOISE_SUPPRESSION = 4; + TF_ENHANCED_NOISE_CANCELLATION = 5; + TF_PRECONNECT_BUFFER = 6; // client will buffer audio once available and send it to the server via bytes stream once connected +} diff --git a/livekit-ffi/src/conversion/participant.rs b/livekit-ffi/src/conversion/participant.rs index c80453cd9..33cf20dec 100644 --- a/livekit-ffi/src/conversion/participant.rs +++ b/livekit-ffi/src/conversion/participant.rs @@ -61,6 +61,7 @@ impl From for proto::DisconnectReason { DisconnectReason::UserUnavailable => proto::DisconnectReason::UserUnavailable, DisconnectReason::UserRejected => proto::DisconnectReason::UserRejected, DisconnectReason::SipTrunkFailure => proto::DisconnectReason::SipTrunkFailure, + DisconnectReason::ConnectionTimeout => proto::DisconnectReason::ConnectionTimeout, } } } diff --git a/livekit-ffi/src/conversion/room.rs b/livekit-ffi/src/conversion/room.rs index 2662bf3b8..805d7ce2f 100644 --- a/livekit-ffi/src/conversion/room.rs +++ b/livekit-ffi/src/conversion/room.rs @@ -98,6 +98,7 @@ impl From for proto::DisconnectReason { DisconnectReason::UserUnavailable => Self::UserUnavailable, DisconnectReason::UserRejected => Self::UserRejected, DisconnectReason::SipTrunkFailure => Self::SipTrunkFailure, + DisconnectReason::ConnectionTimeout => Self::ConnectionTimeout, } } } diff --git a/livekit-ffi/src/conversion/track.rs b/livekit-ffi/src/conversion/track.rs index 2e1d33f69..82da937dd 100644 --- a/livekit-ffi/src/conversion/track.rs +++ b/livekit-ffi/src/conversion/track.rs @@ -34,6 +34,11 @@ impl From<&FfiPublication> for proto::TrackPublicationInfo { muted: publication.is_muted(), remote: publication.is_remote(), encryption_type: proto::EncryptionType::from(publication.encryption_type()).into(), + audio_features: publication + .audio_features() + .into_iter() + .map(|i| proto::AudioTrackFeature::from(i).into()) + .collect(), } } } @@ -121,3 +126,35 @@ impl From for ParticipantTrackPermission { } } } + +impl From for AudioTrackFeature { + fn from(value: proto::AudioTrackFeature) -> Self { + match value { + proto::AudioTrackFeature::TfStereo => AudioTrackFeature::TfStereo, + proto::AudioTrackFeature::TfNoDtx => AudioTrackFeature::TfNoDtx, + proto::AudioTrackFeature::TfAutoGainControl => AudioTrackFeature::TfAutoGainControl, + proto::AudioTrackFeature::TfEchoCancellation => AudioTrackFeature::TfEchoCancellation, + proto::AudioTrackFeature::TfNoiseSuppression => AudioTrackFeature::TfNoiseSuppression, + proto::AudioTrackFeature::TfEnhancedNoiseCancellation => { + AudioTrackFeature::TfEnhancedNoiseCancellation + } + proto::AudioTrackFeature::TfPreconnectBuffer => AudioTrackFeature::TfPreconnectBuffer, + } + } +} + +impl From for proto::AudioTrackFeature { + fn from(value: AudioTrackFeature) -> Self { + match value { + AudioTrackFeature::TfStereo => proto::AudioTrackFeature::TfStereo, + AudioTrackFeature::TfNoDtx => proto::AudioTrackFeature::TfNoDtx, + AudioTrackFeature::TfAutoGainControl => proto::AudioTrackFeature::TfAutoGainControl, + AudioTrackFeature::TfEchoCancellation => proto::AudioTrackFeature::TfEchoCancellation, + AudioTrackFeature::TfNoiseSuppression => proto::AudioTrackFeature::TfNoiseSuppression, + AudioTrackFeature::TfEnhancedNoiseCancellation => { + proto::AudioTrackFeature::TfEnhancedNoiseCancellation + } + AudioTrackFeature::TfPreconnectBuffer => proto::AudioTrackFeature::TfPreconnectBuffer, + } + } +} diff --git a/livekit-ffi/src/livekit.proto.rs b/livekit-ffi/src/livekit.proto.rs index be0e5c1bc..39a539617 100644 --- a/livekit-ffi/src/livekit.proto.rs +++ b/livekit-ffi/src/livekit.proto.rs @@ -1392,6 +1392,8 @@ pub struct TrackPublicationInfo { pub remote: bool, #[prost(enumeration="EncryptionType", required, tag="11")] pub encryption_type: i32, + #[prost(enumeration="AudioTrackFeature", repeated, packed="false", tag="12")] + pub audio_features: ::prost::alloc::vec::Vec, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -1575,6 +1577,48 @@ impl StreamState { } } } +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum AudioTrackFeature { + TfStereo = 0, + TfNoDtx = 1, + TfAutoGainControl = 2, + TfEchoCancellation = 3, + TfNoiseSuppression = 4, + TfEnhancedNoiseCancellation = 5, + /// client will buffer audio once available and send it to the server via bytes stream once connected + TfPreconnectBuffer = 6, +} +impl AudioTrackFeature { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + AudioTrackFeature::TfStereo => "TF_STEREO", + AudioTrackFeature::TfNoDtx => "TF_NO_DTX", + AudioTrackFeature::TfAutoGainControl => "TF_AUTO_GAIN_CONTROL", + AudioTrackFeature::TfEchoCancellation => "TF_ECHO_CANCELLATION", + AudioTrackFeature::TfNoiseSuppression => "TF_NOISE_SUPPRESSION", + AudioTrackFeature::TfEnhancedNoiseCancellation => "TF_ENHANCED_NOISE_CANCELLATION", + AudioTrackFeature::TfPreconnectBuffer => "TF_PRECONNECT_BUFFER", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "TF_STEREO" => Some(Self::TfStereo), + "TF_NO_DTX" => Some(Self::TfNoDtx), + "TF_AUTO_GAIN_CONTROL" => Some(Self::TfAutoGainControl), + "TF_ECHO_CANCELLATION" => Some(Self::TfEchoCancellation), + "TF_NOISE_SUPPRESSION" => Some(Self::TfNoiseSuppression), + "TF_ENHANCED_NOISE_CANCELLATION" => Some(Self::TfEnhancedNoiseCancellation), + "TF_PRECONNECT_BUFFER" => Some(Self::TfPreconnectBuffer), + _ => None, + } + } +} /// Enable/Disable a remote track publication #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -1694,6 +1738,7 @@ pub enum DisconnectReason { UserRejected = 12, /// SIP protocol failure or unexpected response SipTrunkFailure = 13, + ConnectionTimeout = 14, } impl DisconnectReason { /// String value of the enum field names used in the ProtoBuf definition. @@ -1716,6 +1761,7 @@ impl DisconnectReason { DisconnectReason::UserUnavailable => "USER_UNAVAILABLE", DisconnectReason::UserRejected => "USER_REJECTED", DisconnectReason::SipTrunkFailure => "SIP_TRUNK_FAILURE", + DisconnectReason::ConnectionTimeout => "CONNECTION_TIMEOUT", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1735,6 +1781,7 @@ impl DisconnectReason { "USER_UNAVAILABLE" => Some(Self::UserUnavailable), "USER_REJECTED" => Some(Self::UserRejected), "SIP_TRUNK_FAILURE" => Some(Self::SipTrunkFailure), + "CONNECTION_TIMEOUT" => Some(Self::ConnectionTimeout), _ => None, } } diff --git a/livekit-protocol/protocol b/livekit-protocol/protocol index 7d8d588f7..b047e92f0 160000 --- a/livekit-protocol/protocol +++ b/livekit-protocol/protocol @@ -1 +1 @@ -Subproject commit 7d8d588f7509f169fd6b72817dfecf25c31414e8 +Subproject commit b047e92f055de744e5a5293848830cd803f3217b diff --git a/livekit-protocol/src/livekit.rs b/livekit-protocol/src/livekit.rs index 6097e1704..250ab4345 100644 --- a/livekit-protocol/src/livekit.rs +++ b/livekit-protocol/src/livekit.rs @@ -199,6 +199,14 @@ pub struct Pagination { #[prost(int32, tag="2")] pub limit: i32, } +/// ListUpdate is used for updated APIs where 'repeated string' field is modified. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ListUpdate { + /// set the field to a new list + #[prost(string, repeated, tag="1")] + pub set: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, +} #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct Room { @@ -321,6 +329,8 @@ pub struct ParticipantInfo { pub attributes: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, #[prost(enumeration="DisconnectReason", tag="16")] pub disconnect_reason: i32, + #[prost(enumeration="participant_info::KindDetail", repeated, tag="18")] + pub kind_details: ::prost::alloc::vec::Vec, } /// Nested message and enum types in `ParticipantInfo`. pub mod participant_info { @@ -400,6 +410,32 @@ pub mod participant_info { } } } + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] + #[repr(i32)] + pub enum KindDetail { + CloudAgent = 0, + Forwarded = 1, + } + impl KindDetail { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + KindDetail::CloudAgent => "CLOUD_AGENT", + KindDetail::Forwarded => "FORWARDED", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "CLOUD_AGENT" => Some(Self::CloudAgent), + "FORWARDED" => Some(Self::Forwarded), + _ => None, + } + } + } } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -855,6 +891,7 @@ pub mod client_info { Cpp = 10, UnityWeb = 11, Node = 12, + Unreal = 13, } impl Sdk { /// String value of the enum field names used in the ProtoBuf definition. @@ -876,6 +913,7 @@ pub mod client_info { Sdk::Cpp => "CPP", Sdk::UnityWeb => "UNITY_WEB", Sdk::Node => "NODE", + Sdk::Unreal => "UNREAL", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -894,6 +932,7 @@ pub mod client_info { "CPP" => Some(Self::Cpp), "UNITY_WEB" => Some(Self::UnityWeb), "NODE" => Some(Self::Node), + "UNREAL" => Some(Self::Unreal), _ => None, } } @@ -1273,6 +1312,14 @@ pub mod data_stream { } } } +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct WebhookConfig { + #[prost(string, tag="1")] + pub url: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub signing_key: ::prost::alloc::string::String, +} #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] pub enum AudioCodec { @@ -1367,10 +1414,13 @@ impl ImageCodec { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] pub enum BackupCodecPolicy { - /// default behavior, regress to backup codec and all subscribers will receive the backup codec - Regression = 0, + /// default behavior, the track prefer to regress to backup codec and all subscribers will receive the backup codec, + /// the sfu will try to regress codec if possible but not assured. + PreferRegression = 0, /// encoding/send the primary and backup codec simultaneously Simulcast = 1, + /// force the track to regress to backup codec, this option can be used in video conference or the publisher has limited bandwidth/encoding power + Regression = 2, } impl BackupCodecPolicy { /// String value of the enum field names used in the ProtoBuf definition. @@ -1379,15 +1429,17 @@ impl BackupCodecPolicy { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - BackupCodecPolicy::Regression => "REGRESSION", + BackupCodecPolicy::PreferRegression => "PREFER_REGRESSION", BackupCodecPolicy::Simulcast => "SIMULCAST", + BackupCodecPolicy::Regression => "REGRESSION", } } /// Creates an enum from field names used in the ProtoBuf definition. pub fn from_str_name(value: &str) -> ::core::option::Option { match value { - "REGRESSION" => Some(Self::Regression), + "PREFER_REGRESSION" => Some(Self::PreferRegression), "SIMULCAST" => Some(Self::Simulcast), + "REGRESSION" => Some(Self::Regression), _ => None, } } @@ -1579,6 +1631,8 @@ pub enum DisconnectReason { UserRejected = 12, /// SIP protocol failure or unexpected response SipTrunkFailure = 13, + /// server timed out a participant session + ConnectionTimeout = 14, } impl DisconnectReason { /// String value of the enum field names used in the ProtoBuf definition. @@ -1601,6 +1655,7 @@ impl DisconnectReason { DisconnectReason::UserUnavailable => "USER_UNAVAILABLE", DisconnectReason::UserRejected => "USER_REJECTED", DisconnectReason::SipTrunkFailure => "SIP_TRUNK_FAILURE", + DisconnectReason::ConnectionTimeout => "CONNECTION_TIMEOUT", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1620,6 +1675,7 @@ impl DisconnectReason { "USER_UNAVAILABLE" => Some(Self::UserUnavailable), "USER_REJECTED" => Some(Self::UserRejected), "SIP_TRUNK_FAILURE" => Some(Self::SipTrunkFailure), + "CONNECTION_TIMEOUT" => Some(Self::ConnectionTimeout), _ => None, } } @@ -1697,6 +1753,8 @@ pub enum AudioTrackFeature { TfEchoCancellation = 3, TfNoiseSuppression = 4, TfEnhancedNoiseCancellation = 5, + /// client will buffer audio once available and send it to the server via bytes stream once connected + TfPreconnectBuffer = 6, } impl AudioTrackFeature { /// String value of the enum field names used in the ProtoBuf definition. @@ -1711,6 +1769,7 @@ impl AudioTrackFeature { AudioTrackFeature::TfEchoCancellation => "TF_ECHO_CANCELLATION", AudioTrackFeature::TfNoiseSuppression => "TF_NOISE_SUPPRESSION", AudioTrackFeature::TfEnhancedNoiseCancellation => "TF_ENHANCED_NOISE_CANCELLATION", + AudioTrackFeature::TfPreconnectBuffer => "TF_PRECONNECT_BUFFER", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1722,6 +1781,7 @@ impl AudioTrackFeature { "TF_ECHO_CANCELLATION" => Some(Self::TfEchoCancellation), "TF_NOISE_SUPPRESSION" => Some(Self::TfNoiseSuppression), "TF_ENHANCED_NOISE_CANCELLATION" => Some(Self::TfEnhancedNoiseCancellation), + "TF_PRECONNECT_BUFFER" => Some(Self::TfPreconnectBuffer), _ => None, } } @@ -1756,6 +1816,9 @@ pub struct RoomCompositeEgressRequest { pub segment_outputs: ::prost::alloc::vec::Vec, #[prost(message, repeated, tag="14")] pub image_outputs: ::prost::alloc::vec::Vec, + /// extra webhooks to call for this request + #[prost(message, repeated, tag="16")] + pub webhooks: ::prost::alloc::vec::Vec, /// deprecated (use _output fields) #[prost(oneof="room_composite_egress_request::Output", tags="6, 7, 10")] pub output: ::core::option::Option, @@ -1806,6 +1869,9 @@ pub struct WebEgressRequest { pub segment_outputs: ::prost::alloc::vec::Vec, #[prost(message, repeated, tag="13")] pub image_outputs: ::prost::alloc::vec::Vec, + /// extra webhooks to call for this request + #[prost(message, repeated, tag="14")] + pub webhooks: ::prost::alloc::vec::Vec, /// deprecated (use _output fields) #[prost(oneof="web_egress_request::Output", tags="4, 5, 6")] pub output: ::core::option::Option, @@ -1855,6 +1921,9 @@ pub struct ParticipantEgressRequest { pub segment_outputs: ::prost::alloc::vec::Vec, #[prost(message, repeated, tag="9")] pub image_outputs: ::prost::alloc::vec::Vec, + /// extra webhooks to call for this request + #[prost(message, repeated, tag="10")] + pub webhooks: ::prost::alloc::vec::Vec, #[prost(oneof="participant_egress_request::Options", tags="4, 5")] pub options: ::core::option::Option, } @@ -1892,6 +1961,9 @@ pub struct TrackCompositeEgressRequest { pub segment_outputs: ::prost::alloc::vec::Vec, #[prost(message, repeated, tag="14")] pub image_outputs: ::prost::alloc::vec::Vec, + /// extra webhooks to call for this request + #[prost(message, repeated, tag="15")] + pub webhooks: ::prost::alloc::vec::Vec, /// deprecated (use _output fields) #[prost(oneof="track_composite_egress_request::Output", tags="4, 5, 8")] pub output: ::core::option::Option, @@ -1932,6 +2004,9 @@ pub struct TrackEgressRequest { /// required #[prost(string, tag="2")] pub track_id: ::prost::alloc::string::String, + /// extra webhooks to call for this request + #[prost(message, repeated, tag="5")] + pub webhooks: ::prost::alloc::vec::Vec, /// required #[prost(oneof="track_egress_request::Output", tags="3, 4")] pub output: ::core::option::Option, @@ -2572,6 +2647,8 @@ impl SegmentedFileSuffix { pub enum ImageFileSuffix { ImageSuffixIndex = 0, ImageSuffixTimestamp = 1, + /// Do not append any suffix and overwrite the existing image with the latest + ImageSuffixNoneOverwrite = 2, } impl ImageFileSuffix { /// String value of the enum field names used in the ProtoBuf definition. @@ -2582,6 +2659,7 @@ impl ImageFileSuffix { match self { ImageFileSuffix::ImageSuffixIndex => "IMAGE_SUFFIX_INDEX", ImageFileSuffix::ImageSuffixTimestamp => "IMAGE_SUFFIX_TIMESTAMP", + ImageFileSuffix::ImageSuffixNoneOverwrite => "IMAGE_SUFFIX_NONE_OVERWRITE", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -2589,6 +2667,7 @@ impl ImageFileSuffix { match value { "IMAGE_SUFFIX_INDEX" => Some(Self::ImageSuffixIndex), "IMAGE_SUFFIX_TIMESTAMP" => Some(Self::ImageSuffixTimestamp), + "IMAGE_SUFFIX_NONE_OVERWRITE" => Some(Self::ImageSuffixNoneOverwrite), _ => None, } } @@ -2947,6 +3026,9 @@ pub struct AddTrackRequest { #[prost(bool, tag="6")] pub muted: bool, /// true if DTX (Discontinuous Transmission) is disabled for audio + /// + /// deprecated in favor of audio_features + #[deprecated] #[prost(bool, tag="7")] pub disable_dtx: bool, #[prost(enumeration="TrackSource", tag="8")] @@ -2958,6 +3040,8 @@ pub struct AddTrackRequest { /// server ID of track, publish new codec to exist track #[prost(string, tag="11")] pub sid: ::prost::alloc::string::String, + /// deprecated in favor of audio_features + #[deprecated] #[prost(bool, tag="12")] pub stereo: bool, /// true if RED (Redundant Encoding) is disabled for audio @@ -2971,6 +3055,8 @@ pub struct AddTrackRequest { pub stream: ::prost::alloc::string::String, #[prost(enumeration="BackupCodecPolicy", tag="16")] pub backup_codec_policy: i32, + #[prost(enumeration="AudioTrackFeature", repeated, tag="17")] + pub audio_features: ::prost::alloc::vec::Vec, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -4174,6 +4260,23 @@ pub struct RoomConfiguration { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct ForwardParticipantRequest { + /// room to forward participant from + #[prost(string, tag="1")] + pub room: ::prost::alloc::string::String, + /// identity of the participant to forward + #[prost(string, tag="2")] + pub identity: ::prost::alloc::string::String, + /// room to forward participant to + #[prost(string, tag="3")] + pub destination_room: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ForwardParticipantResponse { +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct CreateIngressRequest { #[prost(enumeration="IngressInput", tag="1")] pub input_type: i32, @@ -4608,6 +4711,7 @@ pub struct WebhookEvent { /// timestamp in seconds #[prost(int64, tag="7")] pub created_at: i64, + #[deprecated] #[prost(int32, tag="11")] pub num_dropped: i32, } @@ -4744,6 +4848,25 @@ pub struct CreateSipInboundTrunkRequest { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct UpdateSipInboundTrunkRequest { + #[prost(string, tag="1")] + pub sip_trunk_id: ::prost::alloc::string::String, + #[prost(oneof="update_sip_inbound_trunk_request::Action", tags="2, 3")] + pub action: ::core::option::Option, +} +/// Nested message and enum types in `UpdateSIPInboundTrunkRequest`. +pub mod update_sip_inbound_trunk_request { + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Action { + #[prost(message, tag="2")] + Replace(super::SipInboundTrunkInfo), + #[prost(message, tag="3")] + Update(super::SipInboundTrunkUpdate), + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct SipInboundTrunkInfo { #[prost(string, tag="1")] pub sip_trunk_id: ::prost::alloc::string::String, @@ -4802,6 +4925,26 @@ pub struct SipInboundTrunkInfo { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct SipInboundTrunkUpdate { + #[prost(message, optional, tag="1")] + pub numbers: ::core::option::Option, + #[prost(message, optional, tag="2")] + pub allowed_addresses: ::core::option::Option, + #[prost(message, optional, tag="3")] + pub allowed_numbers: ::core::option::Option, + #[prost(string, optional, tag="4")] + pub auth_username: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="5")] + pub auth_password: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="6")] + pub name: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="7")] + pub metadata: ::core::option::Option<::prost::alloc::string::String>, + #[prost(enumeration="SipMediaEncryption", optional, tag="8")] + pub media_encryption: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct CreateSipOutboundTrunkRequest { /// Trunk ID is ignored #[prost(message, optional, tag="1")] @@ -4809,6 +4952,25 @@ pub struct CreateSipOutboundTrunkRequest { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct UpdateSipOutboundTrunkRequest { + #[prost(string, tag="1")] + pub sip_trunk_id: ::prost::alloc::string::String, + #[prost(oneof="update_sip_outbound_trunk_request::Action", tags="2, 3")] + pub action: ::core::option::Option, +} +/// Nested message and enum types in `UpdateSIPOutboundTrunkRequest`. +pub mod update_sip_outbound_trunk_request { + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Action { + #[prost(message, tag="2")] + Replace(super::SipOutboundTrunkInfo), + #[prost(message, tag="3")] + Update(super::SipOutboundTrunkUpdate), + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct SipOutboundTrunkInfo { #[prost(string, tag="1")] pub sip_trunk_id: ::prost::alloc::string::String, @@ -4859,6 +5021,26 @@ pub struct SipOutboundTrunkInfo { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct SipOutboundTrunkUpdate { + #[prost(string, optional, tag="1")] + pub address: ::core::option::Option<::prost::alloc::string::String>, + #[prost(enumeration="SipTransport", optional, tag="2")] + pub transport: ::core::option::Option, + #[prost(message, optional, tag="3")] + pub numbers: ::core::option::Option, + #[prost(string, optional, tag="4")] + pub auth_username: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="5")] + pub auth_password: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="6")] + pub name: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="7")] + pub metadata: ::core::option::Option<::prost::alloc::string::String>, + #[prost(enumeration="SipMediaEncryption", optional, tag="8")] + pub media_encryption: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct GetSipInboundTrunkRequest { #[prost(string, tag="1")] pub sip_trunk_id: ::prost::alloc::string::String, @@ -4999,24 +5181,33 @@ pub mod sip_dispatch_rule { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CreateSipDispatchRuleRequest { + /// Rule ID is ignored + #[prost(message, optional, tag="10")] + pub dispatch_rule: ::core::option::Option, + #[deprecated] #[prost(message, optional, tag="1")] pub rule: ::core::option::Option, /// What trunks are accepted for this dispatch rule /// If empty all trunks will match this dispatch rule + #[deprecated] #[prost(string, repeated, tag="2")] pub trunk_ids: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, /// By default the From value (Phone number) is used for participant name/identity and added to attributes. /// If true, a random value for identity will be used and numbers will be omitted from attributes. + #[deprecated] #[prost(bool, tag="3")] pub hide_phone_number: bool, /// Dispatch Rule will only accept a call made to these numbers (if set). + #[deprecated] #[prost(string, repeated, tag="6")] pub inbound_numbers: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, /// Optional human-readable name for the Dispatch Rule. + #[deprecated] #[prost(string, tag="4")] pub name: ::prost::alloc::string::String, /// User-defined metadata for the Dispatch Rule. /// Participants created by this rule will inherit this metadata. + #[deprecated] #[prost(string, tag="5")] pub metadata: ::prost::alloc::string::String, /// User-defined attributes for the Dispatch Rule. @@ -5024,14 +5215,35 @@ pub struct CreateSipDispatchRuleRequest { #[prost(map="string, string", tag="7")] pub attributes: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, /// Cloud-only, config preset to use + #[deprecated] #[prost(string, tag="8")] pub room_preset: ::prost::alloc::string::String, /// RoomConfiguration to use if the participant initiates the room + #[deprecated] #[prost(message, optional, tag="9")] pub room_config: ::core::option::Option, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct UpdateSipDispatchRuleRequest { + #[prost(string, tag="1")] + pub sip_dispatch_rule_id: ::prost::alloc::string::String, + #[prost(oneof="update_sip_dispatch_rule_request::Action", tags="2, 3")] + pub action: ::core::option::Option, +} +/// Nested message and enum types in `UpdateSIPDispatchRuleRequest`. +pub mod update_sip_dispatch_rule_request { + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Action { + #[prost(message, tag="2")] + Replace(super::SipDispatchRuleInfo), + #[prost(message, tag="3")] + Update(super::SipDispatchRuleUpdate), + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct SipDispatchRuleInfo { #[prost(string, tag="1")] pub sip_dispatch_rule_id: ::prost::alloc::string::String, @@ -5067,6 +5279,22 @@ pub struct SipDispatchRuleInfo { #[prost(enumeration="SipMediaEncryption", tag="12")] pub media_encryption: i32, } +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SipDispatchRuleUpdate { + #[prost(message, optional, tag="1")] + pub trunk_ids: ::core::option::Option, + #[prost(message, optional, tag="2")] + pub rule: ::core::option::Option, + #[prost(string, optional, tag="3")] + pub name: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="4")] + pub metadata: ::core::option::Option<::prost::alloc::string::String>, + #[prost(map="string, string", tag="5")] + pub attributes: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + #[prost(enumeration="SipMediaEncryption", optional, tag="6")] + pub media_encryption: ::core::option::Option, +} /// ListSIPDispatchRuleRequest lists dispatch rules for given filters. If no filters are set, all rules are listed. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -5217,6 +5445,9 @@ pub struct TransferSipParticipantRequest { /// Add the following headers to the REFER SIP request. #[prost(map="string, string", tag="5")] pub headers: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + /// Max time for the transfer destination to answer the call. + #[prost(message, optional, tag="6")] + pub ringing_timeout: ::core::option::Option<::pbjson_types::Duration>, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] diff --git a/livekit-protocol/src/livekit.serde.rs b/livekit-protocol/src/livekit.serde.rs index 817bccf24..4cd41062f 100644 --- a/livekit-protocol/src/livekit.serde.rs +++ b/livekit-protocol/src/livekit.serde.rs @@ -150,6 +150,9 @@ impl serde::Serialize for AddTrackRequest { if self.backup_codec_policy != 0 { len += 1; } + if !self.audio_features.is_empty() { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.AddTrackRequest", len)?; if !self.cid.is_empty() { struct_ser.serialize_field("cid", &self.cid)?; @@ -207,6 +210,13 @@ impl serde::Serialize for AddTrackRequest { .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.backup_codec_policy)))?; struct_ser.serialize_field("backupCodecPolicy", &v)?; } + if !self.audio_features.is_empty() { + let v = self.audio_features.iter().cloned().map(|v| { + AudioTrackFeature::try_from(v) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", v))) + }).collect::, _>>()?; + struct_ser.serialize_field("audioFeatures", &v)?; + } struct_ser.end() } } @@ -237,6 +247,8 @@ impl<'de> serde::Deserialize<'de> for AddTrackRequest { "stream", "backup_codec_policy", "backupCodecPolicy", + "audio_features", + "audioFeatures", ]; #[allow(clippy::enum_variant_names)] @@ -257,6 +269,7 @@ impl<'de> serde::Deserialize<'de> for AddTrackRequest { Encryption, Stream, BackupCodecPolicy, + AudioFeatures, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -295,6 +308,7 @@ impl<'de> serde::Deserialize<'de> for AddTrackRequest { "encryption" => Ok(GeneratedField::Encryption), "stream" => Ok(GeneratedField::Stream), "backupCodecPolicy" | "backup_codec_policy" => Ok(GeneratedField::BackupCodecPolicy), + "audioFeatures" | "audio_features" => Ok(GeneratedField::AudioFeatures), _ => Ok(GeneratedField::__SkipField__), } } @@ -330,6 +344,7 @@ impl<'de> serde::Deserialize<'de> for AddTrackRequest { let mut encryption__ = None; let mut stream__ = None; let mut backup_codec_policy__ = None; + let mut audio_features__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::Cid => { @@ -432,6 +447,12 @@ impl<'de> serde::Deserialize<'de> for AddTrackRequest { } backup_codec_policy__ = Some(map_.next_value::()? as i32); } + GeneratedField::AudioFeatures => { + if audio_features__.is_some() { + return Err(serde::de::Error::duplicate_field("audioFeatures")); + } + audio_features__ = Some(map_.next_value::>()?.into_iter().map(|x| x as i32).collect()); + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -454,6 +475,7 @@ impl<'de> serde::Deserialize<'de> for AddTrackRequest { encryption: encryption__.unwrap_or_default(), stream: stream__.unwrap_or_default(), backup_codec_policy: backup_codec_policy__.unwrap_or_default(), + audio_features: audio_features__.unwrap_or_default(), }) } } @@ -1088,6 +1110,7 @@ impl serde::Serialize for AudioTrackFeature { Self::TfEchoCancellation => "TF_ECHO_CANCELLATION", Self::TfNoiseSuppression => "TF_NOISE_SUPPRESSION", Self::TfEnhancedNoiseCancellation => "TF_ENHANCED_NOISE_CANCELLATION", + Self::TfPreconnectBuffer => "TF_PRECONNECT_BUFFER", }; serializer.serialize_str(variant) } @@ -1105,6 +1128,7 @@ impl<'de> serde::Deserialize<'de> for AudioTrackFeature { "TF_ECHO_CANCELLATION", "TF_NOISE_SUPPRESSION", "TF_ENHANCED_NOISE_CANCELLATION", + "TF_PRECONNECT_BUFFER", ]; struct GeneratedVisitor; @@ -1151,6 +1175,7 @@ impl<'de> serde::Deserialize<'de> for AudioTrackFeature { "TF_ECHO_CANCELLATION" => Ok(AudioTrackFeature::TfEchoCancellation), "TF_NOISE_SUPPRESSION" => Ok(AudioTrackFeature::TfNoiseSuppression), "TF_ENHANCED_NOISE_CANCELLATION" => Ok(AudioTrackFeature::TfEnhancedNoiseCancellation), + "TF_PRECONNECT_BUFFER" => Ok(AudioTrackFeature::TfPreconnectBuffer), _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), } } @@ -1938,8 +1963,9 @@ impl serde::Serialize for BackupCodecPolicy { S: serde::Serializer, { let variant = match self { - Self::Regression => "REGRESSION", + Self::PreferRegression => "PREFER_REGRESSION", Self::Simulcast => "SIMULCAST", + Self::Regression => "REGRESSION", }; serializer.serialize_str(variant) } @@ -1951,8 +1977,9 @@ impl<'de> serde::Deserialize<'de> for BackupCodecPolicy { D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "REGRESSION", + "PREFER_REGRESSION", "SIMULCAST", + "REGRESSION", ]; struct GeneratedVisitor; @@ -1993,8 +2020,9 @@ impl<'de> serde::Deserialize<'de> for BackupCodecPolicy { E: serde::de::Error, { match value { - "REGRESSION" => Ok(BackupCodecPolicy::Regression), + "PREFER_REGRESSION" => Ok(BackupCodecPolicy::PreferRegression), "SIMULCAST" => Ok(BackupCodecPolicy::Simulcast), + "REGRESSION" => Ok(BackupCodecPolicy::Regression), _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), } } @@ -2802,6 +2830,7 @@ impl serde::Serialize for client_info::Sdk { Self::Cpp => "CPP", Self::UnityWeb => "UNITY_WEB", Self::Node => "NODE", + Self::Unreal => "UNREAL", }; serializer.serialize_str(variant) } @@ -2826,6 +2855,7 @@ impl<'de> serde::Deserialize<'de> for client_info::Sdk { "CPP", "UNITY_WEB", "NODE", + "UNREAL", ]; struct GeneratedVisitor; @@ -2879,6 +2909,7 @@ impl<'de> serde::Deserialize<'de> for client_info::Sdk { "CPP" => Ok(client_info::Sdk::Cpp), "UNITY_WEB" => Ok(client_info::Sdk::UnityWeb), "NODE" => Ok(client_info::Sdk::Node), + "UNREAL" => Ok(client_info::Sdk::Unreal), _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), } } @@ -4052,6 +4083,9 @@ impl serde::Serialize for CreateSipDispatchRuleRequest { { use serde::ser::SerializeStruct; let mut len = 0; + if self.dispatch_rule.is_some() { + len += 1; + } if self.rule.is_some() { len += 1; } @@ -4080,6 +4114,9 @@ impl serde::Serialize for CreateSipDispatchRuleRequest { len += 1; } let mut struct_ser = serializer.serialize_struct("livekit.CreateSIPDispatchRuleRequest", len)?; + if let Some(v) = self.dispatch_rule.as_ref() { + struct_ser.serialize_field("dispatchRule", v)?; + } if let Some(v) = self.rule.as_ref() { struct_ser.serialize_field("rule", v)?; } @@ -4117,6 +4154,8 @@ impl<'de> serde::Deserialize<'de> for CreateSipDispatchRuleRequest { D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ + "dispatch_rule", + "dispatchRule", "rule", "trunk_ids", "trunkIds", @@ -4135,6 +4174,7 @@ impl<'de> serde::Deserialize<'de> for CreateSipDispatchRuleRequest { #[allow(clippy::enum_variant_names)] enum GeneratedField { + DispatchRule, Rule, TrunkIds, HidePhoneNumber, @@ -4166,6 +4206,7 @@ impl<'de> serde::Deserialize<'de> for CreateSipDispatchRuleRequest { E: serde::de::Error, { match value { + "dispatchRule" | "dispatch_rule" => Ok(GeneratedField::DispatchRule), "rule" => Ok(GeneratedField::Rule), "trunkIds" | "trunk_ids" => Ok(GeneratedField::TrunkIds), "hidePhoneNumber" | "hide_phone_number" => Ok(GeneratedField::HidePhoneNumber), @@ -4194,6 +4235,7 @@ impl<'de> serde::Deserialize<'de> for CreateSipDispatchRuleRequest { where V: serde::de::MapAccess<'de>, { + let mut dispatch_rule__ = None; let mut rule__ = None; let mut trunk_ids__ = None; let mut hide_phone_number__ = None; @@ -4205,6 +4247,12 @@ impl<'de> serde::Deserialize<'de> for CreateSipDispatchRuleRequest { let mut room_config__ = None; while let Some(k) = map_.next_key()? { match k { + GeneratedField::DispatchRule => { + if dispatch_rule__.is_some() { + return Err(serde::de::Error::duplicate_field("dispatchRule")); + } + dispatch_rule__ = map_.next_value()?; + } GeneratedField::Rule => { if rule__.is_some() { return Err(serde::de::Error::duplicate_field("rule")); @@ -4267,6 +4315,7 @@ impl<'de> serde::Deserialize<'de> for CreateSipDispatchRuleRequest { } } Ok(CreateSipDispatchRuleRequest { + dispatch_rule: dispatch_rule__, rule: rule__, trunk_ids: trunk_ids__.unwrap_or_default(), hide_phone_number: hide_phone_number__.unwrap_or_default(), @@ -7549,6 +7598,7 @@ impl serde::Serialize for DisconnectReason { Self::UserUnavailable => "USER_UNAVAILABLE", Self::UserRejected => "USER_REJECTED", Self::SipTrunkFailure => "SIP_TRUNK_FAILURE", + Self::ConnectionTimeout => "CONNECTION_TIMEOUT", }; serializer.serialize_str(variant) } @@ -7574,6 +7624,7 @@ impl<'de> serde::Deserialize<'de> for DisconnectReason { "USER_UNAVAILABLE", "USER_REJECTED", "SIP_TRUNK_FAILURE", + "CONNECTION_TIMEOUT", ]; struct GeneratedVisitor; @@ -7628,6 +7679,7 @@ impl<'de> serde::Deserialize<'de> for DisconnectReason { "USER_UNAVAILABLE" => Ok(DisconnectReason::UserUnavailable), "USER_REJECTED" => Ok(DisconnectReason::UserRejected), "SIP_TRUNK_FAILURE" => Ok(DisconnectReason::SipTrunkFailure), + "CONNECTION_TIMEOUT" => Ok(DisconnectReason::ConnectionTimeout), _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), } } @@ -9583,7 +9635,7 @@ impl<'de> serde::Deserialize<'de> for FileInfo { deserializer.deserialize_struct("livekit.FileInfo", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for GcpUpload { +impl serde::Serialize for ForwardParticipantRequest { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -9591,45 +9643,46 @@ impl serde::Serialize for GcpUpload { { use serde::ser::SerializeStruct; let mut len = 0; - if !self.credentials.is_empty() { + if !self.room.is_empty() { len += 1; } - if !self.bucket.is_empty() { + if !self.identity.is_empty() { len += 1; } - if self.proxy.is_some() { + if !self.destination_room.is_empty() { len += 1; } - let mut struct_ser = serializer.serialize_struct("livekit.GCPUpload", len)?; - if !self.credentials.is_empty() { - struct_ser.serialize_field("credentials", &self.credentials)?; + let mut struct_ser = serializer.serialize_struct("livekit.ForwardParticipantRequest", len)?; + if !self.room.is_empty() { + struct_ser.serialize_field("room", &self.room)?; } - if !self.bucket.is_empty() { - struct_ser.serialize_field("bucket", &self.bucket)?; + if !self.identity.is_empty() { + struct_ser.serialize_field("identity", &self.identity)?; } - if let Some(v) = self.proxy.as_ref() { - struct_ser.serialize_field("proxy", v)?; + if !self.destination_room.is_empty() { + struct_ser.serialize_field("destinationRoom", &self.destination_room)?; } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for GcpUpload { +impl<'de> serde::Deserialize<'de> for ForwardParticipantRequest { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "credentials", - "bucket", - "proxy", + "room", + "identity", + "destination_room", + "destinationRoom", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { - Credentials, - Bucket, - Proxy, + Room, + Identity, + DestinationRoom, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -9652,9 +9705,9 @@ impl<'de> serde::Deserialize<'de> for GcpUpload { E: serde::de::Error, { match value { - "credentials" => Ok(GeneratedField::Credentials), - "bucket" => Ok(GeneratedField::Bucket), - "proxy" => Ok(GeneratedField::Proxy), + "room" => Ok(GeneratedField::Room), + "identity" => Ok(GeneratedField::Identity), + "destinationRoom" | "destination_room" => Ok(GeneratedField::DestinationRoom), _ => Ok(GeneratedField::__SkipField__), } } @@ -9664,55 +9717,127 @@ impl<'de> serde::Deserialize<'de> for GcpUpload { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = GcpUpload; + type Value = ForwardParticipantRequest; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.GCPUpload") + formatter.write_str("struct livekit.ForwardParticipantRequest") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut credentials__ = None; - let mut bucket__ = None; - let mut proxy__ = None; + let mut room__ = None; + let mut identity__ = None; + let mut destination_room__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::Credentials => { - if credentials__.is_some() { - return Err(serde::de::Error::duplicate_field("credentials")); + GeneratedField::Room => { + if room__.is_some() { + return Err(serde::de::Error::duplicate_field("room")); } - credentials__ = Some(map_.next_value()?); + room__ = Some(map_.next_value()?); } - GeneratedField::Bucket => { - if bucket__.is_some() { - return Err(serde::de::Error::duplicate_field("bucket")); + GeneratedField::Identity => { + if identity__.is_some() { + return Err(serde::de::Error::duplicate_field("identity")); } - bucket__ = Some(map_.next_value()?); + identity__ = Some(map_.next_value()?); } - GeneratedField::Proxy => { - if proxy__.is_some() { - return Err(serde::de::Error::duplicate_field("proxy")); + GeneratedField::DestinationRoom => { + if destination_room__.is_some() { + return Err(serde::de::Error::duplicate_field("destinationRoom")); } - proxy__ = map_.next_value()?; + destination_room__ = Some(map_.next_value()?); } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } } } - Ok(GcpUpload { - credentials: credentials__.unwrap_or_default(), - bucket: bucket__.unwrap_or_default(), - proxy: proxy__, + Ok(ForwardParticipantRequest { + room: room__.unwrap_or_default(), + identity: identity__.unwrap_or_default(), + destination_room: destination_room__.unwrap_or_default(), }) } } - deserializer.deserialize_struct("livekit.GCPUpload", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.ForwardParticipantRequest", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for GetSipInboundTrunkRequest { +impl serde::Serialize for ForwardParticipantResponse { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let len = 0; + let struct_ser = serializer.serialize_struct("livekit.ForwardParticipantResponse", len)?; + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for ForwardParticipantResponse { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + Ok(GeneratedField::__SkipField__) + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = ForwardParticipantResponse; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.ForwardParticipantResponse") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + while map_.next_key::()?.is_some() { + let _ = map_.next_value::()?; + } + Ok(ForwardParticipantResponse { + }) + } + } + deserializer.deserialize_struct("livekit.ForwardParticipantResponse", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for GcpUpload { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -9720,30 +9845,45 @@ impl serde::Serialize for GetSipInboundTrunkRequest { { use serde::ser::SerializeStruct; let mut len = 0; - if !self.sip_trunk_id.is_empty() { + if !self.credentials.is_empty() { len += 1; } - let mut struct_ser = serializer.serialize_struct("livekit.GetSIPInboundTrunkRequest", len)?; - if !self.sip_trunk_id.is_empty() { - struct_ser.serialize_field("sipTrunkId", &self.sip_trunk_id)?; + if !self.bucket.is_empty() { + len += 1; + } + if self.proxy.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.GCPUpload", len)?; + if !self.credentials.is_empty() { + struct_ser.serialize_field("credentials", &self.credentials)?; + } + if !self.bucket.is_empty() { + struct_ser.serialize_field("bucket", &self.bucket)?; + } + if let Some(v) = self.proxy.as_ref() { + struct_ser.serialize_field("proxy", v)?; } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for GetSipInboundTrunkRequest { +impl<'de> serde::Deserialize<'de> for GcpUpload { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "sip_trunk_id", - "sipTrunkId", + "credentials", + "bucket", + "proxy", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { - SipTrunkId, + Credentials, + Bucket, + Proxy, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -9766,7 +9906,9 @@ impl<'de> serde::Deserialize<'de> for GetSipInboundTrunkRequest { E: serde::de::Error, { match value { - "sipTrunkId" | "sip_trunk_id" => Ok(GeneratedField::SipTrunkId), + "credentials" => Ok(GeneratedField::Credentials), + "bucket" => Ok(GeneratedField::Bucket), + "proxy" => Ok(GeneratedField::Proxy), _ => Ok(GeneratedField::__SkipField__), } } @@ -9776,39 +9918,55 @@ impl<'de> serde::Deserialize<'de> for GetSipInboundTrunkRequest { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = GetSipInboundTrunkRequest; + type Value = GcpUpload; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.GetSIPInboundTrunkRequest") + formatter.write_str("struct livekit.GCPUpload") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut sip_trunk_id__ = None; + let mut credentials__ = None; + let mut bucket__ = None; + let mut proxy__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::SipTrunkId => { - if sip_trunk_id__.is_some() { - return Err(serde::de::Error::duplicate_field("sipTrunkId")); + GeneratedField::Credentials => { + if credentials__.is_some() { + return Err(serde::de::Error::duplicate_field("credentials")); } - sip_trunk_id__ = Some(map_.next_value()?); + credentials__ = Some(map_.next_value()?); + } + GeneratedField::Bucket => { + if bucket__.is_some() { + return Err(serde::de::Error::duplicate_field("bucket")); + } + bucket__ = Some(map_.next_value()?); + } + GeneratedField::Proxy => { + if proxy__.is_some() { + return Err(serde::de::Error::duplicate_field("proxy")); + } + proxy__ = map_.next_value()?; } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } } } - Ok(GetSipInboundTrunkRequest { - sip_trunk_id: sip_trunk_id__.unwrap_or_default(), + Ok(GcpUpload { + credentials: credentials__.unwrap_or_default(), + bucket: bucket__.unwrap_or_default(), + proxy: proxy__, }) } } - deserializer.deserialize_struct("livekit.GetSIPInboundTrunkRequest", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.GCPUpload", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for GetSipInboundTrunkResponse { +impl serde::Serialize for GetSipInboundTrunkRequest { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -9816,29 +9974,125 @@ impl serde::Serialize for GetSipInboundTrunkResponse { { use serde::ser::SerializeStruct; let mut len = 0; - if self.trunk.is_some() { + if !self.sip_trunk_id.is_empty() { len += 1; } - let mut struct_ser = serializer.serialize_struct("livekit.GetSIPInboundTrunkResponse", len)?; - if let Some(v) = self.trunk.as_ref() { - struct_ser.serialize_field("trunk", v)?; + let mut struct_ser = serializer.serialize_struct("livekit.GetSIPInboundTrunkRequest", len)?; + if !self.sip_trunk_id.is_empty() { + struct_ser.serialize_field("sipTrunkId", &self.sip_trunk_id)?; } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for GetSipInboundTrunkResponse { +impl<'de> serde::Deserialize<'de> for GetSipInboundTrunkRequest { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "trunk", + "sip_trunk_id", + "sipTrunkId", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { - Trunk, + SipTrunkId, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "sipTrunkId" | "sip_trunk_id" => Ok(GeneratedField::SipTrunkId), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GetSipInboundTrunkRequest; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.GetSIPInboundTrunkRequest") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut sip_trunk_id__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::SipTrunkId => { + if sip_trunk_id__.is_some() { + return Err(serde::de::Error::duplicate_field("sipTrunkId")); + } + sip_trunk_id__ = Some(map_.next_value()?); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(GetSipInboundTrunkRequest { + sip_trunk_id: sip_trunk_id__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.GetSIPInboundTrunkRequest", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for GetSipInboundTrunkResponse { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.trunk.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.GetSIPInboundTrunkResponse", len)?; + if let Some(v) = self.trunk.as_ref() { + struct_ser.serialize_field("trunk", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for GetSipInboundTrunkResponse { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "trunk", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Trunk, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -10303,6 +10557,7 @@ impl serde::Serialize for ImageFileSuffix { let variant = match self { Self::ImageSuffixIndex => "IMAGE_SUFFIX_INDEX", Self::ImageSuffixTimestamp => "IMAGE_SUFFIX_TIMESTAMP", + Self::ImageSuffixNoneOverwrite => "IMAGE_SUFFIX_NONE_OVERWRITE", }; serializer.serialize_str(variant) } @@ -10316,6 +10571,7 @@ impl<'de> serde::Deserialize<'de> for ImageFileSuffix { const FIELDS: &[&str] = &[ "IMAGE_SUFFIX_INDEX", "IMAGE_SUFFIX_TIMESTAMP", + "IMAGE_SUFFIX_NONE_OVERWRITE", ]; struct GeneratedVisitor; @@ -10358,6 +10614,7 @@ impl<'de> serde::Deserialize<'de> for ImageFileSuffix { match value { "IMAGE_SUFFIX_INDEX" => Ok(ImageFileSuffix::ImageSuffixIndex), "IMAGE_SUFFIX_TIMESTAMP" => Ok(ImageFileSuffix::ImageSuffixTimestamp), + "IMAGE_SUFFIX_NONE_OVERWRITE" => Ok(ImageFileSuffix::ImageSuffixNoneOverwrite), _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), } } @@ -15943,6 +16200,101 @@ impl<'de> serde::Deserialize<'de> for ListSipTrunkResponse { deserializer.deserialize_struct("livekit.ListSIPTrunkResponse", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for ListUpdate { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.set.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.ListUpdate", len)?; + if !self.set.is_empty() { + struct_ser.serialize_field("set", &self.set)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for ListUpdate { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "set", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Set, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "set" => Ok(GeneratedField::Set), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = ListUpdate; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.ListUpdate") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut set__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Set => { + if set__.is_some() { + return Err(serde::de::Error::duplicate_field("set")); + } + set__ = Some(map_.next_value()?); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(ListUpdate { + set: set__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.ListUpdate", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for MetricLabel { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -16973,6 +17325,9 @@ impl serde::Serialize for ParticipantEgressRequest { if !self.image_outputs.is_empty() { len += 1; } + if !self.webhooks.is_empty() { + len += 1; + } if self.options.is_some() { len += 1; } @@ -16998,6 +17353,9 @@ impl serde::Serialize for ParticipantEgressRequest { if !self.image_outputs.is_empty() { struct_ser.serialize_field("imageOutputs", &self.image_outputs)?; } + if !self.webhooks.is_empty() { + struct_ser.serialize_field("webhooks", &self.webhooks)?; + } if let Some(v) = self.options.as_ref() { match v { participant_egress_request::Options::Preset(v) => { @@ -17033,6 +17391,7 @@ impl<'de> serde::Deserialize<'de> for ParticipantEgressRequest { "segmentOutputs", "image_outputs", "imageOutputs", + "webhooks", "preset", "advanced", ]; @@ -17046,6 +17405,7 @@ impl<'de> serde::Deserialize<'de> for ParticipantEgressRequest { StreamOutputs, SegmentOutputs, ImageOutputs, + Webhooks, Preset, Advanced, __SkipField__, @@ -17077,6 +17437,7 @@ impl<'de> serde::Deserialize<'de> for ParticipantEgressRequest { "streamOutputs" | "stream_outputs" => Ok(GeneratedField::StreamOutputs), "segmentOutputs" | "segment_outputs" => Ok(GeneratedField::SegmentOutputs), "imageOutputs" | "image_outputs" => Ok(GeneratedField::ImageOutputs), + "webhooks" => Ok(GeneratedField::Webhooks), "preset" => Ok(GeneratedField::Preset), "advanced" => Ok(GeneratedField::Advanced), _ => Ok(GeneratedField::__SkipField__), @@ -17105,6 +17466,7 @@ impl<'de> serde::Deserialize<'de> for ParticipantEgressRequest { let mut stream_outputs__ = None; let mut segment_outputs__ = None; let mut image_outputs__ = None; + let mut webhooks__ = None; let mut options__ = None; while let Some(k) = map_.next_key()? { match k { @@ -17150,6 +17512,12 @@ impl<'de> serde::Deserialize<'de> for ParticipantEgressRequest { } image_outputs__ = Some(map_.next_value()?); } + GeneratedField::Webhooks => { + if webhooks__.is_some() { + return Err(serde::de::Error::duplicate_field("webhooks")); + } + webhooks__ = Some(map_.next_value()?); + } GeneratedField::Preset => { if options__.is_some() { return Err(serde::de::Error::duplicate_field("preset")); @@ -17176,6 +17544,7 @@ impl<'de> serde::Deserialize<'de> for ParticipantEgressRequest { stream_outputs: stream_outputs__.unwrap_or_default(), segment_outputs: segment_outputs__.unwrap_or_default(), image_outputs: image_outputs__.unwrap_or_default(), + webhooks: webhooks__.unwrap_or_default(), options: options__, }) } @@ -17236,6 +17605,9 @@ impl serde::Serialize for ParticipantInfo { if self.disconnect_reason != 0 { len += 1; } + if !self.kind_details.is_empty() { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.ParticipantInfo", len)?; if !self.sid.is_empty() { struct_ser.serialize_field("sid", &self.sid)?; @@ -17292,6 +17664,13 @@ impl serde::Serialize for ParticipantInfo { .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.disconnect_reason)))?; struct_ser.serialize_field("disconnectReason", &v)?; } + if !self.kind_details.is_empty() { + let v = self.kind_details.iter().cloned().map(|v| { + participant_info::KindDetail::try_from(v) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", v))) + }).collect::, _>>()?; + struct_ser.serialize_field("kindDetails", &v)?; + } struct_ser.end() } } @@ -17321,6 +17700,8 @@ impl<'de> serde::Deserialize<'de> for ParticipantInfo { "attributes", "disconnect_reason", "disconnectReason", + "kind_details", + "kindDetails", ]; #[allow(clippy::enum_variant_names)] @@ -17340,6 +17721,7 @@ impl<'de> serde::Deserialize<'de> for ParticipantInfo { Kind, Attributes, DisconnectReason, + KindDetails, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -17377,6 +17759,7 @@ impl<'de> serde::Deserialize<'de> for ParticipantInfo { "kind" => Ok(GeneratedField::Kind), "attributes" => Ok(GeneratedField::Attributes), "disconnectReason" | "disconnect_reason" => Ok(GeneratedField::DisconnectReason), + "kindDetails" | "kind_details" => Ok(GeneratedField::KindDetails), _ => Ok(GeneratedField::__SkipField__), } } @@ -17411,6 +17794,7 @@ impl<'de> serde::Deserialize<'de> for ParticipantInfo { let mut kind__ = None; let mut attributes__ = None; let mut disconnect_reason__ = None; + let mut kind_details__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::Sid => { @@ -17511,6 +17895,12 @@ impl<'de> serde::Deserialize<'de> for ParticipantInfo { } disconnect_reason__ = Some(map_.next_value::()? as i32); } + GeneratedField::KindDetails => { + if kind_details__.is_some() { + return Err(serde::de::Error::duplicate_field("kindDetails")); + } + kind_details__ = Some(map_.next_value::>()?.into_iter().map(|x| x as i32).collect()); + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -17532,6 +17922,7 @@ impl<'de> serde::Deserialize<'de> for ParticipantInfo { kind: kind__.unwrap_or_default(), attributes: attributes__.unwrap_or_default(), disconnect_reason: disconnect_reason__.unwrap_or_default(), + kind_details: kind_details__.unwrap_or_default(), }) } } @@ -17618,6 +18009,77 @@ impl<'de> serde::Deserialize<'de> for participant_info::Kind { deserializer.deserialize_any(GeneratedVisitor) } } +impl serde::Serialize for participant_info::KindDetail { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + let variant = match self { + Self::CloudAgent => "CLOUD_AGENT", + Self::Forwarded => "FORWARDED", + }; + serializer.serialize_str(variant) + } +} +impl<'de> serde::Deserialize<'de> for participant_info::KindDetail { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "CLOUD_AGENT", + "FORWARDED", + ]; + + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = participant_info::KindDetail; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + fn visit_i64(self, v: i64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self) + }) + } + + fn visit_u64(self, v: u64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(v), &self) + }) + } + + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "CLOUD_AGENT" => Ok(participant_info::KindDetail::CloudAgent), + "FORWARDED" => Ok(participant_info::KindDetail::Forwarded), + _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), + } + } + } + deserializer.deserialize_any(GeneratedVisitor) + } +} impl serde::Serialize for participant_info::State { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -22135,6 +22597,9 @@ impl serde::Serialize for RoomCompositeEgressRequest { if !self.image_outputs.is_empty() { len += 1; } + if !self.webhooks.is_empty() { + len += 1; + } if self.output.is_some() { len += 1; } @@ -22174,6 +22639,9 @@ impl serde::Serialize for RoomCompositeEgressRequest { if !self.image_outputs.is_empty() { struct_ser.serialize_field("imageOutputs", &self.image_outputs)?; } + if !self.webhooks.is_empty() { + struct_ser.serialize_field("webhooks", &self.webhooks)?; + } if let Some(v) = self.output.as_ref() { match v { room_composite_egress_request::Output::File(v) => { @@ -22228,6 +22696,7 @@ impl<'de> serde::Deserialize<'de> for RoomCompositeEgressRequest { "segmentOutputs", "image_outputs", "imageOutputs", + "webhooks", "file", "stream", "segments", @@ -22247,6 +22716,7 @@ impl<'de> serde::Deserialize<'de> for RoomCompositeEgressRequest { StreamOutputs, SegmentOutputs, ImageOutputs, + Webhooks, File, Stream, Segments, @@ -22284,6 +22754,7 @@ impl<'de> serde::Deserialize<'de> for RoomCompositeEgressRequest { "streamOutputs" | "stream_outputs" => Ok(GeneratedField::StreamOutputs), "segmentOutputs" | "segment_outputs" => Ok(GeneratedField::SegmentOutputs), "imageOutputs" | "image_outputs" => Ok(GeneratedField::ImageOutputs), + "webhooks" => Ok(GeneratedField::Webhooks), "file" => Ok(GeneratedField::File), "stream" => Ok(GeneratedField::Stream), "segments" => Ok(GeneratedField::Segments), @@ -22318,6 +22789,7 @@ impl<'de> serde::Deserialize<'de> for RoomCompositeEgressRequest { let mut stream_outputs__ = None; let mut segment_outputs__ = None; let mut image_outputs__ = None; + let mut webhooks__ = None; let mut output__ = None; let mut options__ = None; while let Some(k) = map_.next_key()? { @@ -22382,6 +22854,12 @@ impl<'de> serde::Deserialize<'de> for RoomCompositeEgressRequest { } image_outputs__ = Some(map_.next_value()?); } + GeneratedField::Webhooks => { + if webhooks__.is_some() { + return Err(serde::de::Error::duplicate_field("webhooks")); + } + webhooks__ = Some(map_.next_value()?); + } GeneratedField::File => { if output__.is_some() { return Err(serde::de::Error::duplicate_field("file")); @@ -22432,6 +22910,7 @@ impl<'de> serde::Deserialize<'de> for RoomCompositeEgressRequest { stream_outputs: stream_outputs__.unwrap_or_default(), segment_outputs: segment_outputs__.unwrap_or_default(), image_outputs: image_outputs__.unwrap_or_default(), + webhooks: webhooks__.unwrap_or_default(), output: output__, options: options__, }) @@ -25296,6 +25775,192 @@ impl<'de> serde::Deserialize<'de> for SipDispatchRuleInfo { deserializer.deserialize_struct("livekit.SIPDispatchRuleInfo", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for SipDispatchRuleUpdate { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.trunk_ids.is_some() { + len += 1; + } + if self.rule.is_some() { + len += 1; + } + if self.name.is_some() { + len += 1; + } + if self.metadata.is_some() { + len += 1; + } + if !self.attributes.is_empty() { + len += 1; + } + if self.media_encryption.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.SIPDispatchRuleUpdate", len)?; + if let Some(v) = self.trunk_ids.as_ref() { + struct_ser.serialize_field("trunkIds", v)?; + } + if let Some(v) = self.rule.as_ref() { + struct_ser.serialize_field("rule", v)?; + } + if let Some(v) = self.name.as_ref() { + struct_ser.serialize_field("name", v)?; + } + if let Some(v) = self.metadata.as_ref() { + struct_ser.serialize_field("metadata", v)?; + } + if !self.attributes.is_empty() { + struct_ser.serialize_field("attributes", &self.attributes)?; + } + if let Some(v) = self.media_encryption.as_ref() { + let v = SipMediaEncryption::try_from(*v) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", *v)))?; + struct_ser.serialize_field("mediaEncryption", &v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for SipDispatchRuleUpdate { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "trunk_ids", + "trunkIds", + "rule", + "name", + "metadata", + "attributes", + "media_encryption", + "mediaEncryption", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + TrunkIds, + Rule, + Name, + Metadata, + Attributes, + MediaEncryption, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "trunkIds" | "trunk_ids" => Ok(GeneratedField::TrunkIds), + "rule" => Ok(GeneratedField::Rule), + "name" => Ok(GeneratedField::Name), + "metadata" => Ok(GeneratedField::Metadata), + "attributes" => Ok(GeneratedField::Attributes), + "mediaEncryption" | "media_encryption" => Ok(GeneratedField::MediaEncryption), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = SipDispatchRuleUpdate; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.SIPDispatchRuleUpdate") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut trunk_ids__ = None; + let mut rule__ = None; + let mut name__ = None; + let mut metadata__ = None; + let mut attributes__ = None; + let mut media_encryption__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::TrunkIds => { + if trunk_ids__.is_some() { + return Err(serde::de::Error::duplicate_field("trunkIds")); + } + trunk_ids__ = map_.next_value()?; + } + GeneratedField::Rule => { + if rule__.is_some() { + return Err(serde::de::Error::duplicate_field("rule")); + } + rule__ = map_.next_value()?; + } + GeneratedField::Name => { + if name__.is_some() { + return Err(serde::de::Error::duplicate_field("name")); + } + name__ = map_.next_value()?; + } + GeneratedField::Metadata => { + if metadata__.is_some() { + return Err(serde::de::Error::duplicate_field("metadata")); + } + metadata__ = map_.next_value()?; + } + GeneratedField::Attributes => { + if attributes__.is_some() { + return Err(serde::de::Error::duplicate_field("attributes")); + } + attributes__ = Some( + map_.next_value::>()? + ); + } + GeneratedField::MediaEncryption => { + if media_encryption__.is_some() { + return Err(serde::de::Error::duplicate_field("mediaEncryption")); + } + media_encryption__ = map_.next_value::<::std::option::Option>()?.map(|x| x as i32); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(SipDispatchRuleUpdate { + trunk_ids: trunk_ids__, + rule: rule__, + name: name__, + metadata: metadata__, + attributes: attributes__.unwrap_or_default(), + media_encryption: media_encryption__, + }) + } + } + deserializer.deserialize_struct("livekit.SIPDispatchRuleUpdate", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for SipFeature { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -25813,6 +26478,227 @@ impl<'de> serde::Deserialize<'de> for SipInboundTrunkInfo { deserializer.deserialize_struct("livekit.SIPInboundTrunkInfo", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for SipInboundTrunkUpdate { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.numbers.is_some() { + len += 1; + } + if self.allowed_addresses.is_some() { + len += 1; + } + if self.allowed_numbers.is_some() { + len += 1; + } + if self.auth_username.is_some() { + len += 1; + } + if self.auth_password.is_some() { + len += 1; + } + if self.name.is_some() { + len += 1; + } + if self.metadata.is_some() { + len += 1; + } + if self.media_encryption.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.SIPInboundTrunkUpdate", len)?; + if let Some(v) = self.numbers.as_ref() { + struct_ser.serialize_field("numbers", v)?; + } + if let Some(v) = self.allowed_addresses.as_ref() { + struct_ser.serialize_field("allowedAddresses", v)?; + } + if let Some(v) = self.allowed_numbers.as_ref() { + struct_ser.serialize_field("allowedNumbers", v)?; + } + if let Some(v) = self.auth_username.as_ref() { + struct_ser.serialize_field("authUsername", v)?; + } + if let Some(v) = self.auth_password.as_ref() { + struct_ser.serialize_field("authPassword", v)?; + } + if let Some(v) = self.name.as_ref() { + struct_ser.serialize_field("name", v)?; + } + if let Some(v) = self.metadata.as_ref() { + struct_ser.serialize_field("metadata", v)?; + } + if let Some(v) = self.media_encryption.as_ref() { + let v = SipMediaEncryption::try_from(*v) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", *v)))?; + struct_ser.serialize_field("mediaEncryption", &v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for SipInboundTrunkUpdate { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "numbers", + "allowed_addresses", + "allowedAddresses", + "allowed_numbers", + "allowedNumbers", + "auth_username", + "authUsername", + "auth_password", + "authPassword", + "name", + "metadata", + "media_encryption", + "mediaEncryption", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Numbers, + AllowedAddresses, + AllowedNumbers, + AuthUsername, + AuthPassword, + Name, + Metadata, + MediaEncryption, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "numbers" => Ok(GeneratedField::Numbers), + "allowedAddresses" | "allowed_addresses" => Ok(GeneratedField::AllowedAddresses), + "allowedNumbers" | "allowed_numbers" => Ok(GeneratedField::AllowedNumbers), + "authUsername" | "auth_username" => Ok(GeneratedField::AuthUsername), + "authPassword" | "auth_password" => Ok(GeneratedField::AuthPassword), + "name" => Ok(GeneratedField::Name), + "metadata" => Ok(GeneratedField::Metadata), + "mediaEncryption" | "media_encryption" => Ok(GeneratedField::MediaEncryption), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = SipInboundTrunkUpdate; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.SIPInboundTrunkUpdate") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut numbers__ = None; + let mut allowed_addresses__ = None; + let mut allowed_numbers__ = None; + let mut auth_username__ = None; + let mut auth_password__ = None; + let mut name__ = None; + let mut metadata__ = None; + let mut media_encryption__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Numbers => { + if numbers__.is_some() { + return Err(serde::de::Error::duplicate_field("numbers")); + } + numbers__ = map_.next_value()?; + } + GeneratedField::AllowedAddresses => { + if allowed_addresses__.is_some() { + return Err(serde::de::Error::duplicate_field("allowedAddresses")); + } + allowed_addresses__ = map_.next_value()?; + } + GeneratedField::AllowedNumbers => { + if allowed_numbers__.is_some() { + return Err(serde::de::Error::duplicate_field("allowedNumbers")); + } + allowed_numbers__ = map_.next_value()?; + } + GeneratedField::AuthUsername => { + if auth_username__.is_some() { + return Err(serde::de::Error::duplicate_field("authUsername")); + } + auth_username__ = map_.next_value()?; + } + GeneratedField::AuthPassword => { + if auth_password__.is_some() { + return Err(serde::de::Error::duplicate_field("authPassword")); + } + auth_password__ = map_.next_value()?; + } + GeneratedField::Name => { + if name__.is_some() { + return Err(serde::de::Error::duplicate_field("name")); + } + name__ = map_.next_value()?; + } + GeneratedField::Metadata => { + if metadata__.is_some() { + return Err(serde::de::Error::duplicate_field("metadata")); + } + metadata__ = map_.next_value()?; + } + GeneratedField::MediaEncryption => { + if media_encryption__.is_some() { + return Err(serde::de::Error::duplicate_field("mediaEncryption")); + } + media_encryption__ = map_.next_value::<::std::option::Option>()?.map(|x| x as i32); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(SipInboundTrunkUpdate { + numbers: numbers__, + allowed_addresses: allowed_addresses__, + allowed_numbers: allowed_numbers__, + auth_username: auth_username__, + auth_password: auth_password__, + name: name__, + metadata: metadata__, + media_encryption: media_encryption__, + }) + } + } + deserializer.deserialize_struct("livekit.SIPInboundTrunkUpdate", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for SipMediaEncryption { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -26395,6 +27281,227 @@ impl<'de> serde::Deserialize<'de> for SipOutboundTrunkInfo { deserializer.deserialize_struct("livekit.SIPOutboundTrunkInfo", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for SipOutboundTrunkUpdate { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.address.is_some() { + len += 1; + } + if self.transport.is_some() { + len += 1; + } + if self.numbers.is_some() { + len += 1; + } + if self.auth_username.is_some() { + len += 1; + } + if self.auth_password.is_some() { + len += 1; + } + if self.name.is_some() { + len += 1; + } + if self.metadata.is_some() { + len += 1; + } + if self.media_encryption.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.SIPOutboundTrunkUpdate", len)?; + if let Some(v) = self.address.as_ref() { + struct_ser.serialize_field("address", v)?; + } + if let Some(v) = self.transport.as_ref() { + let v = SipTransport::try_from(*v) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", *v)))?; + struct_ser.serialize_field("transport", &v)?; + } + if let Some(v) = self.numbers.as_ref() { + struct_ser.serialize_field("numbers", v)?; + } + if let Some(v) = self.auth_username.as_ref() { + struct_ser.serialize_field("authUsername", v)?; + } + if let Some(v) = self.auth_password.as_ref() { + struct_ser.serialize_field("authPassword", v)?; + } + if let Some(v) = self.name.as_ref() { + struct_ser.serialize_field("name", v)?; + } + if let Some(v) = self.metadata.as_ref() { + struct_ser.serialize_field("metadata", v)?; + } + if let Some(v) = self.media_encryption.as_ref() { + let v = SipMediaEncryption::try_from(*v) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", *v)))?; + struct_ser.serialize_field("mediaEncryption", &v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for SipOutboundTrunkUpdate { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "address", + "transport", + "numbers", + "auth_username", + "authUsername", + "auth_password", + "authPassword", + "name", + "metadata", + "media_encryption", + "mediaEncryption", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Address, + Transport, + Numbers, + AuthUsername, + AuthPassword, + Name, + Metadata, + MediaEncryption, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "address" => Ok(GeneratedField::Address), + "transport" => Ok(GeneratedField::Transport), + "numbers" => Ok(GeneratedField::Numbers), + "authUsername" | "auth_username" => Ok(GeneratedField::AuthUsername), + "authPassword" | "auth_password" => Ok(GeneratedField::AuthPassword), + "name" => Ok(GeneratedField::Name), + "metadata" => Ok(GeneratedField::Metadata), + "mediaEncryption" | "media_encryption" => Ok(GeneratedField::MediaEncryption), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = SipOutboundTrunkUpdate; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.SIPOutboundTrunkUpdate") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut address__ = None; + let mut transport__ = None; + let mut numbers__ = None; + let mut auth_username__ = None; + let mut auth_password__ = None; + let mut name__ = None; + let mut metadata__ = None; + let mut media_encryption__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Address => { + if address__.is_some() { + return Err(serde::de::Error::duplicate_field("address")); + } + address__ = map_.next_value()?; + } + GeneratedField::Transport => { + if transport__.is_some() { + return Err(serde::de::Error::duplicate_field("transport")); + } + transport__ = map_.next_value::<::std::option::Option>()?.map(|x| x as i32); + } + GeneratedField::Numbers => { + if numbers__.is_some() { + return Err(serde::de::Error::duplicate_field("numbers")); + } + numbers__ = map_.next_value()?; + } + GeneratedField::AuthUsername => { + if auth_username__.is_some() { + return Err(serde::de::Error::duplicate_field("authUsername")); + } + auth_username__ = map_.next_value()?; + } + GeneratedField::AuthPassword => { + if auth_password__.is_some() { + return Err(serde::de::Error::duplicate_field("authPassword")); + } + auth_password__ = map_.next_value()?; + } + GeneratedField::Name => { + if name__.is_some() { + return Err(serde::de::Error::duplicate_field("name")); + } + name__ = map_.next_value()?; + } + GeneratedField::Metadata => { + if metadata__.is_some() { + return Err(serde::de::Error::duplicate_field("metadata")); + } + metadata__ = map_.next_value()?; + } + GeneratedField::MediaEncryption => { + if media_encryption__.is_some() { + return Err(serde::de::Error::duplicate_field("mediaEncryption")); + } + media_encryption__ = map_.next_value::<::std::option::Option>()?.map(|x| x as i32); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(SipOutboundTrunkUpdate { + address: address__, + transport: transport__, + numbers: numbers__, + auth_username: auth_username__, + auth_password: auth_password__, + name: name__, + metadata: metadata__, + media_encryption: media_encryption__, + }) + } + } + deserializer.deserialize_struct("livekit.SIPOutboundTrunkUpdate", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for SipParticipantInfo { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -32968,6 +34075,9 @@ impl serde::Serialize for TrackCompositeEgressRequest { if !self.image_outputs.is_empty() { len += 1; } + if !self.webhooks.is_empty() { + len += 1; + } if self.output.is_some() { len += 1; } @@ -32996,6 +34106,9 @@ impl serde::Serialize for TrackCompositeEgressRequest { if !self.image_outputs.is_empty() { struct_ser.serialize_field("imageOutputs", &self.image_outputs)?; } + if !self.webhooks.is_empty() { + struct_ser.serialize_field("webhooks", &self.webhooks)?; + } if let Some(v) = self.output.as_ref() { match v { track_composite_egress_request::Output::File(v) => { @@ -33045,6 +34158,7 @@ impl<'de> serde::Deserialize<'de> for TrackCompositeEgressRequest { "segmentOutputs", "image_outputs", "imageOutputs", + "webhooks", "file", "stream", "segments", @@ -33061,6 +34175,7 @@ impl<'de> serde::Deserialize<'de> for TrackCompositeEgressRequest { StreamOutputs, SegmentOutputs, ImageOutputs, + Webhooks, File, Stream, Segments, @@ -33095,6 +34210,7 @@ impl<'de> serde::Deserialize<'de> for TrackCompositeEgressRequest { "streamOutputs" | "stream_outputs" => Ok(GeneratedField::StreamOutputs), "segmentOutputs" | "segment_outputs" => Ok(GeneratedField::SegmentOutputs), "imageOutputs" | "image_outputs" => Ok(GeneratedField::ImageOutputs), + "webhooks" => Ok(GeneratedField::Webhooks), "file" => Ok(GeneratedField::File), "stream" => Ok(GeneratedField::Stream), "segments" => Ok(GeneratedField::Segments), @@ -33126,6 +34242,7 @@ impl<'de> serde::Deserialize<'de> for TrackCompositeEgressRequest { let mut stream_outputs__ = None; let mut segment_outputs__ = None; let mut image_outputs__ = None; + let mut webhooks__ = None; let mut output__ = None; let mut options__ = None; while let Some(k) = map_.next_key()? { @@ -33172,6 +34289,12 @@ impl<'de> serde::Deserialize<'de> for TrackCompositeEgressRequest { } image_outputs__ = Some(map_.next_value()?); } + GeneratedField::Webhooks => { + if webhooks__.is_some() { + return Err(serde::de::Error::duplicate_field("webhooks")); + } + webhooks__ = Some(map_.next_value()?); + } GeneratedField::File => { if output__.is_some() { return Err(serde::de::Error::duplicate_field("file")); @@ -33219,6 +34342,7 @@ impl<'de> serde::Deserialize<'de> for TrackCompositeEgressRequest { stream_outputs: stream_outputs__.unwrap_or_default(), segment_outputs: segment_outputs__.unwrap_or_default(), image_outputs: image_outputs__.unwrap_or_default(), + webhooks: webhooks__.unwrap_or_default(), output: output__, options: options__, }) @@ -33241,6 +34365,9 @@ impl serde::Serialize for TrackEgressRequest { if !self.track_id.is_empty() { len += 1; } + if !self.webhooks.is_empty() { + len += 1; + } if self.output.is_some() { len += 1; } @@ -33251,6 +34378,9 @@ impl serde::Serialize for TrackEgressRequest { if !self.track_id.is_empty() { struct_ser.serialize_field("trackId", &self.track_id)?; } + if !self.webhooks.is_empty() { + struct_ser.serialize_field("webhooks", &self.webhooks)?; + } if let Some(v) = self.output.as_ref() { match v { track_egress_request::Output::File(v) => { @@ -33275,6 +34405,7 @@ impl<'de> serde::Deserialize<'de> for TrackEgressRequest { "roomName", "track_id", "trackId", + "webhooks", "file", "websocket_url", "websocketUrl", @@ -33284,6 +34415,7 @@ impl<'de> serde::Deserialize<'de> for TrackEgressRequest { enum GeneratedField { RoomName, TrackId, + Webhooks, File, WebsocketUrl, __SkipField__, @@ -33310,6 +34442,7 @@ impl<'de> serde::Deserialize<'de> for TrackEgressRequest { match value { "roomName" | "room_name" => Ok(GeneratedField::RoomName), "trackId" | "track_id" => Ok(GeneratedField::TrackId), + "webhooks" => Ok(GeneratedField::Webhooks), "file" => Ok(GeneratedField::File), "websocketUrl" | "websocket_url" => Ok(GeneratedField::WebsocketUrl), _ => Ok(GeneratedField::__SkipField__), @@ -33333,6 +34466,7 @@ impl<'de> serde::Deserialize<'de> for TrackEgressRequest { { let mut room_name__ = None; let mut track_id__ = None; + let mut webhooks__ = None; let mut output__ = None; while let Some(k) = map_.next_key()? { match k { @@ -33348,6 +34482,12 @@ impl<'de> serde::Deserialize<'de> for TrackEgressRequest { } track_id__ = Some(map_.next_value()?); } + GeneratedField::Webhooks => { + if webhooks__.is_some() { + return Err(serde::de::Error::duplicate_field("webhooks")); + } + webhooks__ = Some(map_.next_value()?); + } GeneratedField::File => { if output__.is_some() { return Err(serde::de::Error::duplicate_field("file")); @@ -33369,6 +34509,7 @@ impl<'de> serde::Deserialize<'de> for TrackEgressRequest { Ok(TrackEgressRequest { room_name: room_name__.unwrap_or_default(), track_id: track_id__.unwrap_or_default(), + webhooks: webhooks__.unwrap_or_default(), output: output__, }) } @@ -34767,6 +35908,9 @@ impl serde::Serialize for TransferSipParticipantRequest { if !self.headers.is_empty() { len += 1; } + if self.ringing_timeout.is_some() { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.TransferSIPParticipantRequest", len)?; if !self.participant_identity.is_empty() { struct_ser.serialize_field("participantIdentity", &self.participant_identity)?; @@ -34783,6 +35927,9 @@ impl serde::Serialize for TransferSipParticipantRequest { if !self.headers.is_empty() { struct_ser.serialize_field("headers", &self.headers)?; } + if let Some(v) = self.ringing_timeout.as_ref() { + struct_ser.serialize_field("ringingTimeout", v)?; + } struct_ser.end() } } @@ -34802,6 +35949,8 @@ impl<'de> serde::Deserialize<'de> for TransferSipParticipantRequest { "play_dialtone", "playDialtone", "headers", + "ringing_timeout", + "ringingTimeout", ]; #[allow(clippy::enum_variant_names)] @@ -34811,6 +35960,7 @@ impl<'de> serde::Deserialize<'de> for TransferSipParticipantRequest { TransferTo, PlayDialtone, Headers, + RingingTimeout, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -34838,6 +35988,7 @@ impl<'de> serde::Deserialize<'de> for TransferSipParticipantRequest { "transferTo" | "transfer_to" => Ok(GeneratedField::TransferTo), "playDialtone" | "play_dialtone" => Ok(GeneratedField::PlayDialtone), "headers" => Ok(GeneratedField::Headers), + "ringingTimeout" | "ringing_timeout" => Ok(GeneratedField::RingingTimeout), _ => Ok(GeneratedField::__SkipField__), } } @@ -34862,6 +36013,7 @@ impl<'de> serde::Deserialize<'de> for TransferSipParticipantRequest { let mut transfer_to__ = None; let mut play_dialtone__ = None; let mut headers__ = None; + let mut ringing_timeout__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::ParticipantIdentity => { @@ -34896,6 +36048,12 @@ impl<'de> serde::Deserialize<'de> for TransferSipParticipantRequest { map_.next_value::>()? ); } + GeneratedField::RingingTimeout => { + if ringing_timeout__.is_some() { + return Err(serde::de::Error::duplicate_field("ringingTimeout")); + } + ringing_timeout__ = map_.next_value()?; + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -34907,6 +36065,7 @@ impl<'de> serde::Deserialize<'de> for TransferSipParticipantRequest { transfer_to: transfer_to__.unwrap_or_default(), play_dialtone: play_dialtone__.unwrap_or_default(), headers: headers__.unwrap_or_default(), + ringing_timeout: ringing_timeout__, }) } } @@ -36056,12 +37215,543 @@ impl<'de> serde::Deserialize<'de> for UpdateParticipantRequest { E: serde::de::Error, { match value { - "room" => Ok(GeneratedField::Room), - "identity" => Ok(GeneratedField::Identity), - "metadata" => Ok(GeneratedField::Metadata), - "permission" => Ok(GeneratedField::Permission), - "name" => Ok(GeneratedField::Name), - "attributes" => Ok(GeneratedField::Attributes), + "room" => Ok(GeneratedField::Room), + "identity" => Ok(GeneratedField::Identity), + "metadata" => Ok(GeneratedField::Metadata), + "permission" => Ok(GeneratedField::Permission), + "name" => Ok(GeneratedField::Name), + "attributes" => Ok(GeneratedField::Attributes), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = UpdateParticipantRequest; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.UpdateParticipantRequest") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut room__ = None; + let mut identity__ = None; + let mut metadata__ = None; + let mut permission__ = None; + let mut name__ = None; + let mut attributes__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Room => { + if room__.is_some() { + return Err(serde::de::Error::duplicate_field("room")); + } + room__ = Some(map_.next_value()?); + } + GeneratedField::Identity => { + if identity__.is_some() { + return Err(serde::de::Error::duplicate_field("identity")); + } + identity__ = Some(map_.next_value()?); + } + GeneratedField::Metadata => { + if metadata__.is_some() { + return Err(serde::de::Error::duplicate_field("metadata")); + } + metadata__ = Some(map_.next_value()?); + } + GeneratedField::Permission => { + if permission__.is_some() { + return Err(serde::de::Error::duplicate_field("permission")); + } + permission__ = map_.next_value()?; + } + GeneratedField::Name => { + if name__.is_some() { + return Err(serde::de::Error::duplicate_field("name")); + } + name__ = Some(map_.next_value()?); + } + GeneratedField::Attributes => { + if attributes__.is_some() { + return Err(serde::de::Error::duplicate_field("attributes")); + } + attributes__ = Some( + map_.next_value::>()? + ); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(UpdateParticipantRequest { + room: room__.unwrap_or_default(), + identity: identity__.unwrap_or_default(), + metadata: metadata__.unwrap_or_default(), + permission: permission__, + name: name__.unwrap_or_default(), + attributes: attributes__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.UpdateParticipantRequest", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for UpdateRoomMetadataRequest { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.room.is_empty() { + len += 1; + } + if !self.metadata.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.UpdateRoomMetadataRequest", len)?; + if !self.room.is_empty() { + struct_ser.serialize_field("room", &self.room)?; + } + if !self.metadata.is_empty() { + struct_ser.serialize_field("metadata", &self.metadata)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for UpdateRoomMetadataRequest { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "room", + "metadata", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Room, + Metadata, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "room" => Ok(GeneratedField::Room), + "metadata" => Ok(GeneratedField::Metadata), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = UpdateRoomMetadataRequest; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.UpdateRoomMetadataRequest") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut room__ = None; + let mut metadata__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Room => { + if room__.is_some() { + return Err(serde::de::Error::duplicate_field("room")); + } + room__ = Some(map_.next_value()?); + } + GeneratedField::Metadata => { + if metadata__.is_some() { + return Err(serde::de::Error::duplicate_field("metadata")); + } + metadata__ = Some(map_.next_value()?); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(UpdateRoomMetadataRequest { + room: room__.unwrap_or_default(), + metadata: metadata__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.UpdateRoomMetadataRequest", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for UpdateSipDispatchRuleRequest { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.sip_dispatch_rule_id.is_empty() { + len += 1; + } + if self.action.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.UpdateSIPDispatchRuleRequest", len)?; + if !self.sip_dispatch_rule_id.is_empty() { + struct_ser.serialize_field("sipDispatchRuleId", &self.sip_dispatch_rule_id)?; + } + if let Some(v) = self.action.as_ref() { + match v { + update_sip_dispatch_rule_request::Action::Replace(v) => { + struct_ser.serialize_field("replace", v)?; + } + update_sip_dispatch_rule_request::Action::Update(v) => { + struct_ser.serialize_field("update", v)?; + } + } + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for UpdateSipDispatchRuleRequest { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "sip_dispatch_rule_id", + "sipDispatchRuleId", + "replace", + "update", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + SipDispatchRuleId, + Replace, + Update, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "sipDispatchRuleId" | "sip_dispatch_rule_id" => Ok(GeneratedField::SipDispatchRuleId), + "replace" => Ok(GeneratedField::Replace), + "update" => Ok(GeneratedField::Update), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = UpdateSipDispatchRuleRequest; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.UpdateSIPDispatchRuleRequest") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut sip_dispatch_rule_id__ = None; + let mut action__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::SipDispatchRuleId => { + if sip_dispatch_rule_id__.is_some() { + return Err(serde::de::Error::duplicate_field("sipDispatchRuleId")); + } + sip_dispatch_rule_id__ = Some(map_.next_value()?); + } + GeneratedField::Replace => { + if action__.is_some() { + return Err(serde::de::Error::duplicate_field("replace")); + } + action__ = map_.next_value::<::std::option::Option<_>>()?.map(update_sip_dispatch_rule_request::Action::Replace) +; + } + GeneratedField::Update => { + if action__.is_some() { + return Err(serde::de::Error::duplicate_field("update")); + } + action__ = map_.next_value::<::std::option::Option<_>>()?.map(update_sip_dispatch_rule_request::Action::Update) +; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(UpdateSipDispatchRuleRequest { + sip_dispatch_rule_id: sip_dispatch_rule_id__.unwrap_or_default(), + action: action__, + }) + } + } + deserializer.deserialize_struct("livekit.UpdateSIPDispatchRuleRequest", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for UpdateSipInboundTrunkRequest { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.sip_trunk_id.is_empty() { + len += 1; + } + if self.action.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.UpdateSIPInboundTrunkRequest", len)?; + if !self.sip_trunk_id.is_empty() { + struct_ser.serialize_field("sipTrunkId", &self.sip_trunk_id)?; + } + if let Some(v) = self.action.as_ref() { + match v { + update_sip_inbound_trunk_request::Action::Replace(v) => { + struct_ser.serialize_field("replace", v)?; + } + update_sip_inbound_trunk_request::Action::Update(v) => { + struct_ser.serialize_field("update", v)?; + } + } + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for UpdateSipInboundTrunkRequest { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "sip_trunk_id", + "sipTrunkId", + "replace", + "update", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + SipTrunkId, + Replace, + Update, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "sipTrunkId" | "sip_trunk_id" => Ok(GeneratedField::SipTrunkId), + "replace" => Ok(GeneratedField::Replace), + "update" => Ok(GeneratedField::Update), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = UpdateSipInboundTrunkRequest; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.UpdateSIPInboundTrunkRequest") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut sip_trunk_id__ = None; + let mut action__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::SipTrunkId => { + if sip_trunk_id__.is_some() { + return Err(serde::de::Error::duplicate_field("sipTrunkId")); + } + sip_trunk_id__ = Some(map_.next_value()?); + } + GeneratedField::Replace => { + if action__.is_some() { + return Err(serde::de::Error::duplicate_field("replace")); + } + action__ = map_.next_value::<::std::option::Option<_>>()?.map(update_sip_inbound_trunk_request::Action::Replace) +; + } + GeneratedField::Update => { + if action__.is_some() { + return Err(serde::de::Error::duplicate_field("update")); + } + action__ = map_.next_value::<::std::option::Option<_>>()?.map(update_sip_inbound_trunk_request::Action::Update) +; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(UpdateSipInboundTrunkRequest { + sip_trunk_id: sip_trunk_id__.unwrap_or_default(), + action: action__, + }) + } + } + deserializer.deserialize_struct("livekit.UpdateSIPInboundTrunkRequest", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for UpdateSipOutboundTrunkRequest { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.sip_trunk_id.is_empty() { + len += 1; + } + if self.action.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.UpdateSIPOutboundTrunkRequest", len)?; + if !self.sip_trunk_id.is_empty() { + struct_ser.serialize_field("sipTrunkId", &self.sip_trunk_id)?; + } + if let Some(v) = self.action.as_ref() { + match v { + update_sip_outbound_trunk_request::Action::Replace(v) => { + struct_ser.serialize_field("replace", v)?; + } + update_sip_outbound_trunk_request::Action::Update(v) => { + struct_ser.serialize_field("update", v)?; + } + } + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for UpdateSipOutboundTrunkRequest { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "sip_trunk_id", + "sipTrunkId", + "replace", + "update", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + SipTrunkId, + Replace, + Update, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "sipTrunkId" | "sip_trunk_id" => Ok(GeneratedField::SipTrunkId), + "replace" => Ok(GeneratedField::Replace), + "update" => Ok(GeneratedField::Update), _ => Ok(GeneratedField::__SkipField__), } } @@ -36071,190 +37761,52 @@ impl<'de> serde::Deserialize<'de> for UpdateParticipantRequest { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = UpdateParticipantRequest; + type Value = UpdateSipOutboundTrunkRequest; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.UpdateParticipantRequest") + formatter.write_str("struct livekit.UpdateSIPOutboundTrunkRequest") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut room__ = None; - let mut identity__ = None; - let mut metadata__ = None; - let mut permission__ = None; - let mut name__ = None; - let mut attributes__ = None; + let mut sip_trunk_id__ = None; + let mut action__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::Room => { - if room__.is_some() { - return Err(serde::de::Error::duplicate_field("room")); - } - room__ = Some(map_.next_value()?); - } - GeneratedField::Identity => { - if identity__.is_some() { - return Err(serde::de::Error::duplicate_field("identity")); - } - identity__ = Some(map_.next_value()?); - } - GeneratedField::Metadata => { - if metadata__.is_some() { - return Err(serde::de::Error::duplicate_field("metadata")); - } - metadata__ = Some(map_.next_value()?); - } - GeneratedField::Permission => { - if permission__.is_some() { - return Err(serde::de::Error::duplicate_field("permission")); - } - permission__ = map_.next_value()?; - } - GeneratedField::Name => { - if name__.is_some() { - return Err(serde::de::Error::duplicate_field("name")); - } - name__ = Some(map_.next_value()?); - } - GeneratedField::Attributes => { - if attributes__.is_some() { - return Err(serde::de::Error::duplicate_field("attributes")); + GeneratedField::SipTrunkId => { + if sip_trunk_id__.is_some() { + return Err(serde::de::Error::duplicate_field("sipTrunkId")); } - attributes__ = Some( - map_.next_value::>()? - ); - } - GeneratedField::__SkipField__ => { - let _ = map_.next_value::()?; - } - } - } - Ok(UpdateParticipantRequest { - room: room__.unwrap_or_default(), - identity: identity__.unwrap_or_default(), - metadata: metadata__.unwrap_or_default(), - permission: permission__, - name: name__.unwrap_or_default(), - attributes: attributes__.unwrap_or_default(), - }) - } - } - deserializer.deserialize_struct("livekit.UpdateParticipantRequest", FIELDS, GeneratedVisitor) - } -} -impl serde::Serialize for UpdateRoomMetadataRequest { - #[allow(deprecated)] - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::Serializer, - { - use serde::ser::SerializeStruct; - let mut len = 0; - if !self.room.is_empty() { - len += 1; - } - if !self.metadata.is_empty() { - len += 1; - } - let mut struct_ser = serializer.serialize_struct("livekit.UpdateRoomMetadataRequest", len)?; - if !self.room.is_empty() { - struct_ser.serialize_field("room", &self.room)?; - } - if !self.metadata.is_empty() { - struct_ser.serialize_field("metadata", &self.metadata)?; - } - struct_ser.end() - } -} -impl<'de> serde::Deserialize<'de> for UpdateRoomMetadataRequest { - #[allow(deprecated)] - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - const FIELDS: &[&str] = &[ - "room", - "metadata", - ]; - - #[allow(clippy::enum_variant_names)] - enum GeneratedField { - Room, - Metadata, - __SkipField__, - } - impl<'de> serde::Deserialize<'de> for GeneratedField { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - struct GeneratedVisitor; - - impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = GeneratedField; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(formatter, "expected one of: {:?}", &FIELDS) - } - - #[allow(unused_variables)] - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - match value { - "room" => Ok(GeneratedField::Room), - "metadata" => Ok(GeneratedField::Metadata), - _ => Ok(GeneratedField::__SkipField__), + sip_trunk_id__ = Some(map_.next_value()?); } - } - } - deserializer.deserialize_identifier(GeneratedVisitor) - } - } - struct GeneratedVisitor; - impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = UpdateRoomMetadataRequest; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.UpdateRoomMetadataRequest") - } - - fn visit_map(self, mut map_: V) -> std::result::Result - where - V: serde::de::MapAccess<'de>, - { - let mut room__ = None; - let mut metadata__ = None; - while let Some(k) = map_.next_key()? { - match k { - GeneratedField::Room => { - if room__.is_some() { - return Err(serde::de::Error::duplicate_field("room")); + GeneratedField::Replace => { + if action__.is_some() { + return Err(serde::de::Error::duplicate_field("replace")); } - room__ = Some(map_.next_value()?); + action__ = map_.next_value::<::std::option::Option<_>>()?.map(update_sip_outbound_trunk_request::Action::Replace) +; } - GeneratedField::Metadata => { - if metadata__.is_some() { - return Err(serde::de::Error::duplicate_field("metadata")); + GeneratedField::Update => { + if action__.is_some() { + return Err(serde::de::Error::duplicate_field("update")); } - metadata__ = Some(map_.next_value()?); + action__ = map_.next_value::<::std::option::Option<_>>()?.map(update_sip_outbound_trunk_request::Action::Update) +; } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } } } - Ok(UpdateRoomMetadataRequest { - room: room__.unwrap_or_default(), - metadata: metadata__.unwrap_or_default(), + Ok(UpdateSipOutboundTrunkRequest { + sip_trunk_id: sip_trunk_id__.unwrap_or_default(), + action: action__, }) } } - deserializer.deserialize_struct("livekit.UpdateRoomMetadataRequest", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.UpdateSIPOutboundTrunkRequest", FIELDS, GeneratedVisitor) } } impl serde::Serialize for UpdateStreamRequest { @@ -38154,6 +39706,9 @@ impl serde::Serialize for WebEgressRequest { if !self.image_outputs.is_empty() { len += 1; } + if !self.webhooks.is_empty() { + len += 1; + } if self.output.is_some() { len += 1; } @@ -38185,6 +39740,9 @@ impl serde::Serialize for WebEgressRequest { if !self.image_outputs.is_empty() { struct_ser.serialize_field("imageOutputs", &self.image_outputs)?; } + if !self.webhooks.is_empty() { + struct_ser.serialize_field("webhooks", &self.webhooks)?; + } if let Some(v) = self.output.as_ref() { match v { web_egress_request::Output::File(v) => { @@ -38235,6 +39793,7 @@ impl<'de> serde::Deserialize<'de> for WebEgressRequest { "segmentOutputs", "image_outputs", "imageOutputs", + "webhooks", "file", "stream", "segments", @@ -38252,6 +39811,7 @@ impl<'de> serde::Deserialize<'de> for WebEgressRequest { StreamOutputs, SegmentOutputs, ImageOutputs, + Webhooks, File, Stream, Segments, @@ -38287,6 +39847,7 @@ impl<'de> serde::Deserialize<'de> for WebEgressRequest { "streamOutputs" | "stream_outputs" => Ok(GeneratedField::StreamOutputs), "segmentOutputs" | "segment_outputs" => Ok(GeneratedField::SegmentOutputs), "imageOutputs" | "image_outputs" => Ok(GeneratedField::ImageOutputs), + "webhooks" => Ok(GeneratedField::Webhooks), "file" => Ok(GeneratedField::File), "stream" => Ok(GeneratedField::Stream), "segments" => Ok(GeneratedField::Segments), @@ -38319,6 +39880,7 @@ impl<'de> serde::Deserialize<'de> for WebEgressRequest { let mut stream_outputs__ = None; let mut segment_outputs__ = None; let mut image_outputs__ = None; + let mut webhooks__ = None; let mut output__ = None; let mut options__ = None; while let Some(k) = map_.next_key()? { @@ -38371,6 +39933,12 @@ impl<'de> serde::Deserialize<'de> for WebEgressRequest { } image_outputs__ = Some(map_.next_value()?); } + GeneratedField::Webhooks => { + if webhooks__.is_some() { + return Err(serde::de::Error::duplicate_field("webhooks")); + } + webhooks__ = Some(map_.next_value()?); + } GeneratedField::File => { if output__.is_some() { return Err(serde::de::Error::duplicate_field("file")); @@ -38419,6 +39987,7 @@ impl<'de> serde::Deserialize<'de> for WebEgressRequest { stream_outputs: stream_outputs__.unwrap_or_default(), segment_outputs: segment_outputs__.unwrap_or_default(), image_outputs: image_outputs__.unwrap_or_default(), + webhooks: webhooks__.unwrap_or_default(), output: output__, options: options__, }) @@ -38427,6 +39996,119 @@ impl<'de> serde::Deserialize<'de> for WebEgressRequest { deserializer.deserialize_struct("livekit.WebEgressRequest", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for WebhookConfig { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.url.is_empty() { + len += 1; + } + if !self.signing_key.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.WebhookConfig", len)?; + if !self.url.is_empty() { + struct_ser.serialize_field("url", &self.url)?; + } + if !self.signing_key.is_empty() { + struct_ser.serialize_field("signingKey", &self.signing_key)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for WebhookConfig { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "url", + "signing_key", + "signingKey", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Url, + SigningKey, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "url" => Ok(GeneratedField::Url), + "signingKey" | "signing_key" => Ok(GeneratedField::SigningKey), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = WebhookConfig; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.WebhookConfig") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut url__ = None; + let mut signing_key__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Url => { + if url__.is_some() { + return Err(serde::de::Error::duplicate_field("url")); + } + url__ = Some(map_.next_value()?); + } + GeneratedField::SigningKey => { + if signing_key__.is_some() { + return Err(serde::de::Error::duplicate_field("signingKey")); + } + signing_key__ = Some(map_.next_value()?); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(WebhookConfig { + url: url__.unwrap_or_default(), + signing_key: signing_key__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.WebhookConfig", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for WebhookEvent { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result diff --git a/livekit/src/prelude.rs b/livekit/src/prelude.rs index 1c2eab0ff..505cf7e2a 100644 --- a/livekit/src/prelude.rs +++ b/livekit/src/prelude.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +pub use livekit_protocol::AudioTrackFeature; + pub use crate::{ id::*, participant::{ diff --git a/livekit/src/proto.rs b/livekit/src/proto.rs index 2b3442255..698a27d5a 100644 --- a/livekit/src/proto.rs +++ b/livekit/src/proto.rs @@ -47,6 +47,7 @@ impl From for participant::DisconnectReason { DisconnectReason::UserUnavailable => Self::UserUnavailable, DisconnectReason::UserRejected => Self::UserRejected, DisconnectReason::SipTrunkFailure => Self::SipTrunkFailure, + DisconnectReason::ConnectionTimeout => Self::ConnectionTimeout, } } } diff --git a/livekit/src/room/data_stream/outgoing.rs b/livekit/src/room/data_stream/outgoing.rs index 7984f4e32..180d53caf 100644 --- a/livekit/src/room/data_stream/outgoing.rs +++ b/livekit/src/room/data_stream/outgoing.rs @@ -74,7 +74,7 @@ impl<'a> StreamWriter<'a> for ByteStreamWriter { &self.info } - async fn write(&self, bytes: &[u8]) -> StreamResult<()> { + async fn write(&self, bytes: &'a [u8]) -> StreamResult<()> { let mut stream = self.stream.lock().await; for chunk in bytes.chunks(CHUNK_SIZE) { stream.write_chunk(chunk).await?; @@ -116,7 +116,7 @@ impl<'a> StreamWriter<'a> for TextStreamWriter { &self.info } - async fn write(&self, text: &str) -> StreamResult<()> { + async fn write(&self, text: &'a str) -> StreamResult<()> { let mut stream = self.stream.lock().await; for chunk in text.as_bytes().utf8_aware_chunks(CHUNK_SIZE) { stream.write_chunk(chunk).await?; diff --git a/livekit/src/room/participant/mod.rs b/livekit/src/room/participant/mod.rs index 30cf0544c..1f0d9b703 100644 --- a/livekit/src/room/participant/mod.rs +++ b/livekit/src/room/participant/mod.rs @@ -62,6 +62,7 @@ pub enum DisconnectReason { UserUnavailable, UserRejected, SipTrunkFailure, + ConnectionTimeout, } #[derive(Debug, Clone)] diff --git a/livekit/src/room/publication/local.rs b/livekit/src/room/publication/local.rs index bd222982c..728177913 100644 --- a/livekit/src/room/publication/local.rs +++ b/livekit/src/room/publication/local.rs @@ -14,7 +14,7 @@ use std::{fmt::Debug, sync::Arc}; -use livekit_protocol as proto; +use livekit_protocol::{self as proto, AudioTrackFeature}; use parking_lot::Mutex; use super::TrackPublicationInner; @@ -145,4 +145,8 @@ impl LocalTrackPublication { pub fn encryption_type(&self) -> EncryptionType { self.inner.info.read().encryption_type } + + pub fn audio_features(&self) -> Vec { + self.inner.info.read().audio_features.clone() + } } diff --git a/livekit/src/room/publication/mod.rs b/livekit/src/room/publication/mod.rs index c4db8c59b..530a9106c 100644 --- a/livekit/src/room/publication/mod.rs +++ b/livekit/src/room/publication/mod.rs @@ -14,8 +14,8 @@ use std::sync::Arc; -use livekit_protocol as proto; use livekit_protocol::enum_dispatch; +use livekit_protocol::{self as proto, AudioTrackFeature}; use parking_lot::{Mutex, RwLock}; use super::track::TrackDimension; @@ -59,6 +59,7 @@ impl TrackPublication { pub fn is_muted(self: &Self) -> bool; pub fn is_remote(self: &Self) -> bool; pub fn encryption_type(self: &Self) -> EncryptionType; + pub fn audio_features(self: &Self) -> Vec; pub(crate) fn on_muted(self: &Self, on_mute: impl Fn(TrackPublication) + Send + 'static) -> (); pub(crate) fn on_unmuted(self: &Self, on_unmute: impl Fn(TrackPublication) + Send + 'static) -> (); @@ -94,6 +95,7 @@ struct PublicationInfo { pub muted: bool, pub proto_info: proto::TrackInfo, pub encryption_type: EncryptionType, + pub audio_features: Vec, } pub(crate) type MutedHandler = Box; @@ -120,12 +122,17 @@ pub(super) fn new_inner( source: info.source().into(), kind: info.r#type().try_into().unwrap(), encryption_type: info.encryption().into(), - name: info.name, - sid: info.sid.try_into().unwrap(), + name: info.clone().name, + sid: info.sid.clone().try_into().unwrap(), simulcasted: info.simulcast, dimension: TrackDimension(info.width, info.height), - mime_type: info.mime_type, + mime_type: info.mime_type.clone(), muted: info.muted, + audio_features: info + .audio_features() + .into_iter() + .map(|item| item.try_into().unwrap()) + .collect(), }; Arc::new(TrackPublicationInner { info: RwLock::new(info), events: Default::default() }) @@ -141,11 +148,12 @@ pub(super) fn update_info( info.source = TrackSource::from(new_info.source()); info.encryption_type = new_info.encryption().into(); info.proto_info = new_info.clone(); - info.name = new_info.name; - info.sid = new_info.sid.try_into().unwrap(); + info.name = new_info.name.clone(); + info.sid = new_info.sid.clone().try_into().unwrap(); info.dimension = TrackDimension(new_info.width, new_info.height); - info.mime_type = new_info.mime_type; + info.mime_type = new_info.mime_type.clone(); info.simulcasted = new_info.simulcast; + info.audio_features = new_info.audio_features().collect(); } pub(super) fn set_track( diff --git a/livekit/src/room/publication/remote.rs b/livekit/src/room/publication/remote.rs index fe86ebf1f..6e93fdeb7 100644 --- a/livekit/src/room/publication/remote.rs +++ b/livekit/src/room/publication/remote.rs @@ -14,7 +14,7 @@ use std::{fmt::Debug, sync::Arc}; -use livekit_protocol as proto; +use livekit_protocol::{self as proto, AudioTrackFeature}; use parking_lot::{Mutex, RwLock}; use super::{PermissionStatus, SubscriptionStatus, TrackPublication, TrackPublicationInner}; @@ -368,4 +368,8 @@ impl RemoteTrackPublication { pub fn encryption_type(&self) -> EncryptionType { self.inner.info.read().encryption_type } + + pub fn audio_features(&self) -> Vec { + self.inner.info.read().audio_features.clone() + } } diff --git a/livekit/src/room/track/mod.rs b/livekit/src/room/track/mod.rs index 27992bd8f..936b3fec0 100644 --- a/livekit/src/room/track/mod.rs +++ b/livekit/src/room/track/mod.rs @@ -15,8 +15,8 @@ use std::{fmt::Debug, sync::Arc}; use libwebrtc::{prelude::*, stats::RtcStats}; -use livekit_protocol as proto; use livekit_protocol::enum_dispatch; +use livekit_protocol::{self as proto}; use parking_lot::{Mutex, RwLock}; use thiserror::Error; @@ -145,6 +145,7 @@ struct TrackInfo { pub stream_state: StreamState, pub muted: bool, pub transceiver: Option, + pub audio_features: Vec, } pub(super) struct TrackInner { @@ -168,6 +169,7 @@ pub(super) fn new_inner( stream_state: StreamState::Active, muted: false, transceiver: None, + audio_features: Vec::new(), }), rtc_track, events: Default::default(), @@ -203,6 +205,8 @@ pub(super) fn update_info(inner: &Arc, _track: &Track, new_info: pro let mut info = inner.info.write(); info.kind = TrackKind::try_from(new_info.r#type()).unwrap(); info.source = TrackSource::from(new_info.source()); - info.name = new_info.name; - info.sid = new_info.sid.try_into().unwrap(); + info.name = new_info.name.clone(); + info.sid = new_info.sid.clone().try_into().unwrap(); + info.audio_features = + new_info.audio_features().into_iter().map(|item| item.try_into().unwrap()).collect(); } diff --git a/livekit/src/room/track/remote_audio_track.rs b/livekit/src/room/track/remote_audio_track.rs index a73023de7..cb62fbf1b 100644 --- a/livekit/src/room/track/remote_audio_track.rs +++ b/livekit/src/room/track/remote_audio_track.rs @@ -15,7 +15,7 @@ use std::{fmt::Debug, sync::Arc}; use libwebrtc::{prelude::*, stats::RtcStats}; -use livekit_protocol as proto; +use livekit_protocol::{self as proto, AudioTrackFeature}; use super::{remote_track, TrackInner}; use crate::prelude::*; From 01746ff5f939269c7c81ae681f769e3c28c1da72 Mon Sep 17 00:00:00 2001 From: aoife cassidy Date: Thu, 8 May 2025 07:33:05 -0700 Subject: [PATCH 189/274] nanpa: remove (#633) --- .github/workflows/publish.yml | 57 ---------------------------- .nanpa/.keep | 0 .nanpa/fixed-libwebrtc-jar-build.kdl | 1 - .nanpa/i420_to_nv12.kdl | 1 - .nanpa/int32-overflow.kdl | 1 - .nanpa/rule-wound-snarl.kdl | 2 - .nanpa/stream-close.kdl | 1 - .nanparc | 1 - imgproc/.nanparc | 2 - libwebrtc/.nanparc | 2 - livekit-api/.nanparc | 2 - livekit-ffi/.nanparc | 2 - livekit-protocol/.nanparc | 2 - livekit-runtime/.nanparc | 2 - livekit/.nanparc | 2 - soxr-sys/.nanparc | 2 - webrtc-sys/.nanparc | 2 - webrtc-sys/build/.nanparc | 2 - yuv-sys/.nanparc | 2 - 19 files changed, 86 deletions(-) delete mode 100644 .github/workflows/publish.yml delete mode 100644 .nanpa/.keep delete mode 100644 .nanpa/fixed-libwebrtc-jar-build.kdl delete mode 100644 .nanpa/i420_to_nv12.kdl delete mode 100644 .nanpa/int32-overflow.kdl delete mode 100644 .nanpa/rule-wound-snarl.kdl delete mode 100644 .nanpa/stream-close.kdl delete mode 100644 .nanparc delete mode 100644 imgproc/.nanparc delete mode 100644 libwebrtc/.nanparc delete mode 100644 livekit-api/.nanparc delete mode 100644 livekit-ffi/.nanparc delete mode 100644 livekit-protocol/.nanparc delete mode 100644 livekit-runtime/.nanparc delete mode 100644 livekit/.nanparc delete mode 100644 soxr-sys/.nanparc delete mode 100644 webrtc-sys/.nanparc delete mode 100644 webrtc-sys/build/.nanparc delete mode 100644 yuv-sys/.nanparc diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml deleted file mode 100644 index 9670190c4..000000000 --- a/.github/workflows/publish.yml +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright 2023 LiveKit, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -name: Bump and publish crates - -on: - workflow_dispatch: - inputs: - packages: - description: "packages to bump" - type: string - required: true - -env: - CARGO_TERM_COLOR: always - CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_TOKEN }} - -jobs: - bump: - permissions: - contents: write - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - ssh-key: ${{ secrets.NANPA_KEY }} - - uses: nbsp/ilo@v1 - with: - packages: ${{ github.event.inputs.packages }} - publish: - runs-on: ubuntu-latest - needs: bump - steps: - - uses: actions/checkout@v3 - with: - submodules: true - - name: Resync is needed after bump - run: | - git fetch --tags && git checkout origin/main - - name: Publish crates - run: | - git tag --points-at HEAD | - sed 's|^[^/]*@|@|' | - sed 's|^[^/]*/||' | - sed 's|@.*||' | - xargs -I _ sh -c 'cd ./_ && cargo publish --no-verify' diff --git a/.nanpa/.keep b/.nanpa/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/.nanpa/fixed-libwebrtc-jar-build.kdl b/.nanpa/fixed-libwebrtc-jar-build.kdl deleted file mode 100644 index 3af32880b..000000000 --- a/.nanpa/fixed-libwebrtc-jar-build.kdl +++ /dev/null @@ -1 +0,0 @@ -patch type="fixed" "Build issue for libwebrtc.jar and libjingle_peerconnection_so.so" diff --git a/.nanpa/i420_to_nv12.kdl b/.nanpa/i420_to_nv12.kdl deleted file mode 100644 index cae0c2e70..000000000 --- a/.nanpa/i420_to_nv12.kdl +++ /dev/null @@ -1 +0,0 @@ -minor type="added" "Add i420_to_nv12" diff --git a/.nanpa/int32-overflow.kdl b/.nanpa/int32-overflow.kdl deleted file mode 100644 index c1729bd0f..000000000 --- a/.nanpa/int32-overflow.kdl +++ /dev/null @@ -1 +0,0 @@ -patch type="fixed" "fix uint32 overflow" \ No newline at end of file diff --git a/.nanpa/rule-wound-snarl.kdl b/.nanpa/rule-wound-snarl.kdl deleted file mode 100644 index f35fa7fc0..000000000 --- a/.nanpa/rule-wound-snarl.kdl +++ /dev/null @@ -1,2 +0,0 @@ -patch package="livekit-ffi" type="added" "Initial HTTP_PROXY support for SignalClient" -patch package="livekit-api" type="added" "Initial HTTP_PROXY support for SignalClient" diff --git a/.nanpa/stream-close.kdl b/.nanpa/stream-close.kdl deleted file mode 100644 index 8c59d544d..000000000 --- a/.nanpa/stream-close.kdl +++ /dev/null @@ -1 +0,0 @@ -patch type="fixed" "fix rtc.AudioStream.from_participant close" \ No newline at end of file diff --git a/.nanparc b/.nanparc deleted file mode 100644 index 7e9fced1a..000000000 --- a/.nanparc +++ /dev/null @@ -1 +0,0 @@ -packages livekit livekit-ffi livekit-protocol livekit-runtime livekit-api libwebrtc webrtc-sys webrtc-sys/build soxr-sys yuv-sys imgproc diff --git a/imgproc/.nanparc b/imgproc/.nanparc deleted file mode 100644 index 4e9ce1ea8..000000000 --- a/imgproc/.nanparc +++ /dev/null @@ -1,2 +0,0 @@ -version 0.3.12 -language rust diff --git a/libwebrtc/.nanparc b/libwebrtc/.nanparc deleted file mode 100644 index 6f8134801..000000000 --- a/libwebrtc/.nanparc +++ /dev/null @@ -1,2 +0,0 @@ -version 0.3.10 -language rust diff --git a/livekit-api/.nanparc b/livekit-api/.nanparc deleted file mode 100644 index d3108f6b1..000000000 --- a/livekit-api/.nanparc +++ /dev/null @@ -1,2 +0,0 @@ -version 0.4.2 -language rust diff --git a/livekit-ffi/.nanparc b/livekit-ffi/.nanparc deleted file mode 100644 index b1b05a40d..000000000 --- a/livekit-ffi/.nanparc +++ /dev/null @@ -1,2 +0,0 @@ -version 0.12.22 -language rust diff --git a/livekit-protocol/.nanparc b/livekit-protocol/.nanparc deleted file mode 100644 index 96df60bef..000000000 --- a/livekit-protocol/.nanparc +++ /dev/null @@ -1,2 +0,0 @@ -version 0.3.9 -language rust diff --git a/livekit-runtime/.nanparc b/livekit-runtime/.nanparc deleted file mode 100644 index f3e92f54e..000000000 --- a/livekit-runtime/.nanparc +++ /dev/null @@ -1,2 +0,0 @@ -version 0.4.0 -language rust diff --git a/livekit/.nanparc b/livekit/.nanparc deleted file mode 100644 index 1373b1eb9..000000000 --- a/livekit/.nanparc +++ /dev/null @@ -1,2 +0,0 @@ -version 0.7.9 -language rust diff --git a/soxr-sys/.nanparc b/soxr-sys/.nanparc deleted file mode 100644 index 2124a88b3..000000000 --- a/soxr-sys/.nanparc +++ /dev/null @@ -1,2 +0,0 @@ -version 0.1.0 -language rust diff --git a/webrtc-sys/.nanparc b/webrtc-sys/.nanparc deleted file mode 100644 index 5bf971c37..000000000 --- a/webrtc-sys/.nanparc +++ /dev/null @@ -1,2 +0,0 @@ -version 0.3.7 -language rust diff --git a/webrtc-sys/build/.nanparc b/webrtc-sys/build/.nanparc deleted file mode 100644 index 10b95b2c7..000000000 --- a/webrtc-sys/build/.nanparc +++ /dev/null @@ -1,2 +0,0 @@ -version 0.3.6 -language rust diff --git a/yuv-sys/.nanparc b/yuv-sys/.nanparc deleted file mode 100644 index 5bf971c37..000000000 --- a/yuv-sys/.nanparc +++ /dev/null @@ -1,2 +0,0 @@ -version 0.3.7 -language rust From 2c83d4d4f285b6c76fdc318bed53e6cff6832285 Mon Sep 17 00:00:00 2001 From: lukasIO Date: Thu, 8 May 2025 17:26:18 +0200 Subject: [PATCH 190/274] bump ffi, rtc, protocol (#635) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Théo Monnom --- Cargo.lock | 6 +++--- Cargo.toml | 6 +++--- livekit-ffi/Cargo.toml | 2 +- livekit-protocol/Cargo.toml | 2 +- livekit/Cargo.toml | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 36b112358..4b5416ae4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1615,7 +1615,7 @@ checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "livekit" -version = "0.7.9" +version = "0.7.10" dependencies = [ "bmrng", "bytes", @@ -1669,7 +1669,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.12.22" +version = "0.12.23" dependencies = [ "bytes", "console-subscriber", @@ -1695,7 +1695,7 @@ dependencies = [ [[package]] name = "livekit-protocol" -version = "0.3.9" +version = "0.3.10" dependencies = [ "futures-util", "livekit-runtime", diff --git a/Cargo.toml b/Cargo.toml index 96aa7094a..2f370d1a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,10 +19,10 @@ imgproc = { version = "0.3.12", path = "imgproc" } yuv-sys = { version = "0.3.7", path = "yuv-sys" } libwebrtc = { version = "0.3.10", path = "libwebrtc" } livekit-api = { version = "0.4.2", path = "livekit-api" } -livekit-ffi = { version = "0.12.22", path = "livekit-ffi" } -livekit-protocol = { version = "0.3.9", path = "livekit-protocol" } +livekit-ffi = { version = "0.12.23", path = "livekit-ffi" } +livekit-protocol = { version = "0.3.10", path = "livekit-protocol" } livekit-runtime = { version = "0.4.0", path = "livekit-runtime" } -livekit = { version = "0.7.9", path = "livekit" } +livekit = { version = "0.7.10", path = "livekit" } soxr-sys = { version = "0.1.0", path = "soxr-sys" } webrtc-sys-build = { version = "0.3.6", path = "webrtc-sys/build" } webrtc-sys = { version = "0.3.7", path = "webrtc-sys" } diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 2ce094824..bae97d3cf 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.12.22" +version = "0.12.23" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" diff --git a/livekit-protocol/Cargo.toml b/livekit-protocol/Cargo.toml index 875c28846..248915203 100644 --- a/livekit-protocol/Cargo.toml +++ b/livekit-protocol/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-protocol" -version = "0.3.9" +version = "0.3.10" edition = "2021" license = "Apache-2.0" description = "Livekit protocol and utilities for the Rust SDK" diff --git a/livekit/Cargo.toml b/livekit/Cargo.toml index 20afd54d7..369dfe171 100644 --- a/livekit/Cargo.toml +++ b/livekit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit" -version = "0.7.9" +version = "0.7.10" edition = "2021" license = "Apache-2.0" description = "Rust Client SDK for LiveKit" From 3ee79bc21035dcb13f6b9fbc846f32fcc4919703 Mon Sep 17 00:00:00 2001 From: Ben Cherry Date: Wed, 14 May 2025 14:54:04 -0700 Subject: [PATCH 191/274] Unorder the lossy data channel (#639) --- livekit/src/rtc_engine/rtc_session.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/livekit/src/rtc_engine/rtc_session.rs b/livekit/src/rtc_engine/rtc_session.rs index 01e57cd85..7d5b0f7f1 100644 --- a/livekit/src/rtc_engine/rtc_session.rs +++ b/livekit/src/rtc_engine/rtc_session.rs @@ -254,7 +254,7 @@ impl RtcSession { let mut lossy_dc = publisher_pc.peer_connection().create_data_channel( LOSSY_DC_LABEL, DataChannelInit { - ordered: true, + ordered: false, max_retransmits: Some(0), ..DataChannelInit::default() }, From d6a4f07aea3487dd8f5d681607e2407385037cf7 Mon Sep 17 00:00:00 2001 From: Daisuke Murase Date: Thu, 15 May 2025 13:21:54 -0700 Subject: [PATCH 192/274] notify refreshed_token to audio filters (#636) --- livekit/src/plugin.rs | 21 +++++++++++++++++++++ livekit/src/room/mod.rs | 11 +++++++++++ livekit/src/rtc_engine/mod.rs | 7 +++++++ livekit/src/rtc_engine/rtc_session.rs | 8 ++++++++ 4 files changed, 47 insertions(+) diff --git a/livekit/src/plugin.rs b/livekit/src/plugin.rs index 0ef2616ef..ce84ec813 100644 --- a/livekit/src/plugin.rs +++ b/livekit/src/plugin.rs @@ -34,6 +34,7 @@ type DestroyFn = unsafe extern "C" fn(*const c_void); type ProcessI16Fn = unsafe extern "C" fn(*const c_void, usize, *const i16, *mut i16); type ProcessF32Fn = unsafe extern "C" fn(*const c_void, usize, *const f32, *mut f32); type UpdateStreamInfoFn = unsafe extern "C" fn(*const c_void, *const c_char); +type UpdateRefreshedTokenFn = unsafe extern "C" fn(*const c_char, *const c_char); static REGISTERED_PLUGINS: LazyLock>>> = LazyLock::new(|| RwLock::new(HashMap::new())); @@ -59,6 +60,7 @@ pub struct AudioFilterPlugin { process_i16_fn_ptr: *const c_void, process_f32_fn_ptr: *const c_void, update_stream_info_fn_ptr: *const c_void, + update_token_fn_ptr: *const c_void, } impl AudioFilterPlugin { @@ -123,6 +125,13 @@ impl AudioFilterPlugin { .try_as_raw_ptr() .unwrap() }; + let update_token_fn_ptr = unsafe { + // treat as optional function for now + match lib.get::>(b"audio_filter_update_token") { + Ok(sym) => sym.try_as_raw_ptr().unwrap(), + Err(_) => std::ptr::null(), + } + }; Ok(Self { lib, @@ -133,6 +142,7 @@ impl AudioFilterPlugin { process_i16_fn_ptr, process_f32_fn_ptr, update_stream_info_fn_ptr, + update_token_fn_ptr, }) } @@ -162,6 +172,17 @@ impl AudioFilterPlugin { } } + pub fn update_token(&self, url: String, token: String) { + if self.update_token_fn_ptr.is_null() { + return; + } + let update_token_fn: UpdateRefreshedTokenFn = + unsafe { std::mem::transmute(self.update_token_fn_ptr) }; + let url = CString::new(url).unwrap(); + let token = CString::new(token).unwrap(); + unsafe { update_token_fn(url.as_ptr(), token.as_ptr()) } + } + pub fn new_session>( self: Arc, sampling_rate: u32, diff --git a/livekit/src/room/mod.rs b/livekit/src/room/mod.rs index 06c6350f8..d387843e0 100644 --- a/livekit/src/room/mod.rs +++ b/livekit/src/room/mod.rs @@ -47,6 +47,7 @@ pub use crate::rtc_engine::SimulateScenario; use crate::{ participant::ConnectionQuality, prelude::*, + registered_audio_filter_plugins, rtc_engine::{ EngineError, EngineEvent, EngineEvents, EngineOptions, EngineResult, RtcEngine, SessionStats, INITIAL_BUFFERED_AMOUNT_LOW_THRESHOLD, @@ -832,6 +833,9 @@ impl RoomSession { EngineEvent::DataChannelBufferedAmountLowThresholdChanged { kind, threshold } => { self.handle_data_channel_buffered_low_threshold_change(kind, threshold); } + EngineEvent::RefreshToken { url, token } => { + self.handle_refresh_token(url, token); + } _ => {} } @@ -1561,6 +1565,13 @@ impl RoomSession { } return self.get_participant_by_identity(identity).map(Participant::Remote); } + + fn handle_refresh_token(self: &Arc, url: String, token: String) { + // notify refreshed token to registered audio filters + for filter in registered_audio_filter_plugins().into_iter() { + filter.update_token(url.clone(), token.clone()); + } + } } /// Receives stream readers for newly-opened streams and dispatches room events. diff --git a/livekit/src/rtc_engine/mod.rs b/livekit/src/rtc_engine/mod.rs index 5599861e9..16c724487 100644 --- a/livekit/src/rtc_engine/mod.rs +++ b/livekit/src/rtc_engine/mod.rs @@ -175,6 +175,10 @@ pub enum EngineEvent { kind: DataPacketKind, threshold: u64, }, + RefreshToken { + url: String, + token: String, + }, } /// Represents a running RtcSession with the ability to close the session @@ -560,6 +564,9 @@ impl EngineInner { EngineEvent::DataChannelBufferedAmountLowThresholdChanged { kind, threshold }, ); } + SessionEvent::RefreshToken { url, token } => { + let _ = self.engine_tx.send(EngineEvent::RefreshToken { url, token }); + } } Ok(()) } diff --git a/livekit/src/rtc_engine/rtc_session.rs b/livekit/src/rtc_engine/rtc_session.rs index 7d5b0f7f1..ce1cd61ee 100644 --- a/livekit/src/rtc_engine/rtc_session.rs +++ b/livekit/src/rtc_engine/rtc_session.rs @@ -152,6 +152,10 @@ pub enum SessionEvent { kind: DataPacketKind, threshold: u64, }, + RefreshToken { + url: String, + token: String, + }, } #[derive(Debug)] @@ -705,6 +709,10 @@ impl SessionInner { let _ = tx.send(request_response); } } + proto::signal_response::Message::RefreshToken(ref token) => { + let url = self.signal_client.url(); + let _ = self.emitter.send(SessionEvent::RefreshToken { url, token: token.clone() }); + } _ => {} } From 67edb0b041028ba049cebe660315f464e51c04aa Mon Sep 17 00:00:00 2001 From: Ben Cherry Date: Thu, 15 May 2025 13:32:27 -0700 Subject: [PATCH 193/274] Bump livekit + livekit-ffi versions (#640) --- Cargo.toml | 4 ++-- livekit-ffi/Cargo.toml | 2 +- livekit/Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2f370d1a1..c99606dd1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,10 +19,10 @@ imgproc = { version = "0.3.12", path = "imgproc" } yuv-sys = { version = "0.3.7", path = "yuv-sys" } libwebrtc = { version = "0.3.10", path = "libwebrtc" } livekit-api = { version = "0.4.2", path = "livekit-api" } -livekit-ffi = { version = "0.12.23", path = "livekit-ffi" } +livekit-ffi = { version = "0.12.24", path = "livekit-ffi" } livekit-protocol = { version = "0.3.10", path = "livekit-protocol" } livekit-runtime = { version = "0.4.0", path = "livekit-runtime" } -livekit = { version = "0.7.10", path = "livekit" } +livekit = { version = "0.7.11", path = "livekit" } soxr-sys = { version = "0.1.0", path = "soxr-sys" } webrtc-sys-build = { version = "0.3.6", path = "webrtc-sys/build" } webrtc-sys = { version = "0.3.7", path = "webrtc-sys" } diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index bae97d3cf..e8baaf6fe 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.12.23" +version = "0.12.24" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" diff --git a/livekit/Cargo.toml b/livekit/Cargo.toml index 369dfe171..58fb4c334 100644 --- a/livekit/Cargo.toml +++ b/livekit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit" -version = "0.7.10" +version = "0.7.11" edition = "2021" license = "Apache-2.0" description = "Rust Client SDK for LiveKit" From ee570210f7aa451221c5f99b7cfb9c3255153b8f Mon Sep 17 00:00:00 2001 From: Ben Cherry Date: Thu, 15 May 2025 19:11:56 -0700 Subject: [PATCH 194/274] 0.12.24 (#642) --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4b5416ae4..99c2b485d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1615,7 +1615,7 @@ checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "livekit" -version = "0.7.10" +version = "0.7.11" dependencies = [ "bmrng", "bytes", @@ -1669,7 +1669,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.12.23" +version = "0.12.24" dependencies = [ "bytes", "console-subscriber", From d4de69e8beb519e31af01b84704553f094d53936 Mon Sep 17 00:00:00 2001 From: Anunay Maheshwari Date: Fri, 16 May 2025 13:12:11 +0530 Subject: [PATCH 195/274] feat(api): forward and move participant apis (#641) * chore(deps): update protocol * feat(api): forward and move participant apis --- livekit-api/src/access_token.rs | 3 + livekit-api/src/services/room.rs | 58 +++ livekit-protocol/protocol | 2 +- livekit-protocol/src/livekit.rs | 85 +++- livekit-protocol/src/livekit.serde.rs | 694 +++++++++++++++++++++++++- 5 files changed, 827 insertions(+), 15 deletions(-) diff --git a/livekit-api/src/access_token.rs b/livekit-api/src/access_token.rs index 4fce13576..70cb73e93 100644 --- a/livekit-api/src/access_token.rs +++ b/livekit-api/src/access_token.rs @@ -57,6 +57,8 @@ pub struct VideoGrants { pub room_join: bool, #[serde(default)] pub room: String, + #[serde(default)] + pub destination_room: String, // permissions within a room #[serde(default = "default_true")] @@ -102,6 +104,7 @@ impl Default for VideoGrants { room_admin: false, room_join: false, room: "".to_string(), + destination_room: "".to_string(), can_publish: true, can_subscribe: true, can_publish_data: true, diff --git a/livekit-api/src/services/room.rs b/livekit-api/src/services/room.rs index 006e54ee4..e1643d162 100644 --- a/livekit-api/src/services/room.rs +++ b/livekit-api/src/services/room.rs @@ -201,6 +201,64 @@ impl RoomClient { .map_err(Into::into) } + pub async fn forward_participant( + &self, + room: &str, + identity: &str, + destination_room: &str, + ) -> ServiceResult<()> { + self.client + .request( + SVC, + "ForwardParticipant", + proto::ForwardParticipantRequest { + room: room.to_owned(), + identity: identity.to_owned(), + destination_room: destination_room.to_owned(), + }, + self.base.auth_header( + VideoGrants { + room_admin: true, + room: room.to_owned(), + destination_room: destination_room.to_owned(), + ..Default::default() + }, + None, + )?, + ) + .await + .map_err(Into::into) + } + + pub async fn move_participant( + &self, + room: &str, + identity: &str, + destination_room: &str, + ) -> ServiceResult<()> { + self.client + .request( + SVC, + "MoveParticipant", + proto::MoveParticipantRequest { + room: room.to_owned(), + identity: identity.to_owned(), + destination_room: destination_room.to_owned(), + }, + self.base.auth_header( + VideoGrants { + room_admin: true, + room: room.to_owned(), + destination_room: destination_room.to_owned(), + ..Default::default() + }, + None, + )?, + ) + .await + .map_err(Into::into) + } + pub async fn mute_published_track( &self, room: &str, diff --git a/livekit-protocol/protocol b/livekit-protocol/protocol index b047e92f0..3ee266441 160000 --- a/livekit-protocol/protocol +++ b/livekit-protocol/protocol @@ -1 +1 @@ -Subproject commit b047e92f055de744e5a5293848830cd803f3217b +Subproject commit 3ee2664416147b7ca8086fd1aa818164d2673b0b diff --git a/livekit-protocol/src/livekit.rs b/livekit-protocol/src/livekit.rs index 250ab4345..551fe688e 100644 --- a/livekit-protocol/src/livekit.rs +++ b/livekit-protocol/src/livekit.rs @@ -2920,7 +2920,7 @@ pub mod signal_request { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SignalResponse { - #[prost(oneof="signal_response::Message", tags="1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23")] + #[prost(oneof="signal_response::Message", tags="1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24")] pub message: ::core::option::Option, } /// Nested message and enum types in `SignalResponse`. @@ -2997,6 +2997,9 @@ pub mod signal_response { /// notify to the publisher when a published track has been subscribed for the first time #[prost(message, tag="23")] TrackSubscribed(super::TrackSubscribed), + /// notify to the participant when they have been moved to a new room + #[prost(message, tag="24")] + RoomMoved(super::RoomMovedResponse), } } #[allow(clippy::derive_partial_eq_without_eq)] @@ -3398,6 +3401,20 @@ pub struct SubscriptionPermissionUpdate { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct RoomMovedResponse { + /// information about the new room + #[prost(message, optional, tag="1")] + pub room: ::core::option::Option, + /// new reconnect token that can be used to reconnect to the new room + #[prost(string, tag="2")] + pub token: ::prost::alloc::string::String, + #[prost(message, optional, tag="3")] + pub participant: ::core::option::Option, + #[prost(message, repeated, tag="4")] + pub other_participants: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct SyncState { /// last subscribe answer before reconnecting #[prost(message, optional, tag="1")] @@ -4277,6 +4294,23 @@ pub struct ForwardParticipantResponse { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct MoveParticipantRequest { + /// room to move participant from + #[prost(string, tag="1")] + pub room: ::prost::alloc::string::String, + /// identity of the participant to move to + #[prost(string, tag="2")] + pub identity: ::prost::alloc::string::String, + /// room to move participant to + #[prost(string, tag="3")] + pub destination_room: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MoveParticipantResponse { +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct CreateIngressRequest { #[prost(enumeration="IngressInput", tag="1")] pub input_type: i32, @@ -5507,6 +5541,26 @@ pub struct SipCallInfo { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct SipTransferInfo { + #[prost(string, tag="1")] + pub transfer_id: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub call_id: ::prost::alloc::string::String, + #[prost(string, tag="3")] + pub transfer_to: ::prost::alloc::string::String, + #[prost(int64, tag="4")] + pub transfer_initiated_at_ns: i64, + #[prost(int64, tag="5")] + pub transfer_completed_at_ns: i64, + #[prost(enumeration="SipTransferStatus", tag="6")] + pub transfer_status: i32, + #[prost(string, tag="7")] + pub error: ::prost::alloc::string::String, + #[prost(message, optional, tag="8")] + pub transfer_status_code: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct SipUri { #[prost(string, tag="1")] pub user: ::prost::alloc::string::String, @@ -5824,6 +5878,35 @@ impl SipCallStatus { } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] +pub enum SipTransferStatus { + StsTransferOngoing = 0, + StsTransferFailed = 1, + StsTransferSuccessful = 2, +} +impl SipTransferStatus { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + SipTransferStatus::StsTransferOngoing => "STS_TRANSFER_ONGOING", + SipTransferStatus::StsTransferFailed => "STS_TRANSFER_FAILED", + SipTransferStatus::StsTransferSuccessful => "STS_TRANSFER_SUCCESSFUL", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "STS_TRANSFER_ONGOING" => Some(Self::StsTransferOngoing), + "STS_TRANSFER_FAILED" => Some(Self::StsTransferFailed), + "STS_TRANSFER_SUCCESSFUL" => Some(Self::StsTransferSuccessful), + _ => None, + } + } +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] pub enum SipFeature { None = 0, KrispEnabled = 1, diff --git a/livekit-protocol/src/livekit.serde.rs b/livekit-protocol/src/livekit.serde.rs index 4cd41062f..c88fd8c24 100644 --- a/livekit-protocol/src/livekit.serde.rs +++ b/livekit-protocol/src/livekit.serde.rs @@ -16827,6 +16827,208 @@ impl<'de> serde::Deserialize<'de> for MigrateJobRequest { deserializer.deserialize_struct("livekit.MigrateJobRequest", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for MoveParticipantRequest { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.room.is_empty() { + len += 1; + } + if !self.identity.is_empty() { + len += 1; + } + if !self.destination_room.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.MoveParticipantRequest", len)?; + if !self.room.is_empty() { + struct_ser.serialize_field("room", &self.room)?; + } + if !self.identity.is_empty() { + struct_ser.serialize_field("identity", &self.identity)?; + } + if !self.destination_room.is_empty() { + struct_ser.serialize_field("destinationRoom", &self.destination_room)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for MoveParticipantRequest { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "room", + "identity", + "destination_room", + "destinationRoom", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Room, + Identity, + DestinationRoom, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "room" => Ok(GeneratedField::Room), + "identity" => Ok(GeneratedField::Identity), + "destinationRoom" | "destination_room" => Ok(GeneratedField::DestinationRoom), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = MoveParticipantRequest; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.MoveParticipantRequest") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut room__ = None; + let mut identity__ = None; + let mut destination_room__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Room => { + if room__.is_some() { + return Err(serde::de::Error::duplicate_field("room")); + } + room__ = Some(map_.next_value()?); + } + GeneratedField::Identity => { + if identity__.is_some() { + return Err(serde::de::Error::duplicate_field("identity")); + } + identity__ = Some(map_.next_value()?); + } + GeneratedField::DestinationRoom => { + if destination_room__.is_some() { + return Err(serde::de::Error::duplicate_field("destinationRoom")); + } + destination_room__ = Some(map_.next_value()?); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(MoveParticipantRequest { + room: room__.unwrap_or_default(), + identity: identity__.unwrap_or_default(), + destination_room: destination_room__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.MoveParticipantRequest", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for MoveParticipantResponse { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let len = 0; + let struct_ser = serializer.serialize_struct("livekit.MoveParticipantResponse", len)?; + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for MoveParticipantResponse { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + Ok(GeneratedField::__SkipField__) + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = MoveParticipantResponse; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.MoveParticipantResponse") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + while map_.next_key::()?.is_some() { + let _ = map_.next_value::()?; + } + Ok(MoveParticipantResponse { + }) + } + } + deserializer.deserialize_struct("livekit.MoveParticipantResponse", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for MuteRoomTrackRequest { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -23212,7 +23414,145 @@ impl<'de> serde::Deserialize<'de> for RoomEgress { enum GeneratedField { Room, Participant, - Tracks, + Tracks, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "room" => Ok(GeneratedField::Room), + "participant" => Ok(GeneratedField::Participant), + "tracks" => Ok(GeneratedField::Tracks), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = RoomEgress; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.RoomEgress") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut room__ = None; + let mut participant__ = None; + let mut tracks__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Room => { + if room__.is_some() { + return Err(serde::de::Error::duplicate_field("room")); + } + room__ = map_.next_value()?; + } + GeneratedField::Participant => { + if participant__.is_some() { + return Err(serde::de::Error::duplicate_field("participant")); + } + participant__ = map_.next_value()?; + } + GeneratedField::Tracks => { + if tracks__.is_some() { + return Err(serde::de::Error::duplicate_field("tracks")); + } + tracks__ = map_.next_value()?; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(RoomEgress { + room: room__, + participant: participant__, + tracks: tracks__, + }) + } + } + deserializer.deserialize_struct("livekit.RoomEgress", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for RoomMovedResponse { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.room.is_some() { + len += 1; + } + if !self.token.is_empty() { + len += 1; + } + if self.participant.is_some() { + len += 1; + } + if !self.other_participants.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.RoomMovedResponse", len)?; + if let Some(v) = self.room.as_ref() { + struct_ser.serialize_field("room", v)?; + } + if !self.token.is_empty() { + struct_ser.serialize_field("token", &self.token)?; + } + if let Some(v) = self.participant.as_ref() { + struct_ser.serialize_field("participant", v)?; + } + if !self.other_participants.is_empty() { + struct_ser.serialize_field("otherParticipants", &self.other_participants)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for RoomMovedResponse { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "room", + "token", + "participant", + "other_participants", + "otherParticipants", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Room, + Token, + Participant, + OtherParticipants, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -23236,8 +23576,9 @@ impl<'de> serde::Deserialize<'de> for RoomEgress { { match value { "room" => Ok(GeneratedField::Room), + "token" => Ok(GeneratedField::Token), "participant" => Ok(GeneratedField::Participant), - "tracks" => Ok(GeneratedField::Tracks), + "otherParticipants" | "other_participants" => Ok(GeneratedField::OtherParticipants), _ => Ok(GeneratedField::__SkipField__), } } @@ -23247,19 +23588,20 @@ impl<'de> serde::Deserialize<'de> for RoomEgress { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = RoomEgress; + type Value = RoomMovedResponse; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.RoomEgress") + formatter.write_str("struct livekit.RoomMovedResponse") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { let mut room__ = None; + let mut token__ = None; let mut participant__ = None; - let mut tracks__ = None; + let mut other_participants__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::Room => { @@ -23268,31 +23610,38 @@ impl<'de> serde::Deserialize<'de> for RoomEgress { } room__ = map_.next_value()?; } + GeneratedField::Token => { + if token__.is_some() { + return Err(serde::de::Error::duplicate_field("token")); + } + token__ = Some(map_.next_value()?); + } GeneratedField::Participant => { if participant__.is_some() { return Err(serde::de::Error::duplicate_field("participant")); } participant__ = map_.next_value()?; } - GeneratedField::Tracks => { - if tracks__.is_some() { - return Err(serde::de::Error::duplicate_field("tracks")); + GeneratedField::OtherParticipants => { + if other_participants__.is_some() { + return Err(serde::de::Error::duplicate_field("otherParticipants")); } - tracks__ = map_.next_value()?; + other_participants__ = Some(map_.next_value()?); } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } } } - Ok(RoomEgress { + Ok(RoomMovedResponse { room: room__, + token: token__.unwrap_or_default(), participant: participant__, - tracks: tracks__, + other_participants: other_participants__.unwrap_or_default(), }) } } - deserializer.deserialize_struct("livekit.RoomEgress", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.RoomMovedResponse", FIELDS, GeneratedVisitor) } } impl serde::Serialize for RoomParticipantIdentity { @@ -27978,6 +28327,311 @@ impl<'de> serde::Deserialize<'de> for SipStatusCode { deserializer.deserialize_any(GeneratedVisitor) } } +impl serde::Serialize for SipTransferInfo { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.transfer_id.is_empty() { + len += 1; + } + if !self.call_id.is_empty() { + len += 1; + } + if !self.transfer_to.is_empty() { + len += 1; + } + if self.transfer_initiated_at_ns != 0 { + len += 1; + } + if self.transfer_completed_at_ns != 0 { + len += 1; + } + if self.transfer_status != 0 { + len += 1; + } + if !self.error.is_empty() { + len += 1; + } + if self.transfer_status_code.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.SIPTransferInfo", len)?; + if !self.transfer_id.is_empty() { + struct_ser.serialize_field("transferId", &self.transfer_id)?; + } + if !self.call_id.is_empty() { + struct_ser.serialize_field("callId", &self.call_id)?; + } + if !self.transfer_to.is_empty() { + struct_ser.serialize_field("transferTo", &self.transfer_to)?; + } + if self.transfer_initiated_at_ns != 0 { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("transferInitiatedAtNs", ToString::to_string(&self.transfer_initiated_at_ns).as_str())?; + } + if self.transfer_completed_at_ns != 0 { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("transferCompletedAtNs", ToString::to_string(&self.transfer_completed_at_ns).as_str())?; + } + if self.transfer_status != 0 { + let v = SipTransferStatus::try_from(self.transfer_status) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.transfer_status)))?; + struct_ser.serialize_field("transferStatus", &v)?; + } + if !self.error.is_empty() { + struct_ser.serialize_field("error", &self.error)?; + } + if let Some(v) = self.transfer_status_code.as_ref() { + struct_ser.serialize_field("transferStatusCode", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for SipTransferInfo { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "transfer_id", + "transferId", + "call_id", + "callId", + "transfer_to", + "transferTo", + "transfer_initiated_at_ns", + "transferInitiatedAtNs", + "transfer_completed_at_ns", + "transferCompletedAtNs", + "transfer_status", + "transferStatus", + "error", + "transfer_status_code", + "transferStatusCode", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + TransferId, + CallId, + TransferTo, + TransferInitiatedAtNs, + TransferCompletedAtNs, + TransferStatus, + Error, + TransferStatusCode, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "transferId" | "transfer_id" => Ok(GeneratedField::TransferId), + "callId" | "call_id" => Ok(GeneratedField::CallId), + "transferTo" | "transfer_to" => Ok(GeneratedField::TransferTo), + "transferInitiatedAtNs" | "transfer_initiated_at_ns" => Ok(GeneratedField::TransferInitiatedAtNs), + "transferCompletedAtNs" | "transfer_completed_at_ns" => Ok(GeneratedField::TransferCompletedAtNs), + "transferStatus" | "transfer_status" => Ok(GeneratedField::TransferStatus), + "error" => Ok(GeneratedField::Error), + "transferStatusCode" | "transfer_status_code" => Ok(GeneratedField::TransferStatusCode), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = SipTransferInfo; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.SIPTransferInfo") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut transfer_id__ = None; + let mut call_id__ = None; + let mut transfer_to__ = None; + let mut transfer_initiated_at_ns__ = None; + let mut transfer_completed_at_ns__ = None; + let mut transfer_status__ = None; + let mut error__ = None; + let mut transfer_status_code__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::TransferId => { + if transfer_id__.is_some() { + return Err(serde::de::Error::duplicate_field("transferId")); + } + transfer_id__ = Some(map_.next_value()?); + } + GeneratedField::CallId => { + if call_id__.is_some() { + return Err(serde::de::Error::duplicate_field("callId")); + } + call_id__ = Some(map_.next_value()?); + } + GeneratedField::TransferTo => { + if transfer_to__.is_some() { + return Err(serde::de::Error::duplicate_field("transferTo")); + } + transfer_to__ = Some(map_.next_value()?); + } + GeneratedField::TransferInitiatedAtNs => { + if transfer_initiated_at_ns__.is_some() { + return Err(serde::de::Error::duplicate_field("transferInitiatedAtNs")); + } + transfer_initiated_at_ns__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::TransferCompletedAtNs => { + if transfer_completed_at_ns__.is_some() { + return Err(serde::de::Error::duplicate_field("transferCompletedAtNs")); + } + transfer_completed_at_ns__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::TransferStatus => { + if transfer_status__.is_some() { + return Err(serde::de::Error::duplicate_field("transferStatus")); + } + transfer_status__ = Some(map_.next_value::()? as i32); + } + GeneratedField::Error => { + if error__.is_some() { + return Err(serde::de::Error::duplicate_field("error")); + } + error__ = Some(map_.next_value()?); + } + GeneratedField::TransferStatusCode => { + if transfer_status_code__.is_some() { + return Err(serde::de::Error::duplicate_field("transferStatusCode")); + } + transfer_status_code__ = map_.next_value()?; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(SipTransferInfo { + transfer_id: transfer_id__.unwrap_or_default(), + call_id: call_id__.unwrap_or_default(), + transfer_to: transfer_to__.unwrap_or_default(), + transfer_initiated_at_ns: transfer_initiated_at_ns__.unwrap_or_default(), + transfer_completed_at_ns: transfer_completed_at_ns__.unwrap_or_default(), + transfer_status: transfer_status__.unwrap_or_default(), + error: error__.unwrap_or_default(), + transfer_status_code: transfer_status_code__, + }) + } + } + deserializer.deserialize_struct("livekit.SIPTransferInfo", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for SipTransferStatus { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + let variant = match self { + Self::StsTransferOngoing => "STS_TRANSFER_ONGOING", + Self::StsTransferFailed => "STS_TRANSFER_FAILED", + Self::StsTransferSuccessful => "STS_TRANSFER_SUCCESSFUL", + }; + serializer.serialize_str(variant) + } +} +impl<'de> serde::Deserialize<'de> for SipTransferStatus { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "STS_TRANSFER_ONGOING", + "STS_TRANSFER_FAILED", + "STS_TRANSFER_SUCCESSFUL", + ]; + + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = SipTransferStatus; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + fn visit_i64(self, v: i64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self) + }) + } + + fn visit_u64(self, v: u64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(v), &self) + }) + } + + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "STS_TRANSFER_ONGOING" => Ok(SipTransferStatus::StsTransferOngoing), + "STS_TRANSFER_FAILED" => Ok(SipTransferStatus::StsTransferFailed), + "STS_TRANSFER_SUCCESSFUL" => Ok(SipTransferStatus::StsTransferSuccessful), + _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), + } + } + } + deserializer.deserialize_any(GeneratedVisitor) + } +} impl serde::Serialize for SipTransport { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -30518,6 +31172,9 @@ impl serde::Serialize for SignalResponse { signal_response::Message::TrackSubscribed(v) => { struct_ser.serialize_field("trackSubscribed", v)?; } + signal_response::Message::RoomMoved(v) => { + struct_ser.serialize_field("roomMoved", v)?; + } } } struct_ser.end() @@ -30565,6 +31222,8 @@ impl<'de> serde::Deserialize<'de> for SignalResponse { "requestResponse", "track_subscribed", "trackSubscribed", + "room_moved", + "roomMoved", ]; #[allow(clippy::enum_variant_names)] @@ -30591,6 +31250,7 @@ impl<'de> serde::Deserialize<'de> for SignalResponse { SubscriptionResponse, RequestResponse, TrackSubscribed, + RoomMoved, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -30635,6 +31295,7 @@ impl<'de> serde::Deserialize<'de> for SignalResponse { "subscriptionResponse" | "subscription_response" => Ok(GeneratedField::SubscriptionResponse), "requestResponse" | "request_response" => Ok(GeneratedField::RequestResponse), "trackSubscribed" | "track_subscribed" => Ok(GeneratedField::TrackSubscribed), + "roomMoved" | "room_moved" => Ok(GeneratedField::RoomMoved), _ => Ok(GeneratedField::__SkipField__), } } @@ -30807,6 +31468,13 @@ impl<'de> serde::Deserialize<'de> for SignalResponse { return Err(serde::de::Error::duplicate_field("trackSubscribed")); } message__ = map_.next_value::<::std::option::Option<_>>()?.map(signal_response::Message::TrackSubscribed) +; + } + GeneratedField::RoomMoved => { + if message__.is_some() { + return Err(serde::de::Error::duplicate_field("roomMoved")); + } + message__ = map_.next_value::<::std::option::Option<_>>()?.map(signal_response::Message::RoomMoved) ; } GeneratedField::__SkipField__ => { From f2dc6202b770239d74462fe201b29e7d1243dda5 Mon Sep 17 00:00:00 2001 From: Daisuke Murase Date: Fri, 16 May 2025 09:19:42 -0700 Subject: [PATCH 196/274] feat: Region pinning support (#631) * impl connection to region url * fmt * - Move connection fallback functionality to SignalClient - Don't try to fetch region url until first connection is failed * add error log if the default url returns an error except 403 * forgot to update this --- Cargo.lock | 63 ++++++++++++---------- livekit-api/src/signal_client/mod.rs | 42 ++++++++++++--- livekit-api/src/signal_client/region.rs | 70 +++++++++++++++++++++++++ 3 files changed, 140 insertions(+), 35 deletions(-) create mode 100644 livekit-api/src/signal_client/region.rs diff --git a/Cargo.lock b/Cargo.lock index 99c2b485d..2e92caeb7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -667,7 +667,7 @@ dependencies = [ "openssl-probe", "openssl-sys", "schannel", - "socket2 0.5.5", + "socket2 0.5.9", "windows-sys 0.52.0", ] @@ -1229,9 +1229,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.8.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "httpdate" @@ -1262,7 +1262,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.5.5", + "socket2 0.4.10", "tokio", "tower-service", "tracing", @@ -1500,10 +1500,11 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.66" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -1543,9 +1544,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.169" +version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libloading" @@ -2700,9 +2701,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.2" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" [[package]] name = "socket2" @@ -2716,12 +2717,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.5" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -2902,7 +2903,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.5", + "socket2 0.5.9", "tokio-macros", "tracing", "windows-sys 0.48.0", @@ -3042,15 +3043,15 @@ dependencies = [ [[package]] name = "tower-layer" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" @@ -3280,23 +3281,24 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.89" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", + "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.89" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", "syn 2.0.50", @@ -3317,9 +3319,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.89" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3327,9 +3329,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.89" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", @@ -3340,9 +3342,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.89" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "web-sys" diff --git a/livekit-api/src/signal_client/mod.rs b/livekit-api/src/signal_client/mod.rs index 2a8cb16ff..13f89e8e8 100644 --- a/livekit-api/src/signal_client/mod.rs +++ b/livekit-api/src/signal_client/mod.rs @@ -37,8 +37,11 @@ use async_tungstenite::tungstenite::Error as WsError; use crate::{http_client, signal_client::signal_stream::SignalStream}; +mod region; mod signal_stream; +pub use region::RegionUrlProvider; + pub type SignalEmitter = mpsc::UnboundedSender; pub type SignalEvents = mpsc::UnboundedReceiver; pub type SignalResult = Result; @@ -62,6 +65,8 @@ pub enum SignalError { Timeout(String), #[error("failed to send message to the server")] SendError, + #[error("failed to retrieve region info: {0}")] + RegionError(String), } #[derive(Debug, Clone)] @@ -136,14 +141,39 @@ impl SignalClient { token: &str, options: SignalOptions, ) -> SignalResult<(Self, proto::JoinResponse, SignalEvents)> { - let (inner, join_response, stream_events) = - SignalInner::connect(url, token, options).await?; + let handle_success = |inner: Arc, join_response, stream_events| { + let (emitter, events) = mpsc::unbounded_channel(); + let signal_task = + livekit_runtime::spawn(signal_task(inner.clone(), emitter.clone(), stream_events)); - let (emitter, events) = mpsc::unbounded_channel(); - let signal_task = - livekit_runtime::spawn(signal_task(inner.clone(), emitter.clone(), stream_events)); + (Self { inner, emitter, handle: Mutex::new(Some(signal_task)) }, join_response, events) + }; - Ok((Self { inner, emitter, handle: Mutex::new(Some(signal_task)) }, join_response, events)) + match SignalInner::connect(url, token, options.clone()).await { + Ok((inner, join_response, stream_events)) => { + return Ok(handle_success(inner, join_response, stream_events)) + } + Err(err) => { + // fallback to region urls + if matches!(&err, SignalError::WsError(WsError::Http(e)) if e.status() != 403) { + log::error!("unexpected signal error: {}", err.to_string()); + } + let urls = RegionUrlProvider::fetch_region_urls(url.into(), token.into()).await?; + let mut last_err = err; + + for url in urls.iter() { + log::info!("fallback connection to: {}", url); + match SignalInner::connect(url, token, options.clone()).await { + Ok((inner, join_response, stream_events)) => { + return Ok(handle_success(inner, join_response, stream_events)) + } + Err(err) => last_err = err, + } + } + + Err(last_err) + } + } } /// Restart the connection to the server diff --git a/livekit-api/src/signal_client/region.rs b/livekit-api/src/signal_client/region.rs new file mode 100644 index 000000000..09b710105 --- /dev/null +++ b/livekit-api/src/signal_client/region.rs @@ -0,0 +1,70 @@ +use http::header::{HeaderMap, HeaderValue, AUTHORIZATION}; +use serde::Deserialize; + +use crate::http_client; + +use super::{get_livekit_url, SignalError, SignalResult}; + +pub struct RegionUrlProvider; + +#[derive(Deserialize)] +pub struct RegionUrlResponse { + pub regions: Vec, +} + +#[derive(Deserialize)] +pub struct RegionUrlInfo { + pub region: String, + pub url: String, + pub distance: String, +} + +impl RegionUrlProvider { + pub async fn fetch_region_urls(url: &str, token: &str) -> SignalResult> { + if is_cloud_url(url)? { + let client = http_client::Client::new(); + let mut headers = HeaderMap::new(); + headers.insert( + AUTHORIZATION, + HeaderValue::from_str(&format!("Bearer {}", token)).unwrap(), + ); + let res = client + .get(region_endpoint(url)?) + .headers(headers) + .send() + .await + .map_err(|e| SignalError::RegionError(e.to_string()))?; + let res = res + .json::() + .await + .map_err(|e| SignalError::RegionError(e.to_string()))?; + Ok(res.regions.into_iter().map(|i| i.url).collect()) + } else { + Ok(vec![]) + } + } +} + +fn is_cloud_url(url: &str) -> SignalResult { + let url = url::Url::parse(url).map_err(|err| SignalError::UrlParse(err.to_string()))?; + let host = match url.host() { + Some(host) => host.to_string(), + None => { + return Err(SignalError::UrlParse("invalid hostname".into())); + } + }; + + Ok(host.ends_with(".livekit.cloud") || host.ends_with(".livekit.run")) +} + +fn region_endpoint(url: &str) -> SignalResult { + let mut url = url::Url::parse(url).map_err(|err| SignalError::UrlParse(err.to_string()))?; + match url.scheme() { + "wss" => url.set_scheme("https").unwrap(), + "ws" => url.set_scheme("http").unwrap(), + _ => (), + } + url.set_path("/settings/regions"); + + Ok(url.to_string()) +} From 696ea4a1e98fcd81b43ffba7d07484ae0a2fae82 Mon Sep 17 00:00:00 2001 From: Daisuke Murase Date: Fri, 16 May 2025 09:42:13 -0700 Subject: [PATCH 197/274] bump livekit-api and livekit-ffi (#644) --- Cargo.lock | 4 ++-- Cargo.toml | 4 ++-- livekit-api/Cargo.toml | 2 +- livekit-ffi/Cargo.toml | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2e92caeb7..f0b09edec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1640,7 +1640,7 @@ dependencies = [ [[package]] name = "livekit-api" -version = "0.4.2" +version = "0.4.3" dependencies = [ "async-tungstenite", "base64", @@ -1670,7 +1670,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.12.24" +version = "0.12.25" dependencies = [ "bytes", "console-subscriber", diff --git a/Cargo.toml b/Cargo.toml index c99606dd1..1749f9984 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,8 +18,8 @@ members = [ imgproc = { version = "0.3.12", path = "imgproc" } yuv-sys = { version = "0.3.7", path = "yuv-sys" } libwebrtc = { version = "0.3.10", path = "libwebrtc" } -livekit-api = { version = "0.4.2", path = "livekit-api" } -livekit-ffi = { version = "0.12.24", path = "livekit-ffi" } +livekit-api = { version = "0.4.3", path = "livekit-api" } +livekit-ffi = { version = "0.12.25", path = "livekit-ffi" } livekit-protocol = { version = "0.3.10", path = "livekit-protocol" } livekit-runtime = { version = "0.4.0", path = "livekit-runtime" } livekit = { version = "0.7.11", path = "livekit" } diff --git a/livekit-api/Cargo.toml b/livekit-api/Cargo.toml index e90083fb5..c6fb31023 100644 --- a/livekit-api/Cargo.toml +++ b/livekit-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-api" -version = "0.4.2" +version = "0.4.3" license = "Apache-2.0" description = "Rust Server SDK for LiveKit" edition = "2021" diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index e8baaf6fe..5f20945d9 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.12.24" +version = "0.12.25" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" From 318c784f345ab978894bd5af31d6890163dbe6c2 Mon Sep 17 00:00:00 2001 From: CloudWebRTC Date: Tue, 20 May 2025 11:02:50 +0800 Subject: [PATCH 198/274] try to fix webrtc build for iOS/macOS. (#646) * Update webrtc-builds.yml * Update webrtc-builds.yml * Update webrtc-builds.yml * bump version for libwebrtc * Update webrtc-builds.yml --- .github/workflows/webrtc-builds.yml | 8 ++++++-- webrtc-sys/build/src/lib.rs | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/webrtc-builds.yml b/.github/workflows/webrtc-builds.yml index 3f17bc8eb..def67d31d 100644 --- a/.github/workflows/webrtc-builds.yml +++ b/.github/workflows/webrtc-builds.yml @@ -51,7 +51,7 @@ jobs: arch: x64 - name: linux - os: ubuntu-24.04-arm + os: ubuntu-latest cmd: ./build_linux.sh arch: arm64 @@ -109,7 +109,11 @@ jobs: submodules: true - uses: actions/setup-python@v4 - - run: pip3 install setuptools # pkg_resources is sometimes not found? + + - name: install setuptools (none-macOS) + if: ${{ matrix.target.os != 'macos-latest' }} + run: | + pip3 install setuptools # pkg_resources is sometimes not found? - name: Install linux dependencies if: ${{ matrix.target.os == 'ubuntu-latest' }} diff --git a/webrtc-sys/build/src/lib.rs b/webrtc-sys/build/src/lib.rs index a38d740e0..1dfc6cedf 100644 --- a/webrtc-sys/build/src/lib.rs +++ b/webrtc-sys/build/src/lib.rs @@ -26,7 +26,7 @@ use regex::Regex; use reqwest::StatusCode; pub const SCRATH_PATH: &str = "livekit_webrtc"; -pub const WEBRTC_TAG: &str = "webrtc-b99fd2c-6"; +pub const WEBRTC_TAG: &str = "webrtc-7ec4c03"; pub const IGNORE_DEFINES: [&str; 2] = ["CR_CLANG_REVISION", "CR_XCODE_VERSION"]; pub fn target_os() -> String { From 32c73889b29b1de83bd8b297e02cfc723b3f0e51 Mon Sep 17 00:00:00 2001 From: Daisuke Murase Date: Mon, 19 May 2025 21:06:09 -0700 Subject: [PATCH 199/274] use workspace version for easier version management (#645) * use workspace version for easier version management * fix ffi as well --- livekit-api/Cargo.toml | 4 ++-- livekit-ffi/Cargo.toml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/livekit-api/Cargo.toml b/livekit-api/Cargo.toml index c6fb31023..6b3d6fc90 100644 --- a/livekit-api/Cargo.toml +++ b/livekit-api/Cargo.toml @@ -69,7 +69,7 @@ rustls-tls-webpki-roots = [ __rustls-tls = ["tokio-tungstenite?/__rustls-tls", "reqwest?/__rustls"] [dependencies] -livekit-protocol = { path = "../livekit-protocol", version = "0.3.9" } +livekit-protocol = { workspace = true } thiserror = "1.0" serde = { version = "1.0", features = ["derive"] } sha2 = "0.10" @@ -87,7 +87,7 @@ base64 = { version = "0.21", optional = true, features = ["std"] } jsonwebtoken = { version = "9", default-features = false, optional = true } # signal_client -livekit-runtime = { path = "../livekit-runtime", version = "0.4.0", optional = true} +livekit-runtime = { workspace = true, optional = true} tokio-tungstenite = { version = "0.20", optional = true } async-tungstenite = { version = "0.25.0", features = [ "async-std-runtime", "async-native-tls"], optional = true } tokio = { version = "1", default-features = false, features = ["sync", "macros", "signal", "io-util", "net"], optional = true } diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 5f20945d9..118048dc6 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -40,10 +40,10 @@ bytes = "1.10.1" jni = "0.21.1" [build-dependencies] -webrtc-sys-build = { path = "../webrtc-sys/build", version = "0.3.5" } +webrtc-sys-build = { workspace = true } [dev-dependencies] -livekit-api = { path = "../livekit-api", version = "0.4.1" } +livekit-api = { workspace = true } [lib] crate-type = ["lib", "cdylib"] From ba342c21f149269b2712505d572b63547268d5c8 Mon Sep 17 00:00:00 2001 From: CloudWebRTC Date: Tue, 3 Jun 2025 18:03:48 +0800 Subject: [PATCH 200/274] bump version for webrtc (fix win CI) (#650) * bump version for webrtc (fix win CI) * change for test. * revert changes. --- webrtc-sys/build/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webrtc-sys/build/src/lib.rs b/webrtc-sys/build/src/lib.rs index 1dfc6cedf..dec6412e7 100644 --- a/webrtc-sys/build/src/lib.rs +++ b/webrtc-sys/build/src/lib.rs @@ -26,7 +26,7 @@ use regex::Regex; use reqwest::StatusCode; pub const SCRATH_PATH: &str = "livekit_webrtc"; -pub const WEBRTC_TAG: &str = "webrtc-7ec4c03"; +pub const WEBRTC_TAG: &str = "webrtc-ed96590"; pub const IGNORE_DEFINES: [&str; 2] = ["CR_CLANG_REVISION", "CR_XCODE_VERSION"]; pub fn target_os() -> String { From 4b6415587309e7e848c613004afbf11be0acdeb6 Mon Sep 17 00:00:00 2001 From: Daisuke Murase Date: Wed, 4 Jun 2025 08:17:07 -0700 Subject: [PATCH 201/274] Remove debouncer when fast_publish is enabled (#649) * add debounce_queue and use it when fast_publish is true * tmp * fmt & fix logs --- livekit/src/rtc_engine/rtc_session.rs | 133 +++++++++++++++++++++++++- 1 file changed, 131 insertions(+), 2 deletions(-) diff --git a/livekit/src/rtc_engine/rtc_session.rs b/livekit/src/rtc_engine/rtc_session.rs index ce1cd61ee..3ea40ed50 100644 --- a/livekit/src/rtc_engine/rtc_session.rs +++ b/livekit/src/rtc_engine/rtc_session.rs @@ -35,7 +35,7 @@ use proto::{ SignalTarget, }; use serde::{Deserialize, Serialize}; -use tokio::sync::{mpsc, oneshot, watch}; +use tokio::sync::{mpsc, oneshot, watch, Notify}; use super::{rtc_events, EngineError, EngineOptions, EngineResult, SimulateScenario}; use crate::{id::ParticipantIdentity, ChatMessage, TranscriptionSegment}; @@ -60,6 +60,31 @@ pub const RELIABLE_DC_LABEL: &str = "_reliable"; pub const PUBLISHER_NEGOTIATION_FREQUENCY: Duration = Duration::from_millis(150); pub const INITIAL_BUFFERED_AMOUNT_LOW_THRESHOLD: u64 = 2 * 1024 * 1024; +#[derive(Debug)] +enum NegotiationState { + Idle, + InProgress, + PendingRetry, +} + +struct NegotiationQueue { + state: Arc>, + waker: Arc, + task_running: AtomicBool, + waiting_for_answer: AtomicBool, +} + +impl NegotiationQueue { + fn new() -> Self { + Self { + state: Arc::new(Mutex::new(NegotiationState::Idle)), + waker: Arc::new(Notify::new()), + task_running: AtomicBool::new(false), + waiting_for_answer: AtomicBool::new(false), + } + } +} + pub type SessionEmitter = mpsc::UnboundedSender; pub type SessionEvents = mpsc::UnboundedReceiver; @@ -176,6 +201,7 @@ struct IceCandidateJson { struct SessionInner { signal_client: Arc, has_published: AtomicBool, + fast_publish: AtomicBool, publisher_pc: PeerTransport, subscriber_pc: PeerTransport, @@ -200,6 +226,7 @@ struct SessionInner { options: EngineOptions, negotiation_debouncer: Mutex>, + negotiation_queue: NegotiationQueue, pending_requests: Mutex>>, } @@ -276,8 +303,10 @@ impl RtcSession { rtc_events::forward_dc_events(&mut reliable_dc, DataPacketKind::Reliable, rtc_emitter); let (close_tx, close_rx) = watch::channel(false); + let inner = Arc::new(SessionInner { has_published: Default::default(), + fast_publish: AtomicBool::new(join_response.fast_publish), signal_client, publisher_pc, subscriber_pc, @@ -297,6 +326,7 @@ impl RtcSession { emitter, options, negotiation_debouncer: Default::default(), + negotiation_queue: NegotiationQueue::new(), pending_requests: Default::default(), }); @@ -634,6 +664,13 @@ impl SessionInner { let answer = SessionDescription::parse(&answer.sdp, answer.r#type.parse().unwrap()).unwrap(); // Unwrap is ok, the server shouldn't give us an invalid sdp self.publisher_pc.set_remote_description(answer).await?; + + if self.fast_publish.load(Ordering::Acquire) { + if self.negotiation_queue.waiting_for_answer.swap(false, Ordering::AcqRel) { + log::debug!("answer received, notifying negotiation loop"); + self.negotiation_queue.waker.notify_one(); + } + } } proto::signal_response::Message::Offer(offer) => { log::debug!("received subscriber offer: {:?}", offer); @@ -1199,8 +1236,100 @@ impl SessionInner { /// Start publisher negotiation fn publisher_negotiation_needed(self: &Arc) { + let fast_publish = self.fast_publish.load(Ordering::Acquire); self.has_published.store(true, Ordering::Release); + log::debug!("publisher_negotiation_needed: fast_publish={}", fast_publish); + + if fast_publish { + if self.negotiation_queue.waiting_for_answer.load(Ordering::Acquire) { + log::debug!("already waiting for answer, marking for retry"); + let mut state = self.negotiation_queue.state.lock(); + *state = NegotiationState::PendingRetry; + return; + } + self.queue_negotiation(); + } else { + self.debounce_negotiation(); + } + } + + fn queue_negotiation(self: &Arc) { + let mut state = self.negotiation_queue.state.lock(); + + match *state { + NegotiationState::Idle => { + if self.negotiation_queue.task_running.swap(true, Ordering::AcqRel) { + log::debug!("queue_negotiation: task already running, marking for retry"); + *state = NegotiationState::PendingRetry; + return; + } + + log::debug!("queue_negotiation: starting new negotiation"); + *state = NegotiationState::InProgress; + drop(state); + + let session = self.clone(); + livekit_runtime::spawn(async move { + session.execute_negotiation_with_retry().await; + session.negotiation_queue.task_running.store(false, Ordering::Release); + }); + } + NegotiationState::InProgress => { + log::debug!("queue_negotiation: marking for retry"); + *state = NegotiationState::PendingRetry; + } + NegotiationState::PendingRetry => { + log::debug!("queue_negotiation: already pending retry"); + } + } + } + + async fn execute_negotiation_with_retry(self: &Arc) { + loop { + log::debug!("negotiating the publisher (fast mode)"); + + self.negotiation_queue.waiting_for_answer.store(true, Ordering::Release); + + if let Err(err) = self.publisher_pc.create_and_send_offer(OfferOptions::default()).await + { + log::error!("failed to negotiate the publisher: {:?}", err); + self.negotiation_queue.waiting_for_answer.store(false, Ordering::Release); + } else { + log::debug!("offer sent, waiting for answer..."); + + let timeout = tokio::time::sleep(Duration::from_secs(10)); + tokio::pin!(timeout); + + tokio::select! { + _ = self.negotiation_queue.waker.notified() => { + log::debug!("answer received successfully"); + } + _ = &mut timeout => { + log::debug!("timeout waiting for answer"); + self.negotiation_queue.waiting_for_answer.store(false, Ordering::Release); + } + } + } + + let mut state = self.negotiation_queue.state.lock(); + match *state { + NegotiationState::PendingRetry => { + log::debug!("retrying negotiation"); + *state = NegotiationState::InProgress; + drop(state); + continue; + } + _ => { + log::debug!("negotiation completed"); + *state = NegotiationState::Idle; + break; + } + } + } + } + + fn debounce_negotiation(self: &Arc) { let mut debouncer = self.negotiation_debouncer.lock(); // call() returns an error if the debouncer has finished @@ -1208,7 +1337,7 @@ impl SessionInner { let session = self.clone(); *debouncer = Some(debouncer::debounce(PUBLISHER_NEGOTIATION_FREQUENCY, async move { - log::debug!("negotiating the publisher"); + log::debug!("negotiating the publisher (debounced)"); if let Err(err) = session.publisher_pc.create_and_send_offer(OfferOptions::default()).await { From e70bb87578857fa421b4e090136644ed703ac433 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Thu, 5 Jun 2025 10:23:01 -0700 Subject: [PATCH 202/274] add note about building on mac in README (#653) --- README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 884333e13..dd97e9f55 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Use this SDK to add realtime video, audio and data features to your Rust app. By - [x] Publishing tracks - [x] Data channels - [x] Simulcast -- [ ] SVC codecs (AV1/VP9) +- [x] SVC codecs (AV1/VP9) - [ ] Adaptive Streaming - [ ] Dynacast - [x] Hardware video enc/dec @@ -167,6 +167,16 @@ match event { - [play_from_disk](https://github.com/livekit/rust-sdks/tree/main/examples/play_from_disk): publish audio from a wav file - [save_to_disk](https://github.com/livekit/rust-sdks/tree/main/examples/save_to_disk): save received audio to a wav file +## Building + +### MacOS + +When building on MacOS, `-ObjC` linker flag is needed. LiveKit's WebRTC implementation make use of ObjectiveC libraries on the Mac. You may get the following error if the app isn't linked with ObjC: + +``` +*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[RTCVideoCodecInfo nativeSdpVideoFormat]: unrecognized selector sent to instance 0x600003bc6660' +``` + ## Motivation and Design Goals LiveKit aims to provide an open source, end-to-end WebRTC stack that works everywhere. We have two goals in mind with this SDK: From fa727bd8fc7124896155908fbf7474c394daefb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Thu, 5 Jun 2025 20:44:33 +0200 Subject: [PATCH 203/274] fix duration overflow (#654) --- livekit/src/room/participant/local_participant.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/livekit/src/room/participant/local_participant.rs b/livekit/src/room/participant/local_participant.rs index d51c74771..f4a12d627 100644 --- a/livekit/src/room/participant/local_participant.rs +++ b/livekit/src/room/participant/local_participant.rs @@ -738,7 +738,7 @@ impl LocalParticipant { id: id.clone(), method: data.method.clone(), payload: data.payload.clone(), - response_timeout: data.response_timeout - max_round_trip_latency, + response_timeout: data.response_timeout, version: 1, }) .await From 1a238a1acafa5250110144cf5d79c465ff708bba Mon Sep 17 00:00:00 2001 From: CloudWebRTC Date: Tue, 10 Jun 2025 12:28:14 +0800 Subject: [PATCH 204/274] chore: Update dependencies for wgpu room. (#655) --- examples/wgpu_room/Cargo.toml | 10 +++---- examples/wgpu_room/src/app.rs | 4 +-- examples/wgpu_room/src/main.rs | 4 +-- examples/wgpu_room/src/video_grid.rs | 6 ++-- examples/wgpu_room/src/video_renderer.rs | 36 ++++++++++++------------ 5 files changed, 31 insertions(+), 29 deletions(-) diff --git a/examples/wgpu_room/Cargo.toml b/examples/wgpu_room/Cargo.toml index e0d210a5a..1f76dbdd4 100644 --- a/examples/wgpu_room/Cargo.toml +++ b/examples/wgpu_room/Cargo.toml @@ -11,13 +11,13 @@ tracing = ["console-subscriber", "tokio/tracing"] tokio = { version = "1", features = ["full", "parking_lot"] } livekit = { path = "../../livekit", features = ["native-tls"] } futures = "0.3" -winit = "0.28" +winit = "0.30.11" parking_lot = { version = "0.12.1", features = ["deadlock_detection"] } image = "0.24" -wgpu = "0.16" -egui = "0.22.0" -egui-wgpu = "0.22.0" -eframe = { version = "0.22.0", default-features = false, features = ["default_fonts", "wgpu", "persistence"] } +wgpu = "25.0" +egui = "0.31.1" +egui-wgpu = "0.31.1" +eframe = { version = "0.31.1", default-features = false, features = ["default_fonts", "wgpu", "persistence"] } serde = { version = "1", features = ["derive"] } log = "0.4" env_logger = "0.10.0" diff --git a/examples/wgpu_room/src/app.rs b/examples/wgpu_room/src/app.rs index 99001f27a..190dbff21 100644 --- a/examples/wgpu_room/src/app.rs +++ b/examples/wgpu_room/src/app.rs @@ -3,7 +3,7 @@ use crate::{ video_grid::VideoGrid, video_renderer::VideoRenderer, }; -use egui::{Rounding, Stroke}; +use egui::{CornerRadius, Stroke}; use livekit::{e2ee::EncryptionType, prelude::*, SimulateScenario}; use std::collections::HashMap; @@ -428,7 +428,7 @@ fn draw_video(name: &str, speaking: bool, video_renderer: &VideoRenderer, ui: &m if speaking { ui.painter() - .rect(rect, Rounding::none(), egui::Color32::GREEN, Stroke::NONE); + .rect(rect, CornerRadius::default(), egui::Color32::GREEN, Stroke::NONE, egui::StrokeKind::Inside); } // Always draw a background in case we still didn't receive a frame diff --git a/examples/wgpu_room/src/main.rs b/examples/wgpu_room/src/main.rs index ffad45e99..a96d68475 100644 --- a/examples/wgpu_room/src/main.rs +++ b/examples/wgpu_room/src/main.rs @@ -1,4 +1,5 @@ use eframe::Renderer; +use egui::ViewportBuilder; use parking_lot::deadlock; use std::thread; use std::time::Duration; @@ -36,12 +37,11 @@ fn main() { eframe::run_native( "LiveKit - Rust App", eframe::NativeOptions { - follow_system_theme: true, centered: true, renderer: Renderer::Wgpu, ..Default::default() }, - Box::new(|cc| Box::new(app::LkApp::new(cc))), + Box::new(|cc| Ok(Box::new(app::LkApp::new(cc)))), ) .unwrap(); } diff --git a/examples/wgpu_room/src/video_grid.rs b/examples/wgpu_room/src/video_grid.rs index cabc54908..1809b06e5 100644 --- a/examples/wgpu_room/src/video_grid.rs +++ b/examples/wgpu_room/src/video_grid.rs @@ -168,8 +168,10 @@ impl<'a> VideoGridContext<'a> { pub fn video_frame(&mut self, add_contents: impl FnOnce(&mut egui::Ui)) -> egui::Response { let frame_rect = self.layout.next_frame_rect(); - let mut child_ui = self.ui.child_ui(frame_rect, egui::Layout::default()); - add_contents(&mut child_ui); + if self.ui.is_visible() { + let mut child_ui = self.ui.child_ui(frame_rect, egui::Layout::default(), None); + add_contents(&mut child_ui); + } self.ui.allocate_rect(frame_rect, egui::Sense::hover()) } diff --git a/examples/wgpu_room/src/video_renderer.rs b/examples/wgpu_room/src/video_renderer.rs index b45e615c7..962c8cfcc 100644 --- a/examples/wgpu_room/src/video_renderer.rs +++ b/examples/wgpu_room/src/video_renderer.rs @@ -17,8 +17,8 @@ struct RendererInternal { width: u32, height: u32, rgba_data: Vec, - texture: Option, - texture_view: Option, + texture: Option, + texture_view: Option, egui_texture: Option, } @@ -75,18 +75,18 @@ impl VideoRenderer { ); internal.render_state.queue.write_texture( - wgpu::ImageCopyTexture { + eframe::wgpu::TexelCopyTextureInfo { texture: internal.texture.as_ref().unwrap(), mip_level: 0, - origin: wgpu::Origin3d::default(), - aspect: wgpu::TextureAspect::default(), + origin: eframe::wgpu::Origin3d::default(), + aspect: eframe::wgpu::TextureAspect::default(), }, &internal.rgba_data, - wgpu::ImageDataLayout { + eframe::wgpu::TexelCopyBufferLayout { bytes_per_row: Some(width * 4), ..Default::default() }, - wgpu::Extent3d { width, height, ..Default::default() }, + eframe::wgpu::Extent3d { width, height, ..Default::default() }, ); } } @@ -117,22 +117,22 @@ impl RendererInternal { self.height = height; self.rgba_data.resize((width * height * 4) as usize, 0); - self.texture = Some(self.render_state.device.create_texture(&wgpu::TextureDescriptor { + self.texture = Some(self.render_state.device.create_texture(&eframe::wgpu::TextureDescriptor { label: Some("lk-videotexture"), - usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, - dimension: wgpu::TextureDimension::D2, - size: wgpu::Extent3d { width, height, ..Default::default() }, + usage: eframe::wgpu::TextureUsages::TEXTURE_BINDING | eframe::wgpu::TextureUsages::COPY_DST, + dimension: eframe::wgpu::TextureDimension::D2, + size: eframe::wgpu::Extent3d { width, height, ..Default::default() }, sample_count: 1, mip_level_count: 1, - format: wgpu::TextureFormat::Rgba8UnormSrgb, - view_formats: &[wgpu::TextureFormat::Rgba8UnormSrgb], + format: eframe::wgpu::TextureFormat::Rgba8UnormSrgb, + view_formats: &[eframe::wgpu::TextureFormat::Rgba8UnormSrgb], })); self.texture_view = - Some(self.texture.as_mut().unwrap().create_view(&wgpu::TextureViewDescriptor { + Some(self.texture.as_mut().unwrap().create_view(&eframe::wgpu::TextureViewDescriptor { label: Some("lk-videotexture-view"), - format: Some(wgpu::TextureFormat::Rgba8UnormSrgb), - dimension: Some(wgpu::TextureViewDimension::D2), + format: Some(eframe::wgpu::TextureFormat::Rgba8UnormSrgb), + dimension: Some(eframe::wgpu::TextureViewDimension::D2), mip_level_count: Some(1), array_layer_count: Some(1), ..Default::default() @@ -143,14 +143,14 @@ impl RendererInternal { self.render_state.renderer.write().update_egui_texture_from_wgpu_texture( &self.render_state.device, self.texture_view.as_ref().unwrap(), - wgpu::FilterMode::Linear, + eframe::wgpu::FilterMode::Linear, texture_id, ); } else { self.egui_texture = Some(self.render_state.renderer.write().register_native_texture( &self.render_state.device, self.texture_view.as_ref().unwrap(), - wgpu::FilterMode::Linear, + eframe::wgpu::FilterMode::Linear, )); } } From 81a3151ce377d1242423fe6a1b6d07a15f2cb91a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Tue, 10 Jun 2025 22:40:48 +0200 Subject: [PATCH 205/274] add frame-size-ms to AudioStream (#658) --- livekit-ffi/protocol/audio_frame.proto | 2 + livekit-ffi/src/livekit.proto.rs | 4 ++ livekit-ffi/src/server/audio_stream.rs | 83 ++++++++++++++++++++------ 3 files changed, 71 insertions(+), 18 deletions(-) diff --git a/livekit-ffi/protocol/audio_frame.proto b/livekit-ffi/protocol/audio_frame.proto index 6aac005ab..e6d2fbd92 100644 --- a/livekit-ffi/protocol/audio_frame.proto +++ b/livekit-ffi/protocol/audio_frame.proto @@ -29,6 +29,7 @@ message NewAudioStreamRequest { optional uint32 num_channels = 4; optional string audio_filter_module_id = 5; // Unique identifier passed in LoadAudioFilterPluginRequest optional string audio_filter_options = 6; + optional uint32 frame_size_ms = 7; } message NewAudioStreamResponse { required OwnedAudioStream stream = 1; } @@ -40,6 +41,7 @@ message AudioStreamFromParticipantRequest { optional uint32 num_channels = 6; optional string audio_filter_module_id = 7; optional string audio_filter_options = 8; + optional uint32 frame_size_ms = 9; } message AudioStreamFromParticipantResponse { required OwnedAudioStream stream = 1; } diff --git a/livekit-ffi/src/livekit.proto.rs b/livekit-ffi/src/livekit.proto.rs index 39a539617..6eb1f5d03 100644 --- a/livekit-ffi/src/livekit.proto.rs +++ b/livekit-ffi/src/livekit.proto.rs @@ -4126,6 +4126,8 @@ pub struct NewAudioStreamRequest { pub audio_filter_module_id: ::core::option::Option<::prost::alloc::string::String>, #[prost(string, optional, tag="6")] pub audio_filter_options: ::core::option::Option<::prost::alloc::string::String>, + #[prost(uint32, optional, tag="7")] + pub frame_size_ms: ::core::option::Option, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -4150,6 +4152,8 @@ pub struct AudioStreamFromParticipantRequest { pub audio_filter_module_id: ::core::option::Option<::prost::alloc::string::String>, #[prost(string, optional, tag="8")] pub audio_filter_options: ::core::option::Option<::prost::alloc::string::String>, + #[prost(uint32, optional, tag="9")] + pub frame_size_ms: ::core::option::Option, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] diff --git a/livekit-ffi/src/server/audio_stream.rs b/livekit-ffi/src/server/audio_stream.rs index 6ee74fbe7..b9bb2d5b0 100644 --- a/livekit-ffi/src/server/audio_stream.rs +++ b/livekit-ffi/src/server/audio_stream.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::borrow::Cow; use std::time::Duration; use futures_util::StreamExt; @@ -135,6 +136,9 @@ impl FfiAudioStream { server.watch_handle_dropped(new_stream.track_handle), true, info, + new_stream.frame_size_ms, + sample_rate.try_into().unwrap(), + num_channels.try_into().unwrap(), )); server.watch_panic(handle); Ok::(audio_stream) @@ -312,6 +316,9 @@ impl FfiAudioStream { handle_dropped_rx, false, info, + request.frame_size_ms, + sample_rate.try_into().unwrap(), + num_channels.try_into().unwrap(), ) .await; let _ = done_tx.send(()); @@ -348,7 +355,14 @@ impl FfiAudioStream { mut handle_dropped_rx: oneshot::Receiver<()>, send_eos: bool, mut filter_info: Option, + frame_size_ms: Option, + sample_rate: u32, + num_channels: u32, ) { + let mut buf = Vec::new(); + let target_samples = frame_size_ms + .map(|ms| sample_rate as usize * ms as usize / 1000 * num_channels as usize); + loop { tokio::select! { _ = &mut self_dropped_rx => { @@ -377,26 +391,59 @@ impl FfiAudioStream { } } - let handle_id = server.next_id(); - let buffer_info = proto::AudioFrameBufferInfo::from(&frame); - server.store_handle(handle_id, frame); - - if let Err(err) = server.send_event(proto::ffi_event::Message::AudioStreamEvent( - proto::AudioStreamEvent { - stream_handle: stream_handle_id, - message: Some(proto::audio_stream_event::Message::FrameReceived( - proto::AudioFrameReceived { - frame: proto::OwnedAudioFrameBuffer { - handle: proto::FfiOwnedHandle { id: handle_id }, - info: buffer_info, - }, + if let Some(target) = target_samples { + buf.extend_from_slice(&frame.data); + while buf.len() >= target { + let data = buf.split_off(target); + let mut frame_data = std::mem::replace(&mut buf, data); + let new_frame = AudioFrame { + data: Cow::Owned(frame_data), + sample_rate, + num_channels, + samples_per_channel: target as u32 / num_channels, + }; + let handle_id = server.next_id(); + let buffer_info = proto::AudioFrameBufferInfo::from(&new_frame); + server.store_handle(handle_id, new_frame); + if let Err(err) = server.send_event(proto::ffi_event::Message::AudioStreamEvent( + proto::AudioStreamEvent { + stream_handle: stream_handle_id, + message: Some(proto::audio_stream_event::Message::FrameReceived( + proto::AudioFrameReceived { + frame: proto::OwnedAudioFrameBuffer { + handle: proto::FfiOwnedHandle { id: handle_id }, + info: buffer_info, + }, + }, + )), }, - )), - }, - )) { - server.drop_handle(handle_id); - log::warn!("failed to send audio frame: {}", err); + )) { + server.drop_handle(handle_id); + log::warn!("failed to send audio frame: {}", err); + } + } + } else { + let handle_id = server.next_id(); + let buffer_info = proto::AudioFrameBufferInfo::from(&frame); + server.store_handle(handle_id, frame); + if let Err(err) = server.send_event(proto::ffi_event::Message::AudioStreamEvent( + proto::AudioStreamEvent { + stream_handle: stream_handle_id, + message: Some(proto::audio_stream_event::Message::FrameReceived( + proto::AudioFrameReceived { + frame: proto::OwnedAudioFrameBuffer { + handle: proto::FfiOwnedHandle { id: handle_id }, + info: buffer_info, + }, + }, + )), + }, + )) { + server.drop_handle(handle_id); + log::warn!("failed to send audio frame: {}", err); + } } + } } } From b611791c7bf47c2af59124a0e9c3d7b2cc85b2dc Mon Sep 17 00:00:00 2001 From: Daisuke Murase Date: Tue, 10 Jun 2025 15:14:22 -0700 Subject: [PATCH 206/274] bump ffi version (#659) --- Cargo.toml | 2 +- livekit-ffi/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1749f9984..b1b209dab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ imgproc = { version = "0.3.12", path = "imgproc" } yuv-sys = { version = "0.3.7", path = "yuv-sys" } libwebrtc = { version = "0.3.10", path = "libwebrtc" } livekit-api = { version = "0.4.3", path = "livekit-api" } -livekit-ffi = { version = "0.12.25", path = "livekit-ffi" } +livekit-ffi = { version = "0.12.26", path = "livekit-ffi" } livekit-protocol = { version = "0.3.10", path = "livekit-protocol" } livekit-runtime = { version = "0.4.0", path = "livekit-runtime" } livekit = { version = "0.7.11", path = "livekit" } diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 118048dc6..171448238 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.12.25" +version = "0.12.26" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" From c1e6a300947480d4d70daad5cb537dff8a06866b Mon Sep 17 00:00:00 2001 From: Daisuke Murase Date: Tue, 10 Jun 2025 17:55:57 -0700 Subject: [PATCH 207/274] Setup release-plz to automate changelog creation and cretes publishing (#657) * add release-plz config * add workflow * add owner guard * add livekit-ffi * Revert "add livekit-ffi" This reverts commit dbcfea74fbb107b351eeb12d6b0d51d59279e087. --- .github/workflows/release.yml | 59 +++++++++++++++++++++++++++++++++++ release-plz.toml | 52 ++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 .github/workflows/release.yml create mode 100644 release-plz.toml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..7c1423569 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,59 @@ +name: Release + +permissions: + pull-requests: write + contents: write + +on: + push: + branches: + - main + +jobs: + # Release unpublished packages. + release-plz-release: + name: Release-plz release + runs-on: ubuntu-latest + if: ${{ github.repository_owner == 'livekit' }} + permissions: + contents: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: recursive + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + - name: Run release-plz + uses: release-plz/action@v0.5 + with: + command: release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + + # Create a PR with the new versions and changelog, preparing the next release. + release-plz-pr: + name: Release-plz PR + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + concurrency: + group: release-plz-${{ github.ref }} + cancel-in-progress: false + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + - name: Run release-plz + uses: release-plz/action@v0.5 + with: + command: release-pr + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} diff --git a/release-plz.toml b/release-plz.toml new file mode 100644 index 000000000..8f675b05b --- /dev/null +++ b/release-plz.toml @@ -0,0 +1,52 @@ +[workspace] +publish = false +release = false +git_tag_name = "rust-sdks/{{ package }}@{{ version }}" + +[[package]] +name = "imgproc" +changelog_path = "imgproc/CHANGELOG.md" +publish = true +release = true + +[[package]] +name = "libwebrtc" +changelog_path = "libwebrtc/CHANGELOG.md" +publish = true +release = true + +[[package]] +name = "livekit-api" +changelog_path = "livekit-api/CHANGELOG.md" +publish = true +release = true + +[[package]] +name = "livekit-protocol" +changelog_path = "livekit-protocol/CHANGELOG.md" +publish = true +release = true + +# [[package]] +# name = "livekit-runtime" +# changelog_path = "livekit-runtime/CHANGELOG.md" +# publish = true +# release = true + +[[package]] +name = "livekit" +changelog_path = "livekit/CHANGELOG.md" +publish = true +release = true + +[[package]] +name = "webrtc-sys" +changelog_path = "webrtc-sys/CHANGELOG.md" +publish = true +release = true + +[[package]] +name = "yuv-sys" +changelog_path = "yuv-sys/CHANGELOG.md" +publish = true +release = true From 74a0596738913b8cbc2e6290c2626e08a602397b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 10 Jun 2025 18:21:31 -0700 Subject: [PATCH 208/274] chore: release (#660) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- Cargo.toml | 6 +++--- libwebrtc/CHANGELOG.md | 14 ++++++++++++++ libwebrtc/Cargo.toml | 2 +- livekit/CHANGELOG.md | 10 ++++++++++ livekit/Cargo.toml | 2 +- webrtc-sys/CHANGELOG.md | 16 ++++++++++++++++ webrtc-sys/Cargo.toml | 2 +- 8 files changed, 50 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f0b09edec..78f59cf69 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1560,7 +1560,7 @@ dependencies = [ [[package]] name = "libwebrtc" -version = "0.3.10" +version = "0.3.11" dependencies = [ "cxx", "env_logger", @@ -1616,7 +1616,7 @@ checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "livekit" -version = "0.7.11" +version = "0.7.12" dependencies = [ "bmrng", "bytes", @@ -1670,7 +1670,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.12.25" +version = "0.12.26" dependencies = [ "bytes", "console-subscriber", @@ -3367,7 +3367,7 @@ checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" [[package]] name = "webrtc-sys" -version = "0.3.7" +version = "0.3.8" dependencies = [ "cc", "cxx", diff --git a/Cargo.toml b/Cargo.toml index b1b209dab..022d1279c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,12 +17,12 @@ members = [ [workspace.dependencies] imgproc = { version = "0.3.12", path = "imgproc" } yuv-sys = { version = "0.3.7", path = "yuv-sys" } -libwebrtc = { version = "0.3.10", path = "libwebrtc" } +libwebrtc = { version = "0.3.11", path = "libwebrtc" } livekit-api = { version = "0.4.3", path = "livekit-api" } livekit-ffi = { version = "0.12.26", path = "livekit-ffi" } livekit-protocol = { version = "0.3.10", path = "livekit-protocol" } livekit-runtime = { version = "0.4.0", path = "livekit-runtime" } -livekit = { version = "0.7.11", path = "livekit" } +livekit = { version = "0.7.12", path = "livekit" } soxr-sys = { version = "0.1.0", path = "soxr-sys" } webrtc-sys-build = { version = "0.3.6", path = "webrtc-sys/build" } -webrtc-sys = { version = "0.3.7", path = "webrtc-sys" } +webrtc-sys = { version = "0.3.8", path = "webrtc-sys" } diff --git a/libwebrtc/CHANGELOG.md b/libwebrtc/CHANGELOG.md index 78433a7e8..2e209b2da 100644 --- a/libwebrtc/CHANGELOG.md +++ b/libwebrtc/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## [0.3.11](https://github.com/livekit/rust-sdks/compare/rust-sdks/libwebrtc@0.3.10...rust-sdks/libwebrtc@0.3.11) - 2025-06-11 + +### Fixed + +- fix uint32 overflow ([#615](https://github.com/livekit/rust-sdks/pull/615)) + +### Other + +- remove ([#633](https://github.com/livekit/rust-sdks/pull/633)) +- expose apm stream_delay ([#616](https://github.com/livekit/rust-sdks/pull/616)) +- Add i420_to_nv12 ([#605](https://github.com/livekit/rust-sdks/pull/605)) +- ffi-v0.13.0 ([#590](https://github.com/livekit/rust-sdks/pull/590)) +- add AudioProcessingModule ([#580](https://github.com/livekit/rust-sdks/pull/580)) + ## [0.3.10] - 2025-02-05 ### Fixed diff --git a/libwebrtc/Cargo.toml b/libwebrtc/Cargo.toml index fa3f44412..4c58b6511 100644 --- a/libwebrtc/Cargo.toml +++ b/libwebrtc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libwebrtc" -version = "0.3.10" +version = "0.3.11" edition = "2021" homepage = "https://livekit.io" license = "Apache-2.0" diff --git a/livekit/CHANGELOG.md b/livekit/CHANGELOG.md index db344d4d1..c5e0c5fc2 100644 --- a/livekit/CHANGELOG.md +++ b/livekit/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## [0.7.12](https://github.com/livekit/rust-sdks/compare/rust-sdks/livekit@0.7.11...rust-sdks/livekit@0.7.12) - 2025-06-11 + +### Fixed + +- fix duration overflow ([#654](https://github.com/livekit/rust-sdks/pull/654)) + +### Other + +- Remove debouncer when fast_publish is enabled ([#649](https://github.com/livekit/rust-sdks/pull/649)) + ## [0.7.9] - 2025-04-08 ### Added diff --git a/livekit/Cargo.toml b/livekit/Cargo.toml index 58fb4c334..dc3b7979f 100644 --- a/livekit/Cargo.toml +++ b/livekit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit" -version = "0.7.11" +version = "0.7.12" edition = "2021" license = "Apache-2.0" description = "Rust Client SDK for LiveKit" diff --git a/webrtc-sys/CHANGELOG.md b/webrtc-sys/CHANGELOG.md index 6e1609449..c21623b2e 100644 --- a/webrtc-sys/CHANGELOG.md +++ b/webrtc-sys/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## [0.3.8](https://github.com/livekit/rust-sdks/compare/rust-sdks/webrtc-sys@0.3.7...rust-sdks/webrtc-sys@0.3.8) - 2025-06-11 + +### Fixed + +- fix libwebrtc.jar build issue ([#586](https://github.com/livekit/rust-sdks/pull/586)) + +### Other + +- bump version for webrtc (fix win CI) ([#650](https://github.com/livekit/rust-sdks/pull/650)) +- try to fix webrtc build for iOS/macOS. ([#646](https://github.com/livekit/rust-sdks/pull/646)) +- remove ([#633](https://github.com/livekit/rust-sdks/pull/633)) +- expose apm stream_delay ([#616](https://github.com/livekit/rust-sdks/pull/616)) +- Add i420_to_nv12 ([#605](https://github.com/livekit/rust-sdks/pull/605)) +- ffi-v0.13.0 ([#590](https://github.com/livekit/rust-sdks/pull/590)) +- add AudioProcessingModule ([#580](https://github.com/livekit/rust-sdks/pull/580)) + ## [0.3.7] - 2025-02-05 ### Added diff --git a/webrtc-sys/Cargo.toml b/webrtc-sys/Cargo.toml index 7882a0616..c1fa7cd81 100644 --- a/webrtc-sys/Cargo.toml +++ b/webrtc-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "webrtc-sys" -version = "0.3.7" +version = "0.3.8" edition = "2021" homepage = "https://livekit.io" license = "Apache-2.0" From fd7866cc4823f31282d34f00231b5b527a2878e8 Mon Sep 17 00:00:00 2001 From: Daisuke Murase Date: Wed, 11 Jun 2025 10:35:22 -0700 Subject: [PATCH 209/274] fix env (#661) --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7c1423569..492750fcb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,7 +31,7 @@ jobs: command: release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_TOKEN }} # Create a PR with the new versions and changelog, preparing the next release. release-plz-pr: @@ -56,4 +56,4 @@ jobs: command: release-pr env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_TOKEN }} From aa945dd17b6b6dbd5cedf59b4deea4350b94a49a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Mon, 16 Jun 2025 20:07:44 +0200 Subject: [PATCH 210/274] use path.join instead of hardcoded `/` (#663) --- webrtc-sys/build/src/lib.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/webrtc-sys/build/src/lib.rs b/webrtc-sys/build/src/lib.rs index dec6412e7..924cd7b51 100644 --- a/webrtc-sys/build/src/lib.rs +++ b/webrtc-sys/build/src/lib.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::path::PathBuf; use std::{ env, error::Error, @@ -194,9 +195,9 @@ pub fn download_webrtc() -> Result<(), Box> { return Err(format!("failed to download webrtc: {}", resp.status()).into()); } - let tmp_path = env::var("OUT_DIR").unwrap() + "/webrtc.zip"; - let tmp_path = path::Path::new(&tmp_path); - let mut file = fs::File::options().write(true).read(true).create(true).open(tmp_path)?; + let out_dir = env::var("OUT_DIR").unwrap(); + let tmp_path = PathBuf::from(out_dir).join("webrtc.zip"); + let mut file = fs::File::options().write(true).read(true).create(true).open(&tmp_path)?; resp.copy_to(&mut file)?; let mut archive = zip::ZipArchive::new(file)?; From dcb8919c74c2432fb21c53622d67bee7b7462ae6 Mon Sep 17 00:00:00 2001 From: Daisuke Murase Date: Mon, 16 Jun 2025 11:39:49 -0700 Subject: [PATCH 211/274] fix(webrtc-sys-build): add error context to debug issues (#664) * add context * oops --- Cargo.lock | 7 ++--- webrtc-sys/build/Cargo.toml | 1 + webrtc-sys/build/src/lib.rs | 52 +++++++++++++++++++++---------------- 3 files changed, 35 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 78f59cf69..12ef438c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -54,9 +54,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.75" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" [[package]] name = "async-channel" @@ -1262,7 +1262,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.10", + "socket2 0.5.9", "tokio", "tower-service", "tracing", @@ -3382,6 +3382,7 @@ dependencies = [ name = "webrtc-sys-build" version = "0.3.6" dependencies = [ + "anyhow", "fs2", "regex", "reqwest", diff --git a/webrtc-sys/build/Cargo.toml b/webrtc-sys/build/Cargo.toml index c12a0acbc..41d46faa3 100644 --- a/webrtc-sys/build/Cargo.toml +++ b/webrtc-sys/build/Cargo.toml @@ -13,3 +13,4 @@ regex = "1.0" scratch = "1.0" fs2 = "0.4" semver = "1.0" +anyhow = "1.0" diff --git a/webrtc-sys/build/src/lib.rs b/webrtc-sys/build/src/lib.rs index 924cd7b51..d09986451 100644 --- a/webrtc-sys/build/src/lib.rs +++ b/webrtc-sys/build/src/lib.rs @@ -15,13 +15,13 @@ use std::path::PathBuf; use std::{ env, - error::Error, fs::{self, File}, io::{self, BufRead, Write}, path, process::Command, }; +use anyhow::{anyhow, Context, Result}; use fs2::FileExt; use regex::Regex; use reqwest::StatusCode; @@ -136,10 +136,10 @@ pub fn webrtc_defines() -> Vec<(String, Option)> { vec } -pub fn configure_jni_symbols() -> Result<(), Box> { - download_webrtc()?; +pub fn configure_jni_symbols() -> Result<()> { + download_webrtc().context("Failed to download WebRTC binaries for JNI configuration")?; - let toolchain = android_ndk_toolchain()?; + let toolchain = android_ndk_toolchain().context("Failed to locate Android NDK toolchain")?; let toolchain_bin = toolchain.join("bin"); let webrtc_dir = webrtc_dir(); @@ -160,7 +160,7 @@ pub fn configure_jni_symbols() -> Result<(), Box> { jni_regex.captures_iter(&content).map(|cap| cap.get(1).unwrap().as_str()).collect(); if jni_symbols.is_empty() { - return Err("No JNI symbols found".into()); // Shouldn't happen + return Err(anyhow!("No JNI symbols found")); // Shouldn't happen } // Keep JNI symbols @@ -170,45 +170,53 @@ pub fn configure_jni_symbols() -> Result<(), Box> { // Version script let vs_path = out_dir.join("webrtc_jni.map"); - let mut vs_file = fs::File::create(&vs_path).unwrap(); + let mut vs_file = fs::File::create(&vs_path).context("Failed to create version script file")?; let jni_symbols = jni_symbols.join("; "); - write!(vs_file, "JNI_WEBRTC {{\n\tglobal: {}; \n}};", jni_symbols).unwrap(); + write!(vs_file, "JNI_WEBRTC {{\n\tglobal: {}; \n}};", jni_symbols) + .context("Failed to write version script")?; println!("cargo:rustc-link-arg=-Wl,--version-script={}", vs_path.display()); Ok(()) } -pub fn download_webrtc() -> Result<(), Box> { +pub fn download_webrtc() -> Result<()> { let dir = scratch::path(SCRATH_PATH); - let flock = File::create(dir.join(".lock"))?; - flock.lock_exclusive()?; + let flock = File::create(dir.join(".lock")) + .context("Failed to create lock file for WebRTC download")?; + flock.lock_exclusive().context("Failed to acquire exclusive lock for WebRTC download")?; let webrtc_dir = webrtc_dir(); if webrtc_dir.exists() { return Ok(()); } - let mut resp = reqwest::blocking::get(download_url())?; + let mut resp = reqwest::blocking::get(download_url()) + .context("Failed to send HTTP request to download WebRTC")?; if resp.status() != StatusCode::OK { - return Err(format!("failed to download webrtc: {}", resp.status()).into()); + return Err(anyhow!("failed to download webrtc: {}", resp.status())); } let out_dir = env::var("OUT_DIR").unwrap(); let tmp_path = PathBuf::from(out_dir).join("webrtc.zip"); - let mut file = fs::File::options().write(true).read(true).create(true).open(&tmp_path)?; - resp.copy_to(&mut file)?; - - let mut archive = zip::ZipArchive::new(file)?; - archive.extract(webrtc_dir.parent().unwrap())?; + let mut file = fs::File::options() + .write(true) + .read(true) + .create(true) + .open(&tmp_path) + .context("Failed to create temporary file for WebRTC download")?; + resp.copy_to(&mut file).context("Failed to write WebRTC download to temporary file")?; + + let mut archive = zip::ZipArchive::new(file).context("Failed to open WebRTC zip archive")?; + archive.extract(webrtc_dir.parent().unwrap()).context("Failed to extract WebRTC archive")?; drop(archive); - fs::remove_file(tmp_path)?; + fs::remove_file(&tmp_path).context("Failed to remove temporary WebRTC zip file")?; Ok(()) } -pub fn android_ndk_toolchain() -> Result { +pub fn android_ndk_toolchain() -> Result { let host_os = host_os(); let home = env::var("HOME"); @@ -221,7 +229,7 @@ pub fn android_ndk_toolchain() -> Result { } else if host_os == Some("windows") { path::PathBuf::from(local.unwrap()) } else { - return Err("Unsupported host OS"); + return Err(anyhow!("Unsupported host OS")); }; let ndk_dir = || -> Option { @@ -262,12 +270,12 @@ pub fn android_ndk_toolchain() -> Result { } else if host_os == Some("windows") { "windows-x86_64" } else { - return Err("Unsupported host OS"); + return Err(anyhow!("Unsupported host OS")); }; Ok(ndk_dir.join(format!("toolchains/llvm/prebuilt/{}", llvm_dir))) } else { - Err("Android NDK not found, please set ANDROID_NDK_HOME to your NDK path") + Err(anyhow!("Android NDK not found, please set ANDROID_NDK_HOME to your NDK path")) } } From bdc0ffb944da32eb00846490ae8f8fee9e8163d4 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 17 Jun 2025 01:28:52 -0700 Subject: [PATCH 212/274] Expose room updates, support MoveParticipant (protocol 15) (#662) * Expose room updates, support MoveParticipant (protocol 15) giving clients other information about the room so they could receive * also fix sid updates * remove dead var --- livekit-api/src/services/sip.rs | 1 + livekit-api/src/signal_client/mod.rs | 2 +- livekit-ffi/protocol/participant.proto | 1 + livekit-ffi/protocol/room.proto | 17 ++ livekit-ffi/src/conversion/participant.rs | 9 +- livekit-ffi/src/conversion/room.rs | 34 ++- livekit-ffi/src/livekit.proto.rs | 34 ++- livekit-ffi/src/server/room.rs | 27 +- livekit-protocol/protocol | 2 +- livekit-protocol/src/livekit.rs | 39 +++ livekit-protocol/src/livekit.serde.rs | 302 ++++++++++++++++++++++ livekit/src/proto.rs | 1 + livekit/src/room/data_stream/outgoing.rs | 2 + livekit/src/room/mod.rs | 160 ++++++++++-- livekit/src/room/participant/mod.rs | 1 + livekit/src/rtc_engine/mod.rs | 6 + livekit/src/rtc_engine/rtc_session.rs | 8 + 17 files changed, 618 insertions(+), 28 deletions(-) diff --git a/livekit-api/src/services/sip.rs b/livekit-api/src/services/sip.rs index 6fa7aacf1..317ec055a 100644 --- a/livekit-api/src/services/sip.rs +++ b/livekit-api/src/services/sip.rs @@ -236,6 +236,7 @@ impl SIPClient { // TODO: support these attributes include_headers: Default::default(), media_encryption: Default::default(), + destination_country: Default::default(), }), }, self.base.auth_header( diff --git a/livekit-api/src/signal_client/mod.rs b/livekit-api/src/signal_client/mod.rs index 13f89e8e8..a19dc5e87 100644 --- a/livekit-api/src/signal_client/mod.rs +++ b/livekit-api/src/signal_client/mod.rs @@ -47,7 +47,7 @@ pub type SignalEvents = mpsc::UnboundedReceiver; pub type SignalResult = Result; pub const JOIN_RESPONSE_TIMEOUT: Duration = Duration::from_secs(5); -pub const PROTOCOL_VERSION: u32 = 15; +pub const PROTOCOL_VERSION: u32 = 16; #[derive(Error, Debug)] pub enum SignalError { diff --git a/livekit-ffi/protocol/participant.proto b/livekit-ffi/protocol/participant.proto index ea7eb41a1..5e7ab34ba 100644 --- a/livekit-ffi/protocol/participant.proto +++ b/livekit-ffi/protocol/participant.proto @@ -71,4 +71,5 @@ enum DisconnectReason { // SIP protocol failure or unexpected response SIP_TRUNK_FAILURE = 13; CONNECTION_TIMEOUT = 14; + MEDIA_FAILURE = 15; } \ No newline at end of file diff --git a/livekit-ffi/protocol/room.proto b/livekit-ffi/protocol/room.proto index b8fce75f9..94203d096 100644 --- a/livekit-ffi/protocol/room.proto +++ b/livekit-ffi/protocol/room.proto @@ -376,6 +376,12 @@ message RoomEvent { // Data stream (high level) ByteStreamOpened byte_stream_opened = 34; TextStreamOpened text_stream_opened = 35; + // Room info updated + RoomInfo room_updated = 36; + // Participant moved to new room + RoomInfo moved = 37; + // carry over all participant info updates, including sid + ParticipantsUpdated participants_updated = 38; } } @@ -385,6 +391,13 @@ message RoomInfo { required string metadata = 3; required uint64 lossy_dc_buffered_amount_low_threshold = 4; required uint64 reliable_dc_buffered_amount_low_threshold = 5; + required uint32 empty_timeout = 6; + required uint32 departure_timeout = 7; + required uint32 max_participants = 8; + required int64 creation_time = 9; + required uint32 num_participants = 10; + required uint32 num_publishers = 11; + required bool active_recording = 12; } message OwnedRoom { @@ -392,6 +405,10 @@ message OwnedRoom { required RoomInfo info = 2; } +message ParticipantsUpdated { + repeated ParticipantInfo participants = 1; +} + message ParticipantConnected { required OwnedParticipant info = 1; } message ParticipantDisconnected { diff --git a/livekit-ffi/src/conversion/participant.rs b/livekit-ffi/src/conversion/participant.rs index 33cf20dec..c7cbc9b97 100644 --- a/livekit-ffi/src/conversion/participant.rs +++ b/livekit-ffi/src/conversion/participant.rs @@ -13,12 +13,18 @@ // limitations under the License. use crate::{proto, server::participant::FfiParticipant}; +use livekit::prelude::*; use livekit::DisconnectReason; use livekit::ParticipantKind; impl From<&FfiParticipant> for proto::ParticipantInfo { fn from(value: &FfiParticipant) -> Self { - let participant = &value.participant; + From::<&Participant>::from(&value.participant) + } +} + +impl From<&Participant> for proto::ParticipantInfo { + fn from(participant: &Participant) -> Self { Self { sid: participant.sid().into(), name: participant.name(), @@ -62,6 +68,7 @@ impl From for proto::DisconnectReason { DisconnectReason::UserRejected => proto::DisconnectReason::UserRejected, DisconnectReason::SipTrunkFailure => proto::DisconnectReason::SipTrunkFailure, DisconnectReason::ConnectionTimeout => proto::DisconnectReason::ConnectionTimeout, + DisconnectReason::MediaFailure => proto::DisconnectReason::MediaFailure, } } } diff --git a/livekit-ffi/src/conversion/room.rs b/livekit-ffi/src/conversion/room.rs index 805d7ce2f..81772aa7f 100644 --- a/livekit-ffi/src/conversion/room.rs +++ b/livekit-ffi/src/conversion/room.rs @@ -24,6 +24,7 @@ use livekit::{ native::frame_cryptor::EncryptionState, prelude::{ContinualGatheringPolicy, IceServer, IceTransportsType, RtcConfiguration}, }, + RoomInfo, }; impl From for proto::EncryptionState { @@ -99,6 +100,7 @@ impl From for proto::DisconnectReason { DisconnectReason::UserRejected => Self::UserRejected, DisconnectReason::SipTrunkFailure => Self::SipTrunkFailure, DisconnectReason::ConnectionTimeout => Self::ConnectionTimeout, + DisconnectReason::MediaFailure => Self::MediaFailure, } } } @@ -249,7 +251,7 @@ impl From for AudioEncoding { impl From<&FfiRoom> for proto::RoomInfo { fn from(value: &FfiRoom) -> Self { let room = &value.inner.room; - Self { + proto::RoomInfo { sid: room.maybe_sid().map(|x| x.to_string()), name: room.name(), metadata: room.metadata(), @@ -259,6 +261,36 @@ impl From<&FfiRoom> for proto::RoomInfo { reliable_dc_buffered_amount_low_threshold: room .data_channel_options(DataPacketKind::Reliable) .buffered_amount_low_threshold, + empty_timeout: room.empty_timeout(), + departure_timeout: room.departure_timeout(), + max_participants: room.max_participants(), + creation_time: room.creation_time(), + num_participants: room.num_participants(), + num_publishers: room.num_publishers(), + active_recording: room.active_recording(), + } + } +} + +impl From for proto::RoomInfo { + fn from(room: RoomInfo) -> Self { + proto::RoomInfo { + sid: room.sid.map(|x| x.to_string()), + name: room.name, + metadata: room.metadata, + lossy_dc_buffered_amount_low_threshold: room + .lossy_dc_options + .buffered_amount_low_threshold, + reliable_dc_buffered_amount_low_threshold: room + .reliable_dc_options + .buffered_amount_low_threshold, + empty_timeout: room.empty_timeout, + departure_timeout: room.departure_timeout, + max_participants: room.max_participants, + creation_time: room.creation_time, + num_participants: room.num_participants, + num_publishers: room.num_publishers, + active_recording: room.active_recording, } } } diff --git a/livekit-ffi/src/livekit.proto.rs b/livekit-ffi/src/livekit.proto.rs index 6eb1f5d03..410c07b92 100644 --- a/livekit-ffi/src/livekit.proto.rs +++ b/livekit-ffi/src/livekit.proto.rs @@ -1739,6 +1739,7 @@ pub enum DisconnectReason { /// SIP protocol failure or unexpected response SipTrunkFailure = 13, ConnectionTimeout = 14, + MediaFailure = 15, } impl DisconnectReason { /// String value of the enum field names used in the ProtoBuf definition. @@ -1762,6 +1763,7 @@ impl DisconnectReason { DisconnectReason::UserRejected => "USER_REJECTED", DisconnectReason::SipTrunkFailure => "SIP_TRUNK_FAILURE", DisconnectReason::ConnectionTimeout => "CONNECTION_TIMEOUT", + DisconnectReason::MediaFailure => "MEDIA_FAILURE", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1782,6 +1784,7 @@ impl DisconnectReason { "USER_REJECTED" => Some(Self::UserRejected), "SIP_TRUNK_FAILURE" => Some(Self::SipTrunkFailure), "CONNECTION_TIMEOUT" => Some(Self::ConnectionTimeout), + "MEDIA_FAILURE" => Some(Self::MediaFailure), _ => None, } } @@ -3329,7 +3332,7 @@ pub struct OwnedBuffer { pub struct RoomEvent { #[prost(uint64, required, tag="1")] pub room_handle: u64, - #[prost(oneof="room_event::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35")] + #[prost(oneof="room_event::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38")] pub message: ::core::option::Option, } /// Nested message and enum types in `RoomEvent`. @@ -3409,6 +3412,15 @@ pub mod room_event { ByteStreamOpened(super::ByteStreamOpened), #[prost(message, tag="35")] TextStreamOpened(super::TextStreamOpened), + /// Room info updated + #[prost(message, tag="36")] + RoomUpdated(super::RoomInfo), + /// Participant moved to new room + #[prost(message, tag="37")] + Moved(super::RoomInfo), + /// carry over all participant info updates, including sid + #[prost(message, tag="38")] + ParticipantsUpdated(super::ParticipantsUpdated), } } #[allow(clippy::derive_partial_eq_without_eq)] @@ -3424,6 +3436,20 @@ pub struct RoomInfo { pub lossy_dc_buffered_amount_low_threshold: u64, #[prost(uint64, required, tag="5")] pub reliable_dc_buffered_amount_low_threshold: u64, + #[prost(uint32, required, tag="6")] + pub empty_timeout: u32, + #[prost(uint32, required, tag="7")] + pub departure_timeout: u32, + #[prost(uint32, required, tag="8")] + pub max_participants: u32, + #[prost(int64, required, tag="9")] + pub creation_time: i64, + #[prost(uint32, required, tag="10")] + pub num_participants: u32, + #[prost(uint32, required, tag="11")] + pub num_publishers: u32, + #[prost(bool, required, tag="12")] + pub active_recording: bool, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -3435,6 +3461,12 @@ pub struct OwnedRoom { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct ParticipantsUpdated { + #[prost(message, repeated, tag="1")] + pub participants: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct ParticipantConnected { #[prost(message, required, tag="1")] pub info: OwnedParticipant, diff --git a/livekit-ffi/src/server/room.rs b/livekit-ffi/src/server/room.rs index 7a2437280..761fb40aa 100644 --- a/livekit-ffi/src/server/room.rs +++ b/livekit-ffi/src/server/room.rs @@ -629,7 +629,6 @@ impl RoomInner { send_chat_message.sender_identity, ) .await; - let sent_message = res.as_ref().unwrap().clone(); match res { Ok(message) => { let _ = server.send_event(proto::ffi_event::Message::ChatMessage( @@ -652,7 +651,6 @@ impl RoomInner { )); } } - sent_message; }); server.watch_panic(handle); proto::SendChatMessageResponse { async_id } @@ -676,7 +674,6 @@ impl RoomInner { edit_chat_message.sender_identity, ) .await; - let sent_message: ChatMessage = res.as_ref().unwrap().clone(); match res { Ok(message) => { let _ = server.send_event(proto::ffi_event::Message::ChatMessage( @@ -699,7 +696,6 @@ impl RoomInner { )); } } - sent_message; }); server.watch_panic(handle); proto::SendChatMessageResponse { async_id } @@ -720,6 +716,7 @@ impl RoomInner { send_stream_header.header.into(), ) .into(), + ..Default::default() }; let async_id = server.next_id(); let inner = self.clone(); @@ -748,6 +745,7 @@ impl RoomInner { send_stream_chunk.chunk.into(), ) .into(), + ..Default::default() }; let async_id = server.next_id(); let inner = self.clone(); @@ -777,6 +775,7 @@ impl RoomInner { send_stream_trailer.trailer.into(), ) .into(), + ..Default::default() }; let async_id = server.next_id(); let inner = self.clone(); @@ -1248,7 +1247,7 @@ async fn forward_event( )); } RoomEvent::SipDTMFReceived { code, digit, participant } => { - let (sid, identity) = match participant { + let (_sid, identity) = match participant { Some(p) => (Some(p.sid().to_string()), p.identity().to_string()), None => (None, String::new()), }; @@ -1265,7 +1264,7 @@ async fn forward_event( } RoomEvent::ChatMessage { message, participant } => { - let (sid, identity) = match participant { + let (_sid, identity) = match participant { Some(p) => (Some(p.sid().to_string()), p.identity().to_string()), None => (None, String::new()), }; @@ -1359,6 +1358,22 @@ async fn forward_event( }, )); } + RoomEvent::RoomUpdated { room } => { + let _ = send_event(proto::room_event::Message::RoomUpdated(room.into())); + } + RoomEvent::Moved { room } => { + let _ = send_event(proto::room_event::Message::Moved(room.into())); + } + RoomEvent::ParticipantsUpdated { participants } => { + let _ = send_event(proto::room_event::Message::ParticipantsUpdated( + proto::ParticipantsUpdated { + participants: participants + .into_iter() + .map(|p| proto::ParticipantInfo::from(&p)) + .collect(), + }, + )); + } _ => { log::warn!("unhandled room event: {:?}", event); } diff --git a/livekit-protocol/protocol b/livekit-protocol/protocol index 3ee266441..e038e7944 160000 --- a/livekit-protocol/protocol +++ b/livekit-protocol/protocol @@ -1 +1 @@ -Subproject commit 3ee2664416147b7ca8086fd1aa818164d2673b0b +Subproject commit e038e7944595dd9a00871ee5ed52ba6062f76c1e diff --git a/livekit-protocol/src/livekit.rs b/livekit-protocol/src/livekit.rs index 551fe688e..e39604819 100644 --- a/livekit-protocol/src/livekit.rs +++ b/livekit-protocol/src/livekit.rs @@ -567,6 +567,12 @@ pub struct DataPacket { /// identities of participants who will receive the message (sent to all by default) #[prost(string, repeated, tag="5")] pub destination_identities: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + /// sequence number of reliable packet + #[prost(uint32, tag="16")] + pub sequence: u32, + /// sid of the user that sent the message + #[prost(string, tag="17")] + pub participant_sid: ::prost::alloc::string::String, #[prost(oneof="data_packet::Value", tags="2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15")] pub value: ::core::option::Option, } @@ -1633,6 +1639,8 @@ pub enum DisconnectReason { SipTrunkFailure = 13, /// server timed out a participant session ConnectionTimeout = 14, + /// media stream failure or media timeout + MediaFailure = 15, } impl DisconnectReason { /// String value of the enum field names used in the ProtoBuf definition. @@ -1656,6 +1664,7 @@ impl DisconnectReason { DisconnectReason::UserRejected => "USER_REJECTED", DisconnectReason::SipTrunkFailure => "SIP_TRUNK_FAILURE", DisconnectReason::ConnectionTimeout => "CONNECTION_TIMEOUT", + DisconnectReason::MediaFailure => "MEDIA_FAILURE", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1676,6 +1685,7 @@ impl DisconnectReason { "USER_REJECTED" => Some(Self::UserRejected), "SIP_TRUNK_FAILURE" => Some(Self::SipTrunkFailure), "CONNECTION_TIMEOUT" => Some(Self::ConnectionTimeout), + "MEDIA_FAILURE" => Some(Self::MediaFailure), _ => None, } } @@ -3127,6 +3137,11 @@ pub struct ReconnectResponse { pub ice_servers: ::prost::alloc::vec::Vec, #[prost(message, optional, tag="2")] pub client_configuration: ::core::option::Option, + #[prost(message, optional, tag="3")] + pub server_info: ::core::option::Option, + /// last sequence number of reliable message received before resuming + #[prost(uint32, tag="4")] + pub last_message_seq: u32, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -3150,6 +3165,8 @@ pub struct SessionDescription { pub r#type: ::prost::alloc::string::String, #[prost(string, tag="2")] pub sdp: ::prost::alloc::string::String, + #[prost(uint32, tag="3")] + pub id: u32, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -3363,6 +3380,7 @@ pub struct SubscribedCodec { pub struct SubscribedQualityUpdate { #[prost(string, tag="1")] pub track_sid: ::prost::alloc::string::String, + #[deprecated] #[prost(message, repeated, tag="2")] pub subscribed_qualities: ::prost::alloc::vec::Vec, #[prost(message, repeated, tag="3")] @@ -3430,6 +3448,16 @@ pub struct SyncState { pub offer: ::core::option::Option, #[prost(string, repeated, tag="6")] pub track_sids_disabled: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(message, repeated, tag="7")] + pub datachannel_receive_states: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DataChannelReceiveState { + #[prost(string, tag="1")] + pub publisher_sid: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub last_seq: u32, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -3829,12 +3857,15 @@ pub struct AvailabilityResponse { pub available: bool, #[prost(bool, tag="3")] pub supports_resume: bool, + #[prost(bool, tag="8")] + pub terminate: bool, #[prost(string, tag="4")] pub participant_name: ::prost::alloc::string::String, #[prost(string, tag="5")] pub participant_identity: ::prost::alloc::string::String, #[prost(string, tag="6")] pub participant_metadata: ::prost::alloc::string::String, + /// NEXT_ID: 9 #[prost(map="string, string", tag="7")] pub participant_attributes: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, } @@ -5018,6 +5049,9 @@ pub struct SipOutboundTrunkInfo { /// Note that this is not a SIP URI and should not contain the 'sip:' protocol prefix. #[prost(string, tag="4")] pub address: ::prost::alloc::string::String, + /// country where the call terminates as ISO 3166-1 alpha-2 (). This will be used by the livekit infrastructure to route calls. + #[prost(string, tag="14")] + pub destination_country: ::prost::alloc::string::String, /// SIP Transport used for outbound call. #[prost(enumeration="SipTransport", tag="5")] pub transport: i32, @@ -5060,6 +5094,8 @@ pub struct SipOutboundTrunkUpdate { pub address: ::core::option::Option<::prost::alloc::string::String>, #[prost(enumeration="SipTransport", optional, tag="2")] pub transport: ::core::option::Option, + #[prost(string, optional, tag="9")] + pub destination_country: ::core::option::Option<::prost::alloc::string::String>, #[prost(message, optional, tag="3")] pub numbers: ::core::option::Option, #[prost(string, optional, tag="4")] @@ -5361,6 +5397,9 @@ pub struct SipOutboundConfig { /// SIP server address #[prost(string, tag="1")] pub hostname: ::prost::alloc::string::String, + /// country where the call terminates as ISO 3166-1 alpha-2 (). This will be used by the livekit infrastructure to route calls. + #[prost(string, tag="7")] + pub destination_country: ::prost::alloc::string::String, /// SIP Transport used for outbound call. #[prost(enumeration="SipTransport", tag="2")] pub transport: i32, diff --git a/livekit-protocol/src/livekit.serde.rs b/livekit-protocol/src/livekit.serde.rs index c88fd8c24..5c411a433 100644 --- a/livekit-protocol/src/livekit.serde.rs +++ b/livekit-protocol/src/livekit.serde.rs @@ -1636,6 +1636,9 @@ impl serde::Serialize for AvailabilityResponse { if self.supports_resume { len += 1; } + if self.terminate { + len += 1; + } if !self.participant_name.is_empty() { len += 1; } @@ -1658,6 +1661,9 @@ impl serde::Serialize for AvailabilityResponse { if self.supports_resume { struct_ser.serialize_field("supportsResume", &self.supports_resume)?; } + if self.terminate { + struct_ser.serialize_field("terminate", &self.terminate)?; + } if !self.participant_name.is_empty() { struct_ser.serialize_field("participantName", &self.participant_name)?; } @@ -1685,6 +1691,7 @@ impl<'de> serde::Deserialize<'de> for AvailabilityResponse { "available", "supports_resume", "supportsResume", + "terminate", "participant_name", "participantName", "participant_identity", @@ -1700,6 +1707,7 @@ impl<'de> serde::Deserialize<'de> for AvailabilityResponse { JobId, Available, SupportsResume, + Terminate, ParticipantName, ParticipantIdentity, ParticipantMetadata, @@ -1729,6 +1737,7 @@ impl<'de> serde::Deserialize<'de> for AvailabilityResponse { "jobId" | "job_id" => Ok(GeneratedField::JobId), "available" => Ok(GeneratedField::Available), "supportsResume" | "supports_resume" => Ok(GeneratedField::SupportsResume), + "terminate" => Ok(GeneratedField::Terminate), "participantName" | "participant_name" => Ok(GeneratedField::ParticipantName), "participantIdentity" | "participant_identity" => Ok(GeneratedField::ParticipantIdentity), "participantMetadata" | "participant_metadata" => Ok(GeneratedField::ParticipantMetadata), @@ -1755,6 +1764,7 @@ impl<'de> serde::Deserialize<'de> for AvailabilityResponse { let mut job_id__ = None; let mut available__ = None; let mut supports_resume__ = None; + let mut terminate__ = None; let mut participant_name__ = None; let mut participant_identity__ = None; let mut participant_metadata__ = None; @@ -1779,6 +1789,12 @@ impl<'de> serde::Deserialize<'de> for AvailabilityResponse { } supports_resume__ = Some(map_.next_value()?); } + GeneratedField::Terminate => { + if terminate__.is_some() { + return Err(serde::de::Error::duplicate_field("terminate")); + } + terminate__ = Some(map_.next_value()?); + } GeneratedField::ParticipantName => { if participant_name__.is_some() { return Err(serde::de::Error::duplicate_field("participantName")); @@ -1814,6 +1830,7 @@ impl<'de> serde::Deserialize<'de> for AvailabilityResponse { job_id: job_id__.unwrap_or_default(), available: available__.unwrap_or_default(), supports_resume: supports_resume__.unwrap_or_default(), + terminate: terminate__.unwrap_or_default(), participant_name: participant_name__.unwrap_or_default(), participant_identity: participant_identity__.unwrap_or_default(), participant_metadata: participant_metadata__.unwrap_or_default(), @@ -5371,6 +5388,122 @@ impl<'de> serde::Deserialize<'de> for DataChannelInfo { deserializer.deserialize_struct("livekit.DataChannelInfo", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for DataChannelReceiveState { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.publisher_sid.is_empty() { + len += 1; + } + if self.last_seq != 0 { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.DataChannelReceiveState", len)?; + if !self.publisher_sid.is_empty() { + struct_ser.serialize_field("publisherSid", &self.publisher_sid)?; + } + if self.last_seq != 0 { + struct_ser.serialize_field("lastSeq", &self.last_seq)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for DataChannelReceiveState { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "publisher_sid", + "publisherSid", + "last_seq", + "lastSeq", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + PublisherSid, + LastSeq, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "publisherSid" | "publisher_sid" => Ok(GeneratedField::PublisherSid), + "lastSeq" | "last_seq" => Ok(GeneratedField::LastSeq), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = DataChannelReceiveState; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.DataChannelReceiveState") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut publisher_sid__ = None; + let mut last_seq__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::PublisherSid => { + if publisher_sid__.is_some() { + return Err(serde::de::Error::duplicate_field("publisherSid")); + } + publisher_sid__ = Some(map_.next_value()?); + } + GeneratedField::LastSeq => { + if last_seq__.is_some() { + return Err(serde::de::Error::duplicate_field("lastSeq")); + } + last_seq__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(DataChannelReceiveState { + publisher_sid: publisher_sid__.unwrap_or_default(), + last_seq: last_seq__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.DataChannelReceiveState", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for DataPacket { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -5388,6 +5521,12 @@ impl serde::Serialize for DataPacket { if !self.destination_identities.is_empty() { len += 1; } + if self.sequence != 0 { + len += 1; + } + if !self.participant_sid.is_empty() { + len += 1; + } if self.value.is_some() { len += 1; } @@ -5403,6 +5542,12 @@ impl serde::Serialize for DataPacket { if !self.destination_identities.is_empty() { struct_ser.serialize_field("destinationIdentities", &self.destination_identities)?; } + if self.sequence != 0 { + struct_ser.serialize_field("sequence", &self.sequence)?; + } + if !self.participant_sid.is_empty() { + struct_ser.serialize_field("participantSid", &self.participant_sid)?; + } if let Some(v) = self.value.as_ref() { match v { data_packet::Value::User(v) => { @@ -5458,6 +5603,9 @@ impl<'de> serde::Deserialize<'de> for DataPacket { "participantIdentity", "destination_identities", "destinationIdentities", + "sequence", + "participant_sid", + "participantSid", "user", "speaker", "sip_dtmf", @@ -5485,6 +5633,8 @@ impl<'de> serde::Deserialize<'de> for DataPacket { Kind, ParticipantIdentity, DestinationIdentities, + Sequence, + ParticipantSid, User, Speaker, SipDtmf, @@ -5522,6 +5672,8 @@ impl<'de> serde::Deserialize<'de> for DataPacket { "kind" => Ok(GeneratedField::Kind), "participantIdentity" | "participant_identity" => Ok(GeneratedField::ParticipantIdentity), "destinationIdentities" | "destination_identities" => Ok(GeneratedField::DestinationIdentities), + "sequence" => Ok(GeneratedField::Sequence), + "participantSid" | "participant_sid" => Ok(GeneratedField::ParticipantSid), "user" => Ok(GeneratedField::User), "speaker" => Ok(GeneratedField::Speaker), "sipDtmf" | "sip_dtmf" => Ok(GeneratedField::SipDtmf), @@ -5556,6 +5708,8 @@ impl<'de> serde::Deserialize<'de> for DataPacket { let mut kind__ = None; let mut participant_identity__ = None; let mut destination_identities__ = None; + let mut sequence__ = None; + let mut participant_sid__ = None; let mut value__ = None; while let Some(k) = map_.next_key()? { match k { @@ -5577,6 +5731,20 @@ impl<'de> serde::Deserialize<'de> for DataPacket { } destination_identities__ = Some(map_.next_value()?); } + GeneratedField::Sequence => { + if sequence__.is_some() { + return Err(serde::de::Error::duplicate_field("sequence")); + } + sequence__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::ParticipantSid => { + if participant_sid__.is_some() { + return Err(serde::de::Error::duplicate_field("participantSid")); + } + participant_sid__ = Some(map_.next_value()?); + } GeneratedField::User => { if value__.is_some() { return Err(serde::de::Error::duplicate_field("user")); @@ -5670,6 +5838,8 @@ impl<'de> serde::Deserialize<'de> for DataPacket { kind: kind__.unwrap_or_default(), participant_identity: participant_identity__.unwrap_or_default(), destination_identities: destination_identities__.unwrap_or_default(), + sequence: sequence__.unwrap_or_default(), + participant_sid: participant_sid__.unwrap_or_default(), value: value__, }) } @@ -7599,6 +7769,7 @@ impl serde::Serialize for DisconnectReason { Self::UserRejected => "USER_REJECTED", Self::SipTrunkFailure => "SIP_TRUNK_FAILURE", Self::ConnectionTimeout => "CONNECTION_TIMEOUT", + Self::MediaFailure => "MEDIA_FAILURE", }; serializer.serialize_str(variant) } @@ -7625,6 +7796,7 @@ impl<'de> serde::Deserialize<'de> for DisconnectReason { "USER_REJECTED", "SIP_TRUNK_FAILURE", "CONNECTION_TIMEOUT", + "MEDIA_FAILURE", ]; struct GeneratedVisitor; @@ -7680,6 +7852,7 @@ impl<'de> serde::Deserialize<'de> for DisconnectReason { "USER_REJECTED" => Ok(DisconnectReason::UserRejected), "SIP_TRUNK_FAILURE" => Ok(DisconnectReason::SipTrunkFailure), "CONNECTION_TIMEOUT" => Ok(DisconnectReason::ConnectionTimeout), + "MEDIA_FAILURE" => Ok(DisconnectReason::MediaFailure), _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), } } @@ -21297,6 +21470,12 @@ impl serde::Serialize for ReconnectResponse { if self.client_configuration.is_some() { len += 1; } + if self.server_info.is_some() { + len += 1; + } + if self.last_message_seq != 0 { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.ReconnectResponse", len)?; if !self.ice_servers.is_empty() { struct_ser.serialize_field("iceServers", &self.ice_servers)?; @@ -21304,6 +21483,12 @@ impl serde::Serialize for ReconnectResponse { if let Some(v) = self.client_configuration.as_ref() { struct_ser.serialize_field("clientConfiguration", v)?; } + if let Some(v) = self.server_info.as_ref() { + struct_ser.serialize_field("serverInfo", v)?; + } + if self.last_message_seq != 0 { + struct_ser.serialize_field("lastMessageSeq", &self.last_message_seq)?; + } struct_ser.end() } } @@ -21318,12 +21503,18 @@ impl<'de> serde::Deserialize<'de> for ReconnectResponse { "iceServers", "client_configuration", "clientConfiguration", + "server_info", + "serverInfo", + "last_message_seq", + "lastMessageSeq", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { IceServers, ClientConfiguration, + ServerInfo, + LastMessageSeq, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -21348,6 +21539,8 @@ impl<'de> serde::Deserialize<'de> for ReconnectResponse { match value { "iceServers" | "ice_servers" => Ok(GeneratedField::IceServers), "clientConfiguration" | "client_configuration" => Ok(GeneratedField::ClientConfiguration), + "serverInfo" | "server_info" => Ok(GeneratedField::ServerInfo), + "lastMessageSeq" | "last_message_seq" => Ok(GeneratedField::LastMessageSeq), _ => Ok(GeneratedField::__SkipField__), } } @@ -21369,6 +21562,8 @@ impl<'de> serde::Deserialize<'de> for ReconnectResponse { { let mut ice_servers__ = None; let mut client_configuration__ = None; + let mut server_info__ = None; + let mut last_message_seq__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::IceServers => { @@ -21383,6 +21578,20 @@ impl<'de> serde::Deserialize<'de> for ReconnectResponse { } client_configuration__ = map_.next_value()?; } + GeneratedField::ServerInfo => { + if server_info__.is_some() { + return Err(serde::de::Error::duplicate_field("serverInfo")); + } + server_info__ = map_.next_value()?; + } + GeneratedField::LastMessageSeq => { + if last_message_seq__.is_some() { + return Err(serde::de::Error::duplicate_field("lastMessageSeq")); + } + last_message_seq__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -21391,6 +21600,8 @@ impl<'de> serde::Deserialize<'de> for ReconnectResponse { Ok(ReconnectResponse { ice_servers: ice_servers__.unwrap_or_default(), client_configuration: client_configuration__, + server_info: server_info__, + last_message_seq: last_message_seq__.unwrap_or_default(), }) } } @@ -27133,6 +27344,9 @@ impl serde::Serialize for SipOutboundConfig { if !self.hostname.is_empty() { len += 1; } + if !self.destination_country.is_empty() { + len += 1; + } if self.transport != 0 { len += 1; } @@ -27152,6 +27366,9 @@ impl serde::Serialize for SipOutboundConfig { if !self.hostname.is_empty() { struct_ser.serialize_field("hostname", &self.hostname)?; } + if !self.destination_country.is_empty() { + struct_ser.serialize_field("destinationCountry", &self.destination_country)?; + } if self.transport != 0 { let v = SipTransport::try_from(self.transport) .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.transport)))?; @@ -27180,6 +27397,8 @@ impl<'de> serde::Deserialize<'de> for SipOutboundConfig { { const FIELDS: &[&str] = &[ "hostname", + "destination_country", + "destinationCountry", "transport", "auth_username", "authUsername", @@ -27194,6 +27413,7 @@ impl<'de> serde::Deserialize<'de> for SipOutboundConfig { #[allow(clippy::enum_variant_names)] enum GeneratedField { Hostname, + DestinationCountry, Transport, AuthUsername, AuthPassword, @@ -27222,6 +27442,7 @@ impl<'de> serde::Deserialize<'de> for SipOutboundConfig { { match value { "hostname" => Ok(GeneratedField::Hostname), + "destinationCountry" | "destination_country" => Ok(GeneratedField::DestinationCountry), "transport" => Ok(GeneratedField::Transport), "authUsername" | "auth_username" => Ok(GeneratedField::AuthUsername), "authPassword" | "auth_password" => Ok(GeneratedField::AuthPassword), @@ -27247,6 +27468,7 @@ impl<'de> serde::Deserialize<'de> for SipOutboundConfig { V: serde::de::MapAccess<'de>, { let mut hostname__ = None; + let mut destination_country__ = None; let mut transport__ = None; let mut auth_username__ = None; let mut auth_password__ = None; @@ -27260,6 +27482,12 @@ impl<'de> serde::Deserialize<'de> for SipOutboundConfig { } hostname__ = Some(map_.next_value()?); } + GeneratedField::DestinationCountry => { + if destination_country__.is_some() { + return Err(serde::de::Error::duplicate_field("destinationCountry")); + } + destination_country__ = Some(map_.next_value()?); + } GeneratedField::Transport => { if transport__.is_some() { return Err(serde::de::Error::duplicate_field("transport")); @@ -27301,6 +27529,7 @@ impl<'de> serde::Deserialize<'de> for SipOutboundConfig { } Ok(SipOutboundConfig { hostname: hostname__.unwrap_or_default(), + destination_country: destination_country__.unwrap_or_default(), transport: transport__.unwrap_or_default(), auth_username: auth_username__.unwrap_or_default(), auth_password: auth_password__.unwrap_or_default(), @@ -27332,6 +27561,9 @@ impl serde::Serialize for SipOutboundTrunkInfo { if !self.address.is_empty() { len += 1; } + if !self.destination_country.is_empty() { + len += 1; + } if self.transport != 0 { len += 1; } @@ -27372,6 +27604,9 @@ impl serde::Serialize for SipOutboundTrunkInfo { if !self.address.is_empty() { struct_ser.serialize_field("address", &self.address)?; } + if !self.destination_country.is_empty() { + struct_ser.serialize_field("destinationCountry", &self.destination_country)?; + } if self.transport != 0 { let v = SipTransport::try_from(self.transport) .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.transport)))?; @@ -27420,6 +27655,8 @@ impl<'de> serde::Deserialize<'de> for SipOutboundTrunkInfo { "name", "metadata", "address", + "destination_country", + "destinationCountry", "transport", "numbers", "auth_username", @@ -27443,6 +27680,7 @@ impl<'de> serde::Deserialize<'de> for SipOutboundTrunkInfo { Name, Metadata, Address, + DestinationCountry, Transport, Numbers, AuthUsername, @@ -27478,6 +27716,7 @@ impl<'de> serde::Deserialize<'de> for SipOutboundTrunkInfo { "name" => Ok(GeneratedField::Name), "metadata" => Ok(GeneratedField::Metadata), "address" => Ok(GeneratedField::Address), + "destinationCountry" | "destination_country" => Ok(GeneratedField::DestinationCountry), "transport" => Ok(GeneratedField::Transport), "numbers" => Ok(GeneratedField::Numbers), "authUsername" | "auth_username" => Ok(GeneratedField::AuthUsername), @@ -27510,6 +27749,7 @@ impl<'de> serde::Deserialize<'de> for SipOutboundTrunkInfo { let mut name__ = None; let mut metadata__ = None; let mut address__ = None; + let mut destination_country__ = None; let mut transport__ = None; let mut numbers__ = None; let mut auth_username__ = None; @@ -27545,6 +27785,12 @@ impl<'de> serde::Deserialize<'de> for SipOutboundTrunkInfo { } address__ = Some(map_.next_value()?); } + GeneratedField::DestinationCountry => { + if destination_country__.is_some() { + return Err(serde::de::Error::duplicate_field("destinationCountry")); + } + destination_country__ = Some(map_.next_value()?); + } GeneratedField::Transport => { if transport__.is_some() { return Err(serde::de::Error::duplicate_field("transport")); @@ -27615,6 +27861,7 @@ impl<'de> serde::Deserialize<'de> for SipOutboundTrunkInfo { name: name__.unwrap_or_default(), metadata: metadata__.unwrap_or_default(), address: address__.unwrap_or_default(), + destination_country: destination_country__.unwrap_or_default(), transport: transport__.unwrap_or_default(), numbers: numbers__.unwrap_or_default(), auth_username: auth_username__.unwrap_or_default(), @@ -27644,6 +27891,9 @@ impl serde::Serialize for SipOutboundTrunkUpdate { if self.transport.is_some() { len += 1; } + if self.destination_country.is_some() { + len += 1; + } if self.numbers.is_some() { len += 1; } @@ -27671,6 +27921,9 @@ impl serde::Serialize for SipOutboundTrunkUpdate { .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", *v)))?; struct_ser.serialize_field("transport", &v)?; } + if let Some(v) = self.destination_country.as_ref() { + struct_ser.serialize_field("destinationCountry", v)?; + } if let Some(v) = self.numbers.as_ref() { struct_ser.serialize_field("numbers", v)?; } @@ -27703,6 +27956,8 @@ impl<'de> serde::Deserialize<'de> for SipOutboundTrunkUpdate { const FIELDS: &[&str] = &[ "address", "transport", + "destination_country", + "destinationCountry", "numbers", "auth_username", "authUsername", @@ -27718,6 +27973,7 @@ impl<'de> serde::Deserialize<'de> for SipOutboundTrunkUpdate { enum GeneratedField { Address, Transport, + DestinationCountry, Numbers, AuthUsername, AuthPassword, @@ -27748,6 +28004,7 @@ impl<'de> serde::Deserialize<'de> for SipOutboundTrunkUpdate { match value { "address" => Ok(GeneratedField::Address), "transport" => Ok(GeneratedField::Transport), + "destinationCountry" | "destination_country" => Ok(GeneratedField::DestinationCountry), "numbers" => Ok(GeneratedField::Numbers), "authUsername" | "auth_username" => Ok(GeneratedField::AuthUsername), "authPassword" | "auth_password" => Ok(GeneratedField::AuthPassword), @@ -27775,6 +28032,7 @@ impl<'de> serde::Deserialize<'de> for SipOutboundTrunkUpdate { { let mut address__ = None; let mut transport__ = None; + let mut destination_country__ = None; let mut numbers__ = None; let mut auth_username__ = None; let mut auth_password__ = None; @@ -27795,6 +28053,12 @@ impl<'de> serde::Deserialize<'de> for SipOutboundTrunkUpdate { } transport__ = map_.next_value::<::std::option::Option>()?.map(|x| x as i32); } + GeneratedField::DestinationCountry => { + if destination_country__.is_some() { + return Err(serde::de::Error::duplicate_field("destinationCountry")); + } + destination_country__ = map_.next_value()?; + } GeneratedField::Numbers => { if numbers__.is_some() { return Err(serde::de::Error::duplicate_field("numbers")); @@ -27839,6 +28103,7 @@ impl<'de> serde::Deserialize<'de> for SipOutboundTrunkUpdate { Ok(SipOutboundTrunkUpdate { address: address__, transport: transport__, + destination_country: destination_country__, numbers: numbers__, auth_username: auth_username__, auth_password: auth_password__, @@ -30674,6 +30939,9 @@ impl serde::Serialize for SessionDescription { if !self.sdp.is_empty() { len += 1; } + if self.id != 0 { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.SessionDescription", len)?; if !self.r#type.is_empty() { struct_ser.serialize_field("type", &self.r#type)?; @@ -30681,6 +30949,9 @@ impl serde::Serialize for SessionDescription { if !self.sdp.is_empty() { struct_ser.serialize_field("sdp", &self.sdp)?; } + if self.id != 0 { + struct_ser.serialize_field("id", &self.id)?; + } struct_ser.end() } } @@ -30693,12 +30964,14 @@ impl<'de> serde::Deserialize<'de> for SessionDescription { const FIELDS: &[&str] = &[ "type", "sdp", + "id", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { Type, Sdp, + Id, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -30723,6 +30996,7 @@ impl<'de> serde::Deserialize<'de> for SessionDescription { match value { "type" => Ok(GeneratedField::Type), "sdp" => Ok(GeneratedField::Sdp), + "id" => Ok(GeneratedField::Id), _ => Ok(GeneratedField::__SkipField__), } } @@ -30744,6 +31018,7 @@ impl<'de> serde::Deserialize<'de> for SessionDescription { { let mut r#type__ = None; let mut sdp__ = None; + let mut id__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::Type => { @@ -30758,6 +31033,14 @@ impl<'de> serde::Deserialize<'de> for SessionDescription { } sdp__ = Some(map_.next_value()?); } + GeneratedField::Id => { + if id__.is_some() { + return Err(serde::de::Error::duplicate_field("id")); + } + id__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -30766,6 +31049,7 @@ impl<'de> serde::Deserialize<'de> for SessionDescription { Ok(SessionDescription { r#type: r#type__.unwrap_or_default(), sdp: sdp__.unwrap_or_default(), + id: id__.unwrap_or_default(), }) } } @@ -34265,6 +34549,9 @@ impl serde::Serialize for SyncState { if !self.track_sids_disabled.is_empty() { len += 1; } + if !self.datachannel_receive_states.is_empty() { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.SyncState", len)?; if let Some(v) = self.answer.as_ref() { struct_ser.serialize_field("answer", v)?; @@ -34284,6 +34571,9 @@ impl serde::Serialize for SyncState { if !self.track_sids_disabled.is_empty() { struct_ser.serialize_field("trackSidsDisabled", &self.track_sids_disabled)?; } + if !self.datachannel_receive_states.is_empty() { + struct_ser.serialize_field("datachannelReceiveStates", &self.datachannel_receive_states)?; + } struct_ser.end() } } @@ -34303,6 +34593,8 @@ impl<'de> serde::Deserialize<'de> for SyncState { "offer", "track_sids_disabled", "trackSidsDisabled", + "datachannel_receive_states", + "datachannelReceiveStates", ]; #[allow(clippy::enum_variant_names)] @@ -34313,6 +34605,7 @@ impl<'de> serde::Deserialize<'de> for SyncState { DataChannels, Offer, TrackSidsDisabled, + DatachannelReceiveStates, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -34341,6 +34634,7 @@ impl<'de> serde::Deserialize<'de> for SyncState { "dataChannels" | "data_channels" => Ok(GeneratedField::DataChannels), "offer" => Ok(GeneratedField::Offer), "trackSidsDisabled" | "track_sids_disabled" => Ok(GeneratedField::TrackSidsDisabled), + "datachannelReceiveStates" | "datachannel_receive_states" => Ok(GeneratedField::DatachannelReceiveStates), _ => Ok(GeneratedField::__SkipField__), } } @@ -34366,6 +34660,7 @@ impl<'de> serde::Deserialize<'de> for SyncState { let mut data_channels__ = None; let mut offer__ = None; let mut track_sids_disabled__ = None; + let mut datachannel_receive_states__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::Answer => { @@ -34404,6 +34699,12 @@ impl<'de> serde::Deserialize<'de> for SyncState { } track_sids_disabled__ = Some(map_.next_value()?); } + GeneratedField::DatachannelReceiveStates => { + if datachannel_receive_states__.is_some() { + return Err(serde::de::Error::duplicate_field("datachannelReceiveStates")); + } + datachannel_receive_states__ = Some(map_.next_value()?); + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -34416,6 +34717,7 @@ impl<'de> serde::Deserialize<'de> for SyncState { data_channels: data_channels__.unwrap_or_default(), offer: offer__, track_sids_disabled: track_sids_disabled__.unwrap_or_default(), + datachannel_receive_states: datachannel_receive_states__.unwrap_or_default(), }) } } diff --git a/livekit/src/proto.rs b/livekit/src/proto.rs index 698a27d5a..ef833a95a 100644 --- a/livekit/src/proto.rs +++ b/livekit/src/proto.rs @@ -48,6 +48,7 @@ impl From for participant::DisconnectReason { DisconnectReason::UserRejected => Self::UserRejected, DisconnectReason::SipTrunkFailure => Self::SipTrunkFailure, DisconnectReason::ConnectionTimeout => Self::ConnectionTimeout, + DisconnectReason::MediaFailure => Self::MediaFailure, } } } diff --git a/livekit/src/room/data_stream/outgoing.rs b/livekit/src/room/data_stream/outgoing.rs index 180d53caf..68f18dcf2 100644 --- a/livekit/src/room/data_stream/outgoing.rs +++ b/livekit/src/room/data_stream/outgoing.rs @@ -200,6 +200,8 @@ impl RawStream { participant_identity: String::new(), // populate later destination_identities: destination_identities.into_iter().map(|id| id.0).collect(), value: Some(livekit_protocol::data_packet::Value::StreamHeader(header)), + // TODO: placeholder for reliable data transport + ..Default::default() } } diff --git a/livekit/src/room/mod.rs b/livekit/src/room/mod.rs index d387843e0..2dd46863e 100644 --- a/livekit/src/room/mod.rs +++ b/livekit/src/room/mod.rs @@ -221,6 +221,15 @@ pub enum RoomEvent { kind: DataPacketKind, threshold: u64, }, + RoomUpdated { + room: RoomInfo, + }, + Moved { + room: RoomInfo, + }, + ParticipantsUpdated { + participants: Vec, + }, } #[derive(Debug, Clone, Copy, Eq, PartialEq)] @@ -391,14 +400,24 @@ impl Debug for Room { } } -struct RoomInfo { - metadata: String, - state: ConnectionState, - lossy_dc_options: DataChannelOptions, - reliable_dc_options: DataChannelOptions, +#[derive(Clone, Debug)] +pub struct RoomInfo { + pub sid: Option, + pub name: String, + pub metadata: String, + pub state: ConnectionState, + pub lossy_dc_options: DataChannelOptions, + pub reliable_dc_options: DataChannelOptions, + pub empty_timeout: u32, + pub departure_timeout: u32, + pub max_participants: u32, + pub creation_time: i64, + pub num_publishers: u32, + pub num_participants: u32, + pub active_recording: bool, } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct DataChannelOptions { pub buffered_amount_low_threshold: u64, } @@ -411,8 +430,7 @@ impl Default for DataChannelOptions { pub(crate) struct RoomSession { rtc_engine: Arc, - sid: Promise, - name: String, + sid_promise: Promise, info: RwLock, dispatcher: Dispatcher, options: RoomOptions, @@ -434,9 +452,10 @@ struct Handle { impl Debug for RoomSession { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let info = self.info.read(); f.debug_struct("SessionInner") - .field("sid", &self.sid.try_result()) - .field("name", &self.name) + .field("sid", &info.sid) + .field("name", &info.name) .field("rtc_engine", &self.rtc_engine) .finish() } @@ -562,11 +581,19 @@ impl Room { let room_info = join_response.room.unwrap(); let inner = Arc::new(RoomSession { - sid: Promise::new(), - name: room_info.name, + sid_promise: Promise::new(), info: RwLock::new(RoomInfo { - state: ConnectionState::Disconnected, + sid: room_info.sid.try_into().ok(), + name: room_info.name, metadata: room_info.metadata, + empty_timeout: room_info.empty_timeout, + departure_timeout: room_info.departure_timeout, + max_participants: room_info.max_participants, + creation_time: room_info.creation_time, + num_publishers: room_info.num_publishers, + num_participants: room_info.num_participants, + active_recording: room_info.active_recording, + state: ConnectionState::Disconnected, lossy_dc_options: Default::default(), reliable_dc_options: Default::default(), }), @@ -675,15 +702,20 @@ impl Room { } pub async fn sid(&self) -> RoomSid { - self.inner.sid.result().await + // sid could have been updated due to room move + let sid = self.inner.info.read().sid.clone(); + if sid.is_none() { + return self.inner.sid_promise.result().await; + } + sid.unwrap() } pub fn maybe_sid(&self) -> Option { - self.inner.sid.try_result() + self.inner.info.read().sid.clone() } pub fn name(&self) -> String { - self.inner.name.clone() + self.inner.info.read().name.clone() } pub fn metadata(&self) -> String { @@ -712,6 +744,34 @@ impl Room { DataPacketKind::Reliable => self.inner.info.read().reliable_dc_options.clone(), } } + + pub fn empty_timeout(&self) -> u32 { + self.inner.info.read().empty_timeout + } + + pub fn departure_timeout(&self) -> u32 { + self.inner.info.read().departure_timeout + } + + pub fn max_participants(&self) -> u32 { + self.inner.info.read().max_participants + } + + pub fn creation_time(&self) -> i64 { + self.inner.info.read().creation_time + } + + pub fn num_participants(&self) -> u32 { + self.inner.info.read().num_participants + } + + pub fn num_publishers(&self) -> u32 { + self.inner.info.read().num_publishers + } + + pub fn active_recording(&self) -> bool { + self.inner.info.read().active_recording + } } impl RoomSession { @@ -759,6 +819,7 @@ impl RoomSession { self.handle_media_track(track, stream, transceiver) } EngineEvent::RoomUpdate { room } => self.handle_room_update(room), + EngineEvent::RoomMoved { moved } => self.handle_room_moved(moved), EngineEvent::Resuming(tx) => self.handle_resuming(tx), EngineEvent::Resumed(tx) => self.handle_resumed(tx), EngineEvent::SignalResumed { reconnect_response, tx } => { @@ -875,6 +936,8 @@ impl RoomSession { /// It'll create, update or remove a participant /// It also update the participant tracks. fn handle_participant_update(self: &Arc, updates: Vec) { + // only update non-disconnected participants to refresh info + let mut participants: Vec = Vec::new(); for pi in updates { let participant_sid = pi.sid.clone().try_into().unwrap(); let participant_identity: ParticipantIdentity = pi.identity.clone().into(); @@ -883,6 +946,7 @@ impl RoomSession { || participant_identity == self.local_participant.identity() { self.local_participant.clone().update_info(pi); + participants.push(Participant::Local(self.local_participant.clone())); continue; } @@ -908,6 +972,7 @@ impl RoomSession { } } else if let Some(remote_participant) = remote_participant { remote_participant.update_info(pi.clone()); + participants.push(Participant::Remote(remote_participant)); } else { // Create a new participant let remote_participant = { @@ -928,6 +993,9 @@ impl RoomSession { remote_participant.update_info(pi.clone()); // Add tracks } } + if !participants.is_empty() { + self.dispatcher.dispatch(&RoomEvent::ParticipantsUpdated { participants }); + } } fn handle_media_track( @@ -1099,10 +1167,12 @@ impl RoomSession { answer: Some(proto::SessionDescription { sdp: answer.to_string(), r#type: answer.sdp_type().to_string(), + id: 0, }), offer: Some(proto::SessionDescription { sdp: offer.to_string(), r#type: offer.sdp_type().to_string(), + id: 0, }), track_sids_disabled: Vec::default(), // TODO: New protocol version subscription: Some(proto::UpdateSubscription { @@ -1112,6 +1182,8 @@ impl RoomSession { }), publish_tracks: self.local_participant.published_tracks_info(), data_channels: dcs, + // unimplemented, stubbed for now + datachannel_receive_states: Vec::new(), }; log::debug!("sending sync state {:?}", sync_state); @@ -1121,15 +1193,69 @@ impl RoomSession { fn handle_room_update(self: &Arc, room: proto::Room) { let mut info = self.info.write(); let old_metadata = std::mem::replace(&mut info.metadata, room.metadata.clone()); + let mut updated = false; if old_metadata != room.metadata { + updated = true; self.dispatcher.dispatch(&RoomEvent::RoomMetadataChanged { old_metadata, metadata: info.metadata.clone(), }); } if !room.sid.is_empty() { - let _ = self.sid.resolve(room.sid.try_into().unwrap()); + let sid = room.sid.try_into().ok(); + info.sid = sid.clone(); + if let Some(sid) = sid { + let _ = self.sid_promise.resolve(sid); + } + } + if info.name != room.name { + updated = true; + info.name = room.name; + } + if info.empty_timeout != room.empty_timeout { + updated = true; + info.empty_timeout = room.empty_timeout; + } + if info.departure_timeout != room.departure_timeout { + updated = true; + info.departure_timeout = room.departure_timeout; + } + if info.max_participants != room.max_participants { + updated = true; + info.max_participants = room.max_participants; + } + if info.num_participants != room.num_participants { + updated = true; + info.num_participants = room.num_participants; + } + if info.num_publishers != room.num_publishers { + updated = true; + info.num_publishers = room.num_publishers; + } + if info.active_recording != room.active_recording { + updated = true; + info.active_recording = room.active_recording; + } + info.creation_time = room.creation_time_ms; + if updated { + self.dispatcher.dispatch(&RoomEvent::RoomUpdated { room: info.clone() }); + } + } + + fn handle_room_moved(self: &Arc, moved: proto::RoomMovedResponse) { + self.handle_refresh_token(self.rtc_engine.session().signal_client().url(), moved.token); + if let Some(local_participant) = moved.participant { + self.local_participant.update_info(local_participant); + self.dispatcher.dispatch(&RoomEvent::ParticipantsUpdated { + participants: vec![Participant::Local(self.local_participant.clone())], + }); + } + self.handle_participant_update(moved.other_participants); + if let Some(room) = moved.room { + self.handle_room_update(room); } + let info = self.info.read(); + self.dispatcher.dispatch(&RoomEvent::Moved { room: info.clone() }); } fn handle_resuming(self: &Arc, tx: oneshot::Sender<()>) { diff --git a/livekit/src/room/participant/mod.rs b/livekit/src/room/participant/mod.rs index 1f0d9b703..b5ab45f73 100644 --- a/livekit/src/room/participant/mod.rs +++ b/livekit/src/room/participant/mod.rs @@ -63,6 +63,7 @@ pub enum DisconnectReason { UserRejected, SipTrunkFailure, ConnectionTimeout, + MediaFailure, } #[derive(Debug, Clone)] diff --git a/livekit/src/rtc_engine/mod.rs b/livekit/src/rtc_engine/mod.rs index 16c724487..363bd6eb2 100644 --- a/livekit/src/rtc_engine/mod.rs +++ b/livekit/src/rtc_engine/mod.rs @@ -138,6 +138,9 @@ pub enum EngineEvent { RoomUpdate { room: proto::Room, }, + RoomMoved { + moved: proto::RoomMovedResponse, + }, /// The following events are used to notify the room about the reconnection state /// Since the room needs to also sync state in a good timing with the server. /// We synchronize the state with a one-shot channel. @@ -541,6 +544,9 @@ impl EngineInner { SessionEvent::RoomUpdate { room } => { let _ = self.engine_tx.send(EngineEvent::RoomUpdate { room }); } + SessionEvent::RoomMoved { moved } => { + let _ = self.engine_tx.send(EngineEvent::RoomMoved { moved }); + } SessionEvent::LocalTrackSubscribed { track_sid } => { let _ = self.engine_tx.send(EngineEvent::LocalTrackSubscribed { track_sid }); } diff --git a/livekit/src/rtc_engine/rtc_session.rs b/livekit/src/rtc_engine/rtc_session.rs index 3ea40ed50..9ea670072 100644 --- a/livekit/src/rtc_engine/rtc_session.rs +++ b/livekit/src/rtc_engine/rtc_session.rs @@ -152,6 +152,9 @@ pub enum SessionEvent { RoomUpdate { room: proto::Room, }, + RoomMoved { + moved: proto::RoomMovedResponse, + }, LocalTrackSubscribed { track_sid: String, }, @@ -683,6 +686,7 @@ impl SessionInner { .send(proto::signal_request::Message::Answer(proto::SessionDescription { r#type: "answer".to_string(), sdp: answer.to_string(), + id: 0, })) .await; } @@ -735,6 +739,9 @@ impl SessionInner { let _ = self.emitter.send(SessionEvent::RoomUpdate { room: room_update.room.unwrap() }); } + proto::signal_response::Message::RoomMoved(room_moved) => { + let _ = self.emitter.send(SessionEvent::RoomMoved { moved: room_moved }); + } proto::signal_response::Message::TrackSubscribed(track_subscribed) => { let _ = self.emitter.send(SessionEvent::LocalTrackSubscribed { track_sid: track_subscribed.track_sid, @@ -803,6 +810,7 @@ impl SessionInner { .send(proto::signal_request::Message::Offer(proto::SessionDescription { r#type: "offer".to_string(), sdp: offer.to_string(), + id: 0, })) .await; } From 10981d296195aaa6fc9a4d38665ea925af3f5c19 Mon Sep 17 00:00:00 2001 From: Daisuke Murase Date: Tue, 17 Jun 2025 13:57:09 -0700 Subject: [PATCH 213/274] add webrtc-sys-build to release-plz managed crates (#666) --- release-plz.toml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/release-plz.toml b/release-plz.toml index 8f675b05b..4dfbaedfc 100644 --- a/release-plz.toml +++ b/release-plz.toml @@ -45,6 +45,12 @@ changelog_path = "webrtc-sys/CHANGELOG.md" publish = true release = true +[[package]] +name = "webrtc-sys-build" +changelog_path = "webrtc-sys/build/CHANGELOG.md" +publish = true +release = true + [[package]] name = "yuv-sys" changelog_path = "yuv-sys/CHANGELOG.md" From 48ef188eecf7dc1fcebfa1fce46e96d22f5ae345 Mon Sep 17 00:00:00 2001 From: Daisuke Murase Date: Tue, 17 Jun 2025 14:04:59 -0700 Subject: [PATCH 214/274] add soxr desc (#669) --- soxr-sys/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/soxr-sys/Cargo.toml b/soxr-sys/Cargo.toml index fde7a4c42..5e6d3a39f 100644 --- a/soxr-sys/Cargo.toml +++ b/soxr-sys/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" authors = ["Theo Monnom Date: Tue, 17 Jun 2025 14:11:29 -0700 Subject: [PATCH 215/274] add livekit-ffi to release-plz management (#667) * add livekit-ffi * needs --no-verify for ffs --- release-plz.toml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/release-plz.toml b/release-plz.toml index 4dfbaedfc..e1f066c59 100644 --- a/release-plz.toml +++ b/release-plz.toml @@ -56,3 +56,24 @@ name = "yuv-sys" changelog_path = "yuv-sys/CHANGELOG.md" publish = true release = true + +[[package]] +name = "soxr-sys" +changelog_path = "soxr-sys/CHANGELOG.md" +publish = true +release = true + +[[package]] +name = "livekit-ffi" +changelog_path = "livekit-ffi/CHANGELOG.md" +publish = true +publish_no_verify = true +release = true +git_release_enable = false # existing CI manages GitHub release +changelog_include = [ + "livekit", + "soxr-sys", + "imgproc", + "livekit-protocol", + "webrtc-sys-build", +] From ce6fb8b4a0f5d8d0f0b584e51730fb028d1d60ee Mon Sep 17 00:00:00 2001 From: Daisuke Murase Date: Tue, 17 Jun 2025 14:12:12 -0700 Subject: [PATCH 216/274] fix(webrtc-sys-build): add temporary workaround to fix ci in Windows (#665) * add context * oops * test * Revert "test" This reverts commit 5676e129f8b3b5cff0caed0402a1649179fbd6a7. * test * use _all --- webrtc-sys/build/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/webrtc-sys/build/src/lib.rs b/webrtc-sys/build/src/lib.rs index d09986451..87cbeb48e 100644 --- a/webrtc-sys/build/src/lib.rs +++ b/webrtc-sys/build/src/lib.rs @@ -183,6 +183,8 @@ pub fn configure_jni_symbols() -> Result<()> { pub fn download_webrtc() -> Result<()> { let dir = scratch::path(SCRATH_PATH); + // temporary fix to avoid github workflow issue + fs::create_dir_all(&dir).context("Failed to create scratch_path")?; let flock = File::create(dir.join(".lock")) .context("Failed to create lock file for WebRTC download")?; flock.lock_exclusive().context("Failed to acquire exclusive lock for WebRTC download")?; From 89355fbb351014e39a8df5d599532972382f1326 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 14:30:53 -0700 Subject: [PATCH 217/274] chore: release (#668) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- Cargo.lock | 14 +++++++------- Cargo.toml | 14 +++++++------- libwebrtc/CHANGELOG.md | 6 ++++++ libwebrtc/Cargo.toml | 2 +- livekit-api/CHANGELOG.md | 6 ++++++ livekit-api/Cargo.toml | 2 +- livekit-ffi/CHANGELOG.md | 15 +++++++++++++++ livekit-ffi/Cargo.toml | 2 +- livekit-protocol/CHANGELOG.md | 6 ++++++ livekit-protocol/Cargo.toml | 2 +- livekit/CHANGELOG.md | 6 ++++++ livekit/Cargo.toml | 2 +- webrtc-sys/CHANGELOG.md | 6 ++++++ webrtc-sys/Cargo.toml | 2 +- webrtc-sys/build/CHANGELOG.md | 14 ++++++++++++++ webrtc-sys/build/Cargo.toml | 2 +- 16 files changed, 80 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 12ef438c4..2fa488e26 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1560,7 +1560,7 @@ dependencies = [ [[package]] name = "libwebrtc" -version = "0.3.11" +version = "0.3.12" dependencies = [ "cxx", "env_logger", @@ -1616,7 +1616,7 @@ checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "livekit" -version = "0.7.12" +version = "0.7.13" dependencies = [ "bmrng", "bytes", @@ -1640,7 +1640,7 @@ dependencies = [ [[package]] name = "livekit-api" -version = "0.4.3" +version = "0.4.4" dependencies = [ "async-tungstenite", "base64", @@ -1670,7 +1670,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.12.26" +version = "0.12.27" dependencies = [ "bytes", "console-subscriber", @@ -1696,7 +1696,7 @@ dependencies = [ [[package]] name = "livekit-protocol" -version = "0.3.10" +version = "0.4.0" dependencies = [ "futures-util", "livekit-runtime", @@ -3367,7 +3367,7 @@ checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" [[package]] name = "webrtc-sys" -version = "0.3.8" +version = "0.3.9" dependencies = [ "cc", "cxx", @@ -3380,7 +3380,7 @@ dependencies = [ [[package]] name = "webrtc-sys-build" -version = "0.3.6" +version = "0.3.7" dependencies = [ "anyhow", "fs2", diff --git a/Cargo.toml b/Cargo.toml index 022d1279c..a335c5bd2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,12 +17,12 @@ members = [ [workspace.dependencies] imgproc = { version = "0.3.12", path = "imgproc" } yuv-sys = { version = "0.3.7", path = "yuv-sys" } -libwebrtc = { version = "0.3.11", path = "libwebrtc" } -livekit-api = { version = "0.4.3", path = "livekit-api" } -livekit-ffi = { version = "0.12.26", path = "livekit-ffi" } -livekit-protocol = { version = "0.3.10", path = "livekit-protocol" } +libwebrtc = { version = "0.3.12", path = "libwebrtc" } +livekit-api = { version = "0.4.4", path = "livekit-api" } +livekit-ffi = { version = "0.12.27", path = "livekit-ffi" } +livekit-protocol = { version = "0.4.0", path = "livekit-protocol" } livekit-runtime = { version = "0.4.0", path = "livekit-runtime" } -livekit = { version = "0.7.12", path = "livekit" } +livekit = { version = "0.7.13", path = "livekit" } soxr-sys = { version = "0.1.0", path = "soxr-sys" } -webrtc-sys-build = { version = "0.3.6", path = "webrtc-sys/build" } -webrtc-sys = { version = "0.3.8", path = "webrtc-sys" } +webrtc-sys-build = { version = "0.3.7", path = "webrtc-sys/build" } +webrtc-sys = { version = "0.3.9", path = "webrtc-sys" } diff --git a/libwebrtc/CHANGELOG.md b/libwebrtc/CHANGELOG.md index 2e209b2da..a42e1b0cd 100644 --- a/libwebrtc/CHANGELOG.md +++ b/libwebrtc/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [0.3.12](https://github.com/livekit/rust-sdks/compare/rust-sdks/libwebrtc@0.3.11...rust-sdks/libwebrtc@0.3.12) - 2025-06-17 + +### Other + +- updated the following local packages: livekit-protocol, webrtc-sys + ## [0.3.11](https://github.com/livekit/rust-sdks/compare/rust-sdks/libwebrtc@0.3.10...rust-sdks/libwebrtc@0.3.11) - 2025-06-11 ### Fixed diff --git a/libwebrtc/Cargo.toml b/libwebrtc/Cargo.toml index 4c58b6511..3cb207e62 100644 --- a/libwebrtc/Cargo.toml +++ b/libwebrtc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libwebrtc" -version = "0.3.11" +version = "0.3.12" edition = "2021" homepage = "https://livekit.io" license = "Apache-2.0" diff --git a/livekit-api/CHANGELOG.md b/livekit-api/CHANGELOG.md index 9d8d3994b..368f990ae 100644 --- a/livekit-api/CHANGELOG.md +++ b/livekit-api/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [0.4.4](https://github.com/livekit/rust-sdks/compare/rust-sdks/livekit-api@0.4.3...rust-sdks/livekit-api@0.4.4) - 2025-06-17 + +### Other + +- Expose room updates, support MoveParticipant (protocol 15) ([#662](https://github.com/livekit/rust-sdks/pull/662)) + ## [0.4.2] - 2025-02-03 ### Added diff --git a/livekit-api/Cargo.toml b/livekit-api/Cargo.toml index 6b3d6fc90..d93973e06 100644 --- a/livekit-api/Cargo.toml +++ b/livekit-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-api" -version = "0.4.3" +version = "0.4.4" license = "Apache-2.0" description = "Rust Server SDK for LiveKit" edition = "2021" diff --git a/livekit-ffi/CHANGELOG.md b/livekit-ffi/CHANGELOG.md index b778cc1ad..bca5e0854 100644 --- a/livekit-ffi/CHANGELOG.md +++ b/livekit-ffi/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## [0.12.27](https://github.com/livekit/rust-sdks/compare/rust-sdks/livekit-ffi@0.12.26...rust-sdks/livekit-ffi@0.12.27) - 2025-06-17 + +### Fixed + +- *(webrtc-sys-build)* add temporary workaround to fix ci in Windows ([#665](https://github.com/livekit/rust-sdks/pull/665)) +- *(webrtc-sys-build)* add error context to debug issues ([#664](https://github.com/livekit/rust-sdks/pull/664)) + +### Other + +- Expose room updates, support MoveParticipant (protocol 15) ([#662](https://github.com/livekit/rust-sdks/pull/662)) +- use path.join instead of hardcoded `/` ([#663](https://github.com/livekit/rust-sdks/pull/663)) +- bump version for webrtc (fix win CI) ([#650](https://github.com/livekit/rust-sdks/pull/650)) +- try to fix webrtc build for iOS/macOS. ([#646](https://github.com/livekit/rust-sdks/pull/646)) +- remove ([#633](https://github.com/livekit/rust-sdks/pull/633)) + ## [0.12.20] - 2025-04-08 ### Added diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 171448238..08814d547 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.12.26" +version = "0.12.27" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" diff --git a/livekit-protocol/CHANGELOG.md b/livekit-protocol/CHANGELOG.md index e532bf5df..918790197 100644 --- a/livekit-protocol/CHANGELOG.md +++ b/livekit-protocol/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [0.4.0](https://github.com/livekit/rust-sdks/compare/rust-sdks/livekit-protocol@0.3.10...rust-sdks/livekit-protocol@0.4.0) - 2025-06-17 + +### Other + +- Expose room updates, support MoveParticipant (protocol 15) ([#662](https://github.com/livekit/rust-sdks/pull/662)) + ## [0.3.9] - 2025-03-04 ### Fixed diff --git a/livekit-protocol/Cargo.toml b/livekit-protocol/Cargo.toml index 248915203..91c49ff72 100644 --- a/livekit-protocol/Cargo.toml +++ b/livekit-protocol/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-protocol" -version = "0.3.10" +version = "0.4.0" edition = "2021" license = "Apache-2.0" description = "Livekit protocol and utilities for the Rust SDK" diff --git a/livekit/CHANGELOG.md b/livekit/CHANGELOG.md index c5e0c5fc2..3640fc95b 100644 --- a/livekit/CHANGELOG.md +++ b/livekit/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [0.7.13](https://github.com/livekit/rust-sdks/compare/rust-sdks/livekit@0.7.12...rust-sdks/livekit@0.7.13) - 2025-06-17 + +### Other + +- Expose room updates, support MoveParticipant (protocol 15) ([#662](https://github.com/livekit/rust-sdks/pull/662)) + ## [0.7.12](https://github.com/livekit/rust-sdks/compare/rust-sdks/livekit@0.7.11...rust-sdks/livekit@0.7.12) - 2025-06-11 ### Fixed diff --git a/livekit/Cargo.toml b/livekit/Cargo.toml index dc3b7979f..f8c8c5353 100644 --- a/livekit/Cargo.toml +++ b/livekit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit" -version = "0.7.12" +version = "0.7.13" edition = "2021" license = "Apache-2.0" description = "Rust Client SDK for LiveKit" diff --git a/webrtc-sys/CHANGELOG.md b/webrtc-sys/CHANGELOG.md index c21623b2e..9e77f8c62 100644 --- a/webrtc-sys/CHANGELOG.md +++ b/webrtc-sys/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [0.3.9](https://github.com/livekit/rust-sdks/compare/rust-sdks/webrtc-sys@0.3.8...rust-sdks/webrtc-sys@0.3.9) - 2025-06-17 + +### Other + +- updated the following local packages: webrtc-sys-build + ## [0.3.8](https://github.com/livekit/rust-sdks/compare/rust-sdks/webrtc-sys@0.3.7...rust-sdks/webrtc-sys@0.3.8) - 2025-06-11 ### Fixed diff --git a/webrtc-sys/Cargo.toml b/webrtc-sys/Cargo.toml index c1fa7cd81..1d13543c8 100644 --- a/webrtc-sys/Cargo.toml +++ b/webrtc-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "webrtc-sys" -version = "0.3.8" +version = "0.3.9" edition = "2021" homepage = "https://livekit.io" license = "Apache-2.0" diff --git a/webrtc-sys/build/CHANGELOG.md b/webrtc-sys/build/CHANGELOG.md index 215126331..d1233689b 100644 --- a/webrtc-sys/build/CHANGELOG.md +++ b/webrtc-sys/build/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## [0.3.7](https://github.com/livekit/rust-sdks/compare/rust-sdks/webrtc-sys-build@0.3.6...rust-sdks/webrtc-sys-build@0.3.7) - 2025-06-17 + +### Fixed + +- *(webrtc-sys-build)* add temporary workaround to fix ci in Windows ([#665](https://github.com/livekit/rust-sdks/pull/665)) +- *(webrtc-sys-build)* add error context to debug issues ([#664](https://github.com/livekit/rust-sdks/pull/664)) + +### Other + +- use path.join instead of hardcoded `/` ([#663](https://github.com/livekit/rust-sdks/pull/663)) +- bump version for webrtc (fix win CI) ([#650](https://github.com/livekit/rust-sdks/pull/650)) +- try to fix webrtc build for iOS/macOS. ([#646](https://github.com/livekit/rust-sdks/pull/646)) +- remove ([#633](https://github.com/livekit/rust-sdks/pull/633)) + ## [0.3.6] - 2024-12-14 ### Added diff --git a/webrtc-sys/build/Cargo.toml b/webrtc-sys/build/Cargo.toml index 41d46faa3..2039d6671 100644 --- a/webrtc-sys/build/Cargo.toml +++ b/webrtc-sys/build/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "webrtc-sys-build" -version = "0.3.6" +version = "0.3.7" edition = "2021" license = "Apache-2.0" description = "Build utilities when working with libwebrtc" From 3003bf2d0838452cc6d815d5e2789f95cdfb84d0 Mon Sep 17 00:00:00 2001 From: Daisuke Murase Date: Wed, 18 Jun 2025 09:41:36 -0700 Subject: [PATCH 218/274] call ffi-build workflow after release (#670) --- .github/workflows/release.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 492750fcb..4ab14982e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,11 +27,25 @@ jobs: uses: dtolnay/rust-toolchain@stable - name: Run release-plz uses: release-plz/action@v0.5 + id: release-plz with: command: release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_TOKEN }} + - name: Trigger FFI workflow if needed + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RELEASES: ${{ steps.release-plz.outputs.releases }} + run: | + set -e + echo "releases: $RELEASES" + + ffi_tag=$(echo "$RELEASES" | jq -r '.[].tag' | grep 'rust-sdks/livekit-ffi@' || true) + if [ -n "$ffi_tag" ]; then + echo "Found ffi_tag: $ffi_tag" + gh workflow run ffi-builds.yml --ref $ffi_tag + fi # Create a PR with the new versions and changelog, preparing the next release. release-plz-pr: From bcffbe2532f0465a76c91425c75a5a076300b076 Mon Sep 17 00:00:00 2001 From: Daisuke Murase Date: Mon, 23 Jun 2025 11:41:01 -0700 Subject: [PATCH 219/274] fix: `audio_frame_ms` didn't work expectedly (#671) * fix buf split logic * we need to update len --- livekit-ffi/src/server/audio_stream.rs | 3 +-- livekit/src/plugin.rs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/livekit-ffi/src/server/audio_stream.rs b/livekit-ffi/src/server/audio_stream.rs index b9bb2d5b0..d383158c2 100644 --- a/livekit-ffi/src/server/audio_stream.rs +++ b/livekit-ffi/src/server/audio_stream.rs @@ -394,8 +394,7 @@ impl FfiAudioStream { if let Some(target) = target_samples { buf.extend_from_slice(&frame.data); while buf.len() >= target { - let data = buf.split_off(target); - let mut frame_data = std::mem::replace(&mut buf, data); + let frame_data = buf.drain(..target).collect::>(); let new_frame = AudioFrame { data: Cow::Owned(frame_data), sample_rate, diff --git a/livekit/src/plugin.rs b/livekit/src/plugin.rs index ce84ec813..9ffb06090 100644 --- a/livekit/src/plugin.rs +++ b/livekit/src/plugin.rs @@ -294,7 +294,7 @@ impl Stream for AudioFilterAudioStream { if this.buffer.len() >= this.frame_size { let data = this.buffer.drain(..this.frame_size).collect::>(); - let mut out: Vec = Vec::with_capacity(this.frame_size); + let mut out: Vec = vec![0; this.frame_size]; this.session.process_i16(this.frame_size, &data, &mut out); From ab99f24eb4f55fe0c32a462faf1d74c752ffd76d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 23 Jun 2025 11:52:52 -0700 Subject: [PATCH 220/274] chore: release (#672) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- Cargo.toml | 4 ++-- livekit-ffi/CHANGELOG.md | 6 ++++++ livekit-ffi/Cargo.toml | 2 +- livekit/CHANGELOG.md | 6 ++++++ livekit/Cargo.toml | 2 +- 6 files changed, 18 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2fa488e26..47b02db52 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1616,7 +1616,7 @@ checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "livekit" -version = "0.7.13" +version = "0.7.14" dependencies = [ "bmrng", "bytes", @@ -1670,7 +1670,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.12.27" +version = "0.12.28" dependencies = [ "bytes", "console-subscriber", diff --git a/Cargo.toml b/Cargo.toml index a335c5bd2..d8acbc075 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,10 +19,10 @@ imgproc = { version = "0.3.12", path = "imgproc" } yuv-sys = { version = "0.3.7", path = "yuv-sys" } libwebrtc = { version = "0.3.12", path = "libwebrtc" } livekit-api = { version = "0.4.4", path = "livekit-api" } -livekit-ffi = { version = "0.12.27", path = "livekit-ffi" } +livekit-ffi = { version = "0.12.28", path = "livekit-ffi" } livekit-protocol = { version = "0.4.0", path = "livekit-protocol" } livekit-runtime = { version = "0.4.0", path = "livekit-runtime" } -livekit = { version = "0.7.13", path = "livekit" } +livekit = { version = "0.7.14", path = "livekit" } soxr-sys = { version = "0.1.0", path = "soxr-sys" } webrtc-sys-build = { version = "0.3.7", path = "webrtc-sys/build" } webrtc-sys = { version = "0.3.9", path = "webrtc-sys" } diff --git a/livekit-ffi/CHANGELOG.md b/livekit-ffi/CHANGELOG.md index bca5e0854..f96e1b59f 100644 --- a/livekit-ffi/CHANGELOG.md +++ b/livekit-ffi/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [0.12.28](https://github.com/livekit/rust-sdks/compare/rust-sdks/livekit-ffi@0.12.27...rust-sdks/livekit-ffi@0.12.28) - 2025-06-23 + +### Fixed + +- `audio_frame_ms` didn't work expectedly ([#671](https://github.com/livekit/rust-sdks/pull/671)) + ## [0.12.27](https://github.com/livekit/rust-sdks/compare/rust-sdks/livekit-ffi@0.12.26...rust-sdks/livekit-ffi@0.12.27) - 2025-06-17 ### Fixed diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 08814d547..295df522a 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.12.27" +version = "0.12.28" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" diff --git a/livekit/CHANGELOG.md b/livekit/CHANGELOG.md index 3640fc95b..ba668e7f0 100644 --- a/livekit/CHANGELOG.md +++ b/livekit/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [0.7.14](https://github.com/livekit/rust-sdks/compare/rust-sdks/livekit@0.7.13...rust-sdks/livekit@0.7.14) - 2025-06-23 + +### Fixed + +- `audio_frame_ms` didn't work expectedly ([#671](https://github.com/livekit/rust-sdks/pull/671)) + ## [0.7.13](https://github.com/livekit/rust-sdks/compare/rust-sdks/livekit@0.7.12...rust-sdks/livekit@0.7.13) - 2025-06-17 ### Other diff --git a/livekit/Cargo.toml b/livekit/Cargo.toml index f8c8c5353..e99222006 100644 --- a/livekit/Cargo.toml +++ b/livekit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit" -version = "0.7.13" +version = "0.7.14" edition = "2021" license = "Apache-2.0" description = "Rust Client SDK for LiveKit" From 3268abacfe5ea788144b44c7a676652c3fada3d7 Mon Sep 17 00:00:00 2001 From: Daisuke Murase Date: Mon, 23 Jun 2025 12:27:31 -0700 Subject: [PATCH 221/274] need actions permission to run workflows (#673) --- .github/workflows/release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4ab14982e..72842afbb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,6 +3,7 @@ name: Release permissions: pull-requests: write contents: write + actions: write on: push: From 5aeba0ba56c98889fc8ba6de76692609d76e8506 Mon Sep 17 00:00:00 2001 From: Daisuke Murase Date: Wed, 16 Jul 2025 08:31:41 -0700 Subject: [PATCH 222/274] remove published tracks when the room is closed (#677) --- livekit/src/room/mod.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/livekit/src/room/mod.rs b/livekit/src/room/mod.rs index 2dd46863e..ee428e595 100644 --- a/livekit/src/room/mod.rs +++ b/livekit/src/room/mod.rs @@ -906,6 +906,11 @@ impl RoomSession { async fn close(&self, reason: DisconnectReason) -> RoomResult<()> { let Some(handle) = self.handle.lock().await.take() else { Err(RoomError::AlreadyClosed)? }; + // remove published tracks + for (sid, _) in self.local_participant.track_publications().iter() { + let _ = self.local_participant.unpublish_track(sid).await; + } + self.rtc_engine.close(reason).await; self.e2ee_manager.cleanup(); From 2d47d16448cf987b488b080adacb7f17ecc13cb8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 16 Jul 2025 08:50:50 -0700 Subject: [PATCH 223/274] chore: release (#679) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- Cargo.toml | 4 ++-- livekit-ffi/CHANGELOG.md | 6 ++++++ livekit-ffi/Cargo.toml | 2 +- livekit/CHANGELOG.md | 6 ++++++ livekit/Cargo.toml | 2 +- 6 files changed, 18 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 47b02db52..be2c8329e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1616,7 +1616,7 @@ checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "livekit" -version = "0.7.14" +version = "0.7.15" dependencies = [ "bmrng", "bytes", @@ -1670,7 +1670,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.12.28" +version = "0.12.29" dependencies = [ "bytes", "console-subscriber", diff --git a/Cargo.toml b/Cargo.toml index d8acbc075..da8d7ec4c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,10 +19,10 @@ imgproc = { version = "0.3.12", path = "imgproc" } yuv-sys = { version = "0.3.7", path = "yuv-sys" } libwebrtc = { version = "0.3.12", path = "libwebrtc" } livekit-api = { version = "0.4.4", path = "livekit-api" } -livekit-ffi = { version = "0.12.28", path = "livekit-ffi" } +livekit-ffi = { version = "0.12.29", path = "livekit-ffi" } livekit-protocol = { version = "0.4.0", path = "livekit-protocol" } livekit-runtime = { version = "0.4.0", path = "livekit-runtime" } -livekit = { version = "0.7.14", path = "livekit" } +livekit = { version = "0.7.15", path = "livekit" } soxr-sys = { version = "0.1.0", path = "soxr-sys" } webrtc-sys-build = { version = "0.3.7", path = "webrtc-sys/build" } webrtc-sys = { version = "0.3.9", path = "webrtc-sys" } diff --git a/livekit-ffi/CHANGELOG.md b/livekit-ffi/CHANGELOG.md index f96e1b59f..7bd532bbb 100644 --- a/livekit-ffi/CHANGELOG.md +++ b/livekit-ffi/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [0.12.29](https://github.com/livekit/rust-sdks/compare/rust-sdks/livekit-ffi@0.12.28...rust-sdks/livekit-ffi@0.12.29) - 2025-07-16 + +### Other + +- remove published tracks when the room is closed ([#677](https://github.com/livekit/rust-sdks/pull/677)) + ## [0.12.28](https://github.com/livekit/rust-sdks/compare/rust-sdks/livekit-ffi@0.12.27...rust-sdks/livekit-ffi@0.12.28) - 2025-06-23 ### Fixed diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 295df522a..b47b843fb 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.12.28" +version = "0.12.29" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" diff --git a/livekit/CHANGELOG.md b/livekit/CHANGELOG.md index ba668e7f0..1a4d38944 100644 --- a/livekit/CHANGELOG.md +++ b/livekit/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [0.7.15](https://github.com/livekit/rust-sdks/compare/rust-sdks/livekit@0.7.14...rust-sdks/livekit@0.7.15) - 2025-07-16 + +### Other + +- remove published tracks when the room is closed ([#677](https://github.com/livekit/rust-sdks/pull/677)) + ## [0.7.14](https://github.com/livekit/rust-sdks/compare/rust-sdks/livekit@0.7.13...rust-sdks/livekit@0.7.14) - 2025-06-23 ### Fixed diff --git a/livekit/Cargo.toml b/livekit/Cargo.toml index e99222006..52bdb91ac 100644 --- a/livekit/Cargo.toml +++ b/livekit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit" -version = "0.7.14" +version = "0.7.15" edition = "2021" license = "Apache-2.0" description = "Rust Client SDK for LiveKit" From eadb1b6a7d7d61ede5f25cb434044d37a34e9a8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Wed, 16 Jul 2025 21:50:34 +0200 Subject: [PATCH 224/274] fix SoxrResampler flush segv (#678) * Fix: prevent crash and ensure output buffer size in SoxResampler::flush() * change: linux release add debug info * Update resampler.rs * Update ffi-builds.yml --------- Co-authored-by: samwell --- livekit-ffi/src/server/resampler.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/livekit-ffi/src/server/resampler.rs b/livekit-ffi/src/server/resampler.rs index 6faa68b02..c8a1229a1 100644 --- a/livekit-ffi/src/server/resampler.rs +++ b/livekit-ffi/src/server/resampler.rs @@ -71,7 +71,13 @@ impl SoxResampler { return Err(error_msg.to_string_lossy().to_string()); } - Ok(Self { soxr_ptr, out_buf: Vec::new(), input_rate, output_rate, num_channels }) + Ok(Self { + soxr_ptr, + out_buf: Vec::with_capacity(output_rate as usize / 100), // ensure valid memory ptr + input_rate, + output_rate, + num_channels, + }) } pub fn push(&mut self, input: &[i16]) -> Result<&[i16], String> { @@ -134,7 +140,8 @@ impl SoxResampler { return Err(error_msg.to_string_lossy().to_string()); } - Ok(&self.out_buf[..odone]) + let output_samples = odone * self.num_channels as usize; + Ok(&self.out_buf[..output_samples]) } } From ce3301ce8ab5e50030693d99541d5b40277d37f4 Mon Sep 17 00:00:00 2001 From: Daisuke Murase Date: Thu, 17 Jul 2025 22:58:21 -0700 Subject: [PATCH 225/274] update workflows to use workflow_call (#681) * update workflows to use workflow_call * unable to use env. in if --- .github/workflows/ffi-builds.yml | 12 +++++++++--- .github/workflows/release.yml | 22 ++++++++++++++-------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ffi-builds.yml b/.github/workflows/ffi-builds.yml index 491a721cc..ab49e8338 100644 --- a/.github/workflows/ffi-builds.yml +++ b/.github/workflows/ffi-builds.yml @@ -19,9 +19,15 @@ on: tags: - "rust-sdks/livekit-ffi@*" workflow_dispatch: + workflow_call: + inputs: + tag: + required: false + type: string env: CARGO_TERM_COLOR: always + TAG_NAME: ${{ inputs.tag || github.ref_name }} jobs: build: @@ -207,7 +213,7 @@ jobs: needs: build permissions: contents: write - if: startsWith(github.ref, 'refs/tags/rust-sdks/livekit-ffi') + if: startsWith(inputs.tag || github.ref_name, 'rust-sdks/livekit-ffi@') env: GH_TOKEN: ${{ github.token }} steps: @@ -222,5 +228,5 @@ jobs: - name: Create draft release run: | - gh release create ${{ github.ref_name }} --draft --title "${{ github.ref_name }}" - gh release upload ${{ github.ref_name }} ${{ github.workspace }}/ffi-builds/* + gh release create ${{ env.TAG_NAME }} --draft --title "${{ env.TAG_NAME }}" + gh release upload ${{ env.TAG_NAME }} ${{ github.workspace }}/ffi-builds/* diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 72842afbb..ffc22443c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,6 +18,8 @@ jobs: if: ${{ github.repository_owner == 'livekit' }} permissions: contents: write + outputs: + ffi_tag: ${{ steps.ffi.outputs.ffi_tag }} steps: - name: Checkout repository uses: actions/checkout@v4 @@ -34,19 +36,23 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_TOKEN }} - - name: Trigger FFI workflow if needed + - name: Extract ffi tag + id: ffi env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} RELEASES: ${{ steps.release-plz.outputs.releases }} run: | set -e - echo "releases: $RELEASES" - ffi_tag=$(echo "$RELEASES" | jq -r '.[].tag' | grep 'rust-sdks/livekit-ffi@' || true) - if [ -n "$ffi_tag" ]; then - echo "Found ffi_tag: $ffi_tag" - gh workflow run ffi-builds.yml --ref $ffi_tag - fi + echo "ffi_tag=$ffi_tag" + echo "ffi_tag=$ffi_tag" >> $GITHUB_OUTPUT + + call-ffi: + name: Call FFI Builds + if: ${{ needs.release-plz-release.outputs.ffi_tag != '' }} + uses: ./.github/workflows/ffi-builds.yml + needs: release-plz-release + with: + tag: ${{ needs.release-plz-release.outputs.ffi_tag }} # Create a PR with the new versions and changelog, preparing the next release. release-plz-pr: From 5ded9c724dd22339f4ef1bd32383002d594c700a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 17 Jul 2025 23:03:41 -0700 Subject: [PATCH 226/274] chore(livekit-ffi): release v0.12.30 (#680) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- Cargo.lock | 2 +- Cargo.toml | 2 +- livekit-ffi/CHANGELOG.md | 6 ++++++ livekit-ffi/Cargo.toml | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index be2c8329e..0f9cad6ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1670,7 +1670,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.12.29" +version = "0.12.30" dependencies = [ "bytes", "console-subscriber", diff --git a/Cargo.toml b/Cargo.toml index da8d7ec4c..1e482dc00 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ imgproc = { version = "0.3.12", path = "imgproc" } yuv-sys = { version = "0.3.7", path = "yuv-sys" } libwebrtc = { version = "0.3.12", path = "libwebrtc" } livekit-api = { version = "0.4.4", path = "livekit-api" } -livekit-ffi = { version = "0.12.29", path = "livekit-ffi" } +livekit-ffi = { version = "0.12.30", path = "livekit-ffi" } livekit-protocol = { version = "0.4.0", path = "livekit-protocol" } livekit-runtime = { version = "0.4.0", path = "livekit-runtime" } livekit = { version = "0.7.15", path = "livekit" } diff --git a/livekit-ffi/CHANGELOG.md b/livekit-ffi/CHANGELOG.md index 7bd532bbb..7b14ceee5 100644 --- a/livekit-ffi/CHANGELOG.md +++ b/livekit-ffi/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [0.12.30](https://github.com/livekit/rust-sdks/compare/rust-sdks/livekit-ffi@0.12.29...rust-sdks/livekit-ffi@0.12.30) - 2025-07-18 + +### Fixed + +- fix SoxrResampler flush segv ([#678](https://github.com/livekit/rust-sdks/pull/678)) + ## [0.12.29](https://github.com/livekit/rust-sdks/compare/rust-sdks/livekit-ffi@0.12.28...rust-sdks/livekit-ffi@0.12.29) - 2025-07-16 ### Other diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index b47b843fb..5d8a0932a 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.12.29" +version = "0.12.30" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" From d2fb275fc3aea09212b80036db8b41b396647731 Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Mon, 21 Jul 2025 19:24:52 -0700 Subject: [PATCH 227/274] update readme (#682) --- .github/banner_dark.png | Bin 123952 -> 123451 bytes .github/banner_light.png | Bin 46138 -> 46246 bytes README.md | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/banner_dark.png b/.github/banner_dark.png index e9cb174e2dff1ac26c0949ffcc2776bcfe41bc0c..f4080d01206a61338d4242b6110e247c14fa5bc2 100644 GIT binary patch literal 123451 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

    W3I%K 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 literal 123952 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 z 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

    #&dV#;Fm$; zHxt1;eIQ{~uzAl*kAhDu(b*ODP}9TR*&)ed>KKZQIF(y^#|_B@#Z;PGiQGWgg`U#$}dw1=JE)o!@I{adi> zu1K-%^l9K>$`)$f!S0x#-e`ia;UjgZB^2=3d56mVNuE>$?j+o}+#S1eV(nN)`3x-U z0YhMnsQCQ>Gp!J4kmRM@GP3W1ZMpii`~)-{5Ckdc#uddyztyOer9NIbNG_Q@<-7&`hZ@T;<&QAlz`Z4R4hoK(6 z$AFA}U+mBp zyd(vJ1wiZT#4W@^uc@Sad@m!~1VPW&jjiZNMtd0ILC3e8?7wy{!Ld=) zU0pPPJgES>2a?n4w-^vE|G#5k*R}3TQMY+Zu-iqZoWoGhFYst=W2pUuO1%j- zwXvn>?Xs=w(;v@5W!W}PN3v9i!LHMok)!@{1{GQ39Gh-L|0H|luk5IbFkWw)X?`ib zjng<)%-e3(Ecg*N#JMb}wQ@Ma!H ztb=hFt8_DI&A4ywgoOL5=O!)mU==tM*&2$`1h0U`dJxjSP-**kxZ{Ad_t-yZWVSt3 zsI-a8SMSLg2ww|8Qq^0%;U;Eq>CFWU?D587sh4 z;O6 zTyd<+4o^^ubHox`S1ZidJSKz>pro&aA9G#DL+HfDP%8OkOkQPn@J{M;+etj@x4d*q zy1MD<`9426GB=Ogv^IwbV^D>1`~w2wX+oy=7jy^lI=UO0oEmGLz5NOEVkSE~Lrin} z-G|VOlMjDBHlM22J*P%i`C|Q;sYixNJrPL?oc(BQ<7tj{|9(E<%uaw6QNn_hr;Rf; zd7Pa{BcSoH>Z55-_3VHFdGR}H;p6NyEy$C!X=ZyR=4CJMV;}dsRLCU0EB`NxT{7TR z0C&(el>kNj=f~74`ss$g%leZZ9s8@L)hlki@s}3+%gFA3FfL0YF4&t8&uz+7Q?yV0m`1*Xi%IK5%(5>Dgeo~ApYRu6mk4e&xy@J0b3L`RveLG(akCgQh z7mCMRU_^yHvS><+KG6dCs>2!lZdGBS1$na20N*b=AFkIPzQSfd^AHEYE_2ugX8V?O z(4d`9!BGF0X1=RXD*x?!f1IzgBe*Q|&H&eY;^_2YBd@z9YjS$M(u5_8YEf81xOx8h z85XPAS||XTM}Ud1Z*&|>cfR@`rWf(UdKe1aiuHH16AZ{0>#!u!H$al2AjP2*uC zfR)BJCg8Q!QYOgQ?!L=p0upZlGoZ&?JZk%BV_WDZpDQZ#OGYhTrobMt`baH*+Gu_#OxO78|UupS3yk=_d9svrUqtnXdl ziA}wkBW%}{vzp1tbHe< zdt$A?EyI%gZno&fmau$(et6b%Kh~c^Zr^J64m#rR@0#CjI@6-vyfsi=e*$I@0E@n1 zq!7}{WmovpIzM!+p}8~lnQ;CwxhJWaUI12TpnIC~OXm2yBgO=B8~O zjD;nnw0d2nZ5((d=EcQn@xQMmknk_qZ7s53+U(3O`*o1WPk-#j;yPxcGJd>iG};3# zNk8ECg;l%);imvRag8dpz6R&S?NL$MYVb4~z1c}}xq`p^J&{0aQL|BJimDLbRUJN= zhB8M)_6zaA{^Z{$9=0B?^HC(fk)YCN63n$VH_HQg0}sXW((uiq?W7*a^ZCrGu<3Th zJbGm;0d<7eCxS2ik6A2;4Os<+S46%>YjJQ^(?~8znO36Lnhf9qG^_>q*h_a@K! z?gL4eI!T;_2~((RyIAQeDp~wl+{I>*J~FuGn9Rghe{K@BBVl8IwyyJeYuIfouZnZp zpU-uj*0k*Z(LdOeTSYz1e1~3HNuUU1pVaUY*(JGo1~nUPU8lc}B!9}3ix(#WgE*3# z*}a@_Zk1!}>Y8oezDpX?R9*8fTpX&Yv7orAN=c@fYmE-^Il^1YKB2g z>u#$#G~KV^qlx3XzY#j5Koz<>wnExOckM<)r{*z^Da+N7FwU5#>?*Dl6iEamIeS#%mi znHa=fH^4{UD$KT+m6em^b#r@Y+eYW(Wl^+6X1+ba%pkm zRXrvOihDqeeN|5;WJc5x7R$>U{bh$=^O7RqDDs8lpza@F(GqvkBe~LOtu*wFHrrVw z3Vh|*nd$IeouKm$PrxqDLHt`b160K3n{}l;r?7#yuT@dT^b{SDqZe%ahZ-f>oA z9M*Ny350jTHDCsMUe5y7vEEcZv3V6bv z12{77Tq74woqHh#Dv+PS%UxjcwdCYqo|v{t)gD~MYXk7Hg@En+IWf2EtP~J zm!I#6^;L(HR82fCgOaR;6FA7N9kxUG6_K-a^cNW@OLba)>mU2G<4#EVXgGQVweGX! z(Pfl=a!uj^t1vvL0Yb+94o^xP+K-w+@A~HwO@*%w{yQn zgaK4%O8exaKg0&_^l zl#(Xis}FJVxw6`IoSqRze~)L>F0z!D4xg0q$pDTsA*((8IbwC)tLq{Cp? zT_~)zag1n(Z#XOOR2h71cdM4^GzlRhrLsA1O_Es_NNE*4j&xSH!93W#j!MvT+u)rU zRR3;3^kx%l1R|)S+IXx&C1K-MrGqbc03YbAr}RcT=Ux zadIh%T)F6aetUAlYpYbhHUd@wfsWr>t(xyrjpR|*oL?5-o5e+sJj9%OWO=6cp6ul_ zE&>Q~kZ2V!fRf-4SHg@cj{l)=h9?W7bfur4rbjam^E^GI`3C3Sa#55!^mm@@A%xBU zcz+|_F>%+qy0f%|@;Kf5R+w5OB3pE&UF}0_)<85qfBDjsVU>>$#!&bdkacc{ zvINwRpYC8u{@cq~d7Nu4;cV?wTzi}9aSZ-E2Q`2*os5q2acLPMEVb4wkv|rSvHt5i z!YV3SJ>V+dTG{>MkN`WyLm5lP0~L2QIy8T_i2h*u8y-_z>PEsR?OOcK?4QP!S=@qb z=O=*aHarQ~8drWU`wt(HisUjT$^OKT0$rMbd4;38=^;#Eywa$bYz^T_4;&FHoup*a z(jg+HCA@xO-o367IxoyuM};To61ulSf6JX%?*&!rNsfP1Wmk{e)nscKkDiaONVu5Q zFApopirQpN>Eic8n{evz>bH9y0ZLw<89Xs&Vh(+VDsq0E>1PmoDl%CboVJcS!{XShjEP8p6XzW#LY>_SgQ zv!tQOqd+!_)~;}4pmzr>-wF2YZgxJZ9GGx(r$c#9-x1x&D3sEr5De=I%D-cIQaTfR zBr1Bd()p*@I?GS^*{zE6DIC`H{AjCecCW7#@F0C@2S_FLIITGFwgfxneuuq=ENlaXrqKVI_sfu4#nK%CBXvGw-=h zpqQy2&ee6op;z|EF_K@S@097d|B<(zC#Asw@C>Le9hd)^&-b;v>j{mlOEOJ4&#&{y zUubNwl?aKuJzf*N5$!c*e^`@RBWwyd6MS3QGpLR7x7(!fD>dGh)>;;1|GB86PNuQZ zh@vUDU~KyN;Xep1H&8Z9JbdNjTorrZ%3G&gL}*RDWLPkB;j~bCM#IM>#Z?Y>UH;Ej zV1Cl9gi$wa+qvfQE?=bM(cvEAt_t9sHNk`7{=ECdX95_suqw-r4rWO@A6Lz+!-R_E z9JNx%uU6RJ9_uJPnio8G)#N602zV1(jXbcQoUA7@Cx$!N09pzv?3wr?4Z=9U$F{4M zF3b{6fulDVXzD)t~Li*UjCy(`civl4D@!A*+BF?|02VUn; zss$lMyTFN+==Z2SI7-y`KU@Jd^pbnc_@NFz5#Yewi`iW7f#%h5mNVVy-wMyvBQ||V zc(BNFsj?}re_Ms$LPT*8v-Fc{-?rNH%Lz}>0DELoaz4s1QG$>^R;)W)($`@`+O36X zq#vpIyLoZY>%dYtlzQJbx>Ht;$Fpc!+*ivxicRWWSTj6}X0TrtI?nCxzZDLVemIyW zlct{}&AnNB%7yI#pZ8Ntt2A7)U(YYtttBb1TWpVcHAOM*X9kf)M%c}cEfaN!8=jnf zUh;5m`j*$5g_3#;@R~ffCYKyOhMyC_T-wRbusl? z>ZagpIHNUGitX=9!cdw~b|V>r0huV&xvZD(kf=(Ca3HMiX)j2$vhm8zVbI@qX( zXGFRr_1!i3RL|7s-%Q8i^z0_49N8V=K>h+VLQ3mCr+j0}<-&?$JW7Y2!R^HB+os}p z^9>HwZOnU|mVEgJD%m^NzO~1!Z>U7QSOcw0?xDK_{Ad`NhhNuDY_cRKBu^NGSH`bX z9b%fJy!rhp4Pkbv%6zLFh$T_)QM+@dKO!$$xH*4keYTMH2}t%CFnD@$fqn@TaYk`F zy5KE}G82Nz7T(V-s%-~`3yJVhHiXAdV=JL?$%{;=nKKR~bXfUdumkGD*rL*r_s>QP zq`gq8kbZP2{`z5@QHYE8%$UjQp_&EQ=}@LBR&r5(@nT!asU_I=uMQP4c@=HWY>

    r%DTRYRj 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 U5ag$fc*Q?x#I4#7MGTvmf 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�{_C!jTR7WwV)8k)O2JAY);YE(~_ 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 I0|^WM1Bp=K`v3p{ literal 46138 zcmeFZc{r5s|2`}fic~7ulcdPVzEdfZkZnYkWZwrd7(-IDh$8z|3E8s`6Q+b1OP0wt z!(?B_ZZL-5rS$%MKkx5z9M3<`as2*y=BUG9-1oJ3E8X6YO z8`tz{Xc&Ib(Ck}bqy;~juHcsfUzlJwOuT4lP73b+->0d6c7cZGG>zu9D~7(wvjoJ` zi|&C#aB2*n$XOZMbb9RhFM9WnTf;6g9J_z%{Fck{{X*wFp0b4o)4gbzh9WP8ar~_-|uL2gDJ{?zhSJ`>1V?4+xxbc zDOmCEZ;qaZG3>oRO-oPu`{Qd5F+6+kA2J?6(e1rIvX2)2_akR66A$mbzf8~g&&8k8 z{QnpK|87_wGK5;S*c&w7X|W$@i*V%3w2IHpBpYt zeEORt0;Dh+dv>;m$%BpdZnn`u(()($W*%QouGQk|nZODp(VR}sd~bCS_XVSC&HP_r z%zNTOEbM>Gv}9WA6dJJN5rm(?a2Dc&y&KZlYb7-K-grWR1XEx?%F@hokIPm2I9}*- zbOcw@Vcc6LcLq4@24W>L9e)Zxagr~@-L(IypNacugxzqG=lj5}6pS!buTu$De8_fn zZ@ry4gttDQgF%YWll1suk>Bc~YBL>8pi*KC_muaRX9Pxy+1xTQvl0i5wVm2rqRH;d zrYALo#O!!6;$=;zHX%YWR(ZTR+jO6=K4n{f6^wRF%*6?tv65p7+!6A8`U)G{YvVb% zJU+_hhszp&Zj3oOUgB68FUnq$5<9*<FT2T)B)6$_--qR-abBtdU&pGtMT^tfKCoF!7CYlO=K&lMI z<;lmH$wp6)_A;$k*@q6^94rxw^^=<%rS zU<#F5tz>VmT=u3w@hYrKN-~?P>}Hi-p+i|zBBIK&OA=O*+hd#CTTWM(EG@XJNPMWfu^DzX`$lx0 zt@i9Ep~VvYi5TUSfI>0$r7ZR8GeMp$?-XGbjcQr1Cs03&^8yR=E!bZtV-PI(#^y-L z&FZ!;tDWe1yGwK}7^YSXvSlwT!JcBwqZ&_pi9)46_Mr3?a-)i&^7Kg-smqOBTn3{?pASJ<#nFH4RL5MjcD zbwIX6w+nrmh-B@?R9hgu6>Wlq7Pe{O*kYxiv3o*D=zK1--9wJ28_cH+{{(YbrKs(j z>cYlPk#hc9!Pku)aslo5UF>`BETw)JidM7(kS_bH*cYXcS$KLM&IC` zO~n*PWw~=_cD}bE+>)7Yv)g1pfkG4za(uEpq<+BZNvYa{WDnYTF<6>#?*5Ou=U_r} zwg+@<4=@Y=0TZZR3?bq$*h)%t8u?J4(WEHJ!9lZ_r3*V9^X0lCFyrW;pVx~T8wz*7tOfh2j-TW^dv6$aU9dp%E*!+~dXY?dvsNO>$h1mz)~Y&5$>Os(4%Oh73(oe5tJ zxwX;pEIsIOu%e@wBurTH-tAw%(Gw;+Iv!>A9ktW{(Rhmv9%FTiYH>H;&v>TTQX?(* z<51%w9asmW)$MkyEMIcRwhTUyV)i?ibsC7A%0C8y>7FalUgp1Mkq<4N2|EF`lzmbA z3RA5ohTf@)Zx2u*=hKou>f95Ak#BUuD=)=i>#<20VU(h1g9DQH_VP3=*skkY%8xOL zLMWxQsVe0PEgU{Qal^6rywm87wPvaO$_1oKiEoP_0o04pWnQH`{sjt!0J<@Glk%6Q;cQdEPyYFzQ)ko*&{wLqfqhjbuPs}Yr=|_ruzbhy1i||8K)S9_vBy33)ZDO zz=u8@z!P+ob1>#FzcNq@f(nG6OVgiRxcp338) z$52CqW}-1~`k)|R99*to!3)R-uTCiHgY;RpUBUPZa?9k@TinN{5PFh^t{z9r!|Q+d z`)#dSoUIT@Jw^v-Je9GL$HSSDh9ES)1##N=x-y zHtAE)^dvHwoY4I;J+8WaGojP#T%0DV?!MxiLZgEbD*@(2HxoO^@|$;A(engRESw3y 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 ng6hCae<{1)4G5&2u+lP+UGiY{@H|;(3!

    LiveKit Ecosystem
    Realtime SDKsReact Components · Browser · Swift Components · iOS/macOS/visionOS · Android · Flutter · React Native · Rust · Node.js · Python · Unity (web) · Unity (beta)
    Server APIsNode.js · Golang · Ruby · Java/Kotlin · Python · Rust · PHP (community)
    Agents FrameworksPython · Playground
    LiveKit SDKsBrowser · iOS/macOS/visionOS · Android · Flutter · React Native · Rust · Node.js · Python · Unity · Unity (WebGL)
    Server APIsNode.js · Golang · Ruby · Java/Kotlin · Python · Rust · PHP (community) · .NET (community)
    UI ComponentsReact · Android Compose · SwiftUI
    Agents FrameworksPython · Node.js · Playground
    ServicesLiveKit server · Egress · Ingress · SIP
    ResourcesDocs · Example apps · Cloud · Self-hosting · CLI
    - + - + From e25d238c04312cdc1fdfe4b2269fce6a84199e39 Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Thu, 31 Jul 2025 16:19:34 +0100 Subject: [PATCH 228/274] TLS feature for all examples (#684) --- examples/Cargo.lock | 1838 +++++++++++++++++-------- examples/basic_text_stream/Cargo.lock | 545 +++++++- examples/basic_text_stream/Cargo.toml | 2 +- examples/play_from_disk/Cargo.lock | 653 ++++++++- examples/play_from_disk/Cargo.toml | 2 +- examples/save_to_disk/Cargo.toml | 2 +- 6 files changed, 2381 insertions(+), 661 deletions(-) diff --git a/examples/Cargo.lock b/examples/Cargo.lock index 95cb4972e..40cca5727 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -20,9 +20,9 @@ checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" [[package]] name = "accesskit" -version = "0.11.2" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76eb1adf08c5bcaa8490b9851fd53cca27fa9880076f178ea9d29f05196728a8" +checksum = "d3d3b8f9bae46a948369bc4a03e815d4ed6d616bd00de4051133a5019dc31c5a" dependencies = [ "enumn", "serde", @@ -43,6 +43,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "aes" version = "0.8.3" @@ -56,15 +62,16 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.6" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", + "getrandom 0.3.2", "once_cell", "serde", "version_check", - "zerocopy 0.7.31", + "zerocopy", ] [[package]] @@ -76,28 +83,25 @@ dependencies = [ "memchr", ] -[[package]] -name = "allocator-api2" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" - [[package]] name = "android-activity" -version = "0.4.3" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64529721f27c2314ced0890ce45e469574a73e5e6fdd6e9da1860eb29285f5e0" +checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" dependencies = [ "android-properties", - "bitflags 1.3.2", + "bitflags 2.9.1", "cc", + "cesu8", + "jni", "jni-sys", "libc", "log", "ndk", "ndk-context", - "ndk-sys", - "num_enum 0.6.1", + "ndk-sys 0.6.0+11769913", + "num_enum", + "thiserror 1.0.51", ] [[package]] @@ -161,14 +165,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aafb29b107435aa276664c1db8954ac27a6e105cdad3c88287a199eb0e313c08" dependencies = [ "clipboard-win", + "core-graphics 0.22.3", + "image 0.24.7", "log", "objc", "objc-foundation", "objc_id", "parking_lot", - "thiserror", + "thiserror 1.0.51", "winapi", - "x11rb", + "x11rb 0.12.0", ] [[package]] @@ -179,17 +185,23 @@ checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" [[package]] name = "arrayvec" -version = "0.7.4" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" [[package]] name = "ash" -version = "0.37.3+1.3.251" +version = "0.38.0+1.3.281" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a" +checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" dependencies = [ - "libloading 0.7.4", + "libloading", ] [[package]] @@ -278,7 +290,7 @@ dependencies = [ "futures-lite 2.2.0", "parking", "polling 3.4.0", - "rustix 0.38.28", + "rustix 0.38.44", "slab", "tracing", "windows-sys 0.52.0", @@ -312,7 +324,7 @@ checksum = "9343dc5acf07e79ff82d0c37899f079db3534d99f189a1837c8e549c99405bec" dependencies = [ "futures-util", "native-tls", - "thiserror", + "thiserror 1.0.51", "url", ] @@ -380,12 +392,6 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" -[[package]] -name = "atomic_refcell" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41e67cd8309bbd06cd603a9e693a784ac2e5d1e955f11286e355089fcab3047c" - [[package]] name = "autocfg" version = "1.1.0" @@ -447,7 +453,7 @@ dependencies = [ "cc", "cfg-if", "libc", - "miniz_oxide", + "miniz_oxide 0.7.1", "object", "rustc-demangle", ] @@ -477,18 +483,18 @@ dependencies = [ [[package]] name = "bit-set" -version = "0.5.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" -version = "0.6.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bit_field" @@ -504,9 +510,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.1" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" dependencies = [ "serde", ] @@ -526,23 +532,13 @@ dependencies = [ "generic-array", ] -[[package]] -name = "block-sys" -version = "0.1.0-beta.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa55741ee90902547802152aaf3f8e5248aab7e21468089560d4c8840561146" -dependencies = [ - "objc-sys", -] - [[package]] name = "block2" -version = "0.2.0-alpha.6" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dd9e63c1744f755c2f60332b88de39d341e5e86239014ad839bd71c106dec42" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" dependencies = [ - "block-sys", - "objc2-encode", + "objc2 0.5.2", ] [[package]] @@ -579,9 +575,9 @@ checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "bytemuck" -version = "1.14.0" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" +checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" dependencies = [ "bytemuck_derive", ] @@ -603,6 +599,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "bytes" version = "1.10.1" @@ -632,16 +634,28 @@ dependencies = [ [[package]] name = "calloop" -version = "0.10.6" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e0d00eb1ea24371a97d2da6201c6747a633dc6dc1988ef503403b4c59504a8" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.9.1", "log", - "nix 0.25.1", - "slotmap", - "thiserror", - "vec_map", + "polling 3.4.0", + "rustix 0.38.44", + "slab", + "thiserror 1.0.51", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" +dependencies = [ + "calloop", + "rustix 0.38.44", + "wayland-backend", + "wayland-client", ] [[package]] @@ -668,9 +682,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cfg_aliases" -version = "0.1.1" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" @@ -683,7 +697,7 @@ dependencies = [ "js-sys", "num-traits", "wasm-bindgen", - "windows-targets 0.52.0", + "windows-targets 0.52.6", ] [[package]] @@ -708,41 +722,22 @@ dependencies = [ ] [[package]] -name = "cocoa" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a" -dependencies = [ - "bitflags 1.3.2", - "block", - "cocoa-foundation", - "core-foundation", - "core-graphics", - "foreign-types", - "libc", - "objc", -] - -[[package]] -name = "cocoa-foundation" -version = "0.1.2" +name = "codespan-reporting" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" dependencies = [ - "bitflags 1.3.2", - "block", - "core-foundation", - "core-graphics-types", - "libc", - "objc", + "termcolor", + "unicode-width", ] [[package]] name = "codespan-reporting" -version = "0.11.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81" dependencies = [ + "serde", "termcolor", "unicode-width", ] @@ -753,12 +748,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" -[[package]] -name = "com-rs" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf43edc576402991846b093a7ca18a3477e0ef9c588cde84964b5d3e43016642" - [[package]] name = "combine" version = "4.6.6" @@ -831,6 +820,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.6" @@ -844,9 +843,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" dependencies = [ "bitflags 1.3.2", - "core-foundation", + "core-foundation 0.9.4", + "core-graphics-types", + "foreign-types 0.3.2", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", "core-graphics-types", - "foreign-types", + "foreign-types 0.5.0", "libc", ] @@ -857,7 +869,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" dependencies = [ "bitflags 1.3.2", - "core-foundation", + "core-foundation 0.9.4", "libc", ] @@ -937,6 +949,12 @@ dependencies = [ "typenum", ] +[[package]] +name = "cursor-icon" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" + [[package]] name = "cxx" version = "1.0.111" @@ -956,7 +974,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51bc81d2664db24cf1d35405f66e18a85cffd4d49ab930c71a5c6342a410f38c" dependencies = [ "cc", - "codespan-reporting", + "codespan-reporting 0.11.1", "once_cell", "proc-macro2", "quote", @@ -981,17 +999,6 @@ dependencies = [ "syn 2.0.100", ] -[[package]] -name = "d3d12" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8f0de2f5a8e7bd4a9eec0e3c781992a4ce1724f68aec7d7a3715344de8b39da" -dependencies = [ - "bitflags 1.3.2", - "libloading 0.7.4", - "winapi", -] - [[package]] name = "data-encoding" version = "2.5.0" @@ -1018,27 +1025,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "directories-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" -dependencies = [ - "cfg-if", - "dirs-sys-next", -] - -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - [[package]] name = "dispatch" version = "0.2.0" @@ -1051,7 +1037,16 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading 0.7.4", + "libloading", +] + +[[package]] +name = "document-features" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" +dependencies = [ + "litrs", ] [[package]] @@ -1060,89 +1055,114 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" + [[package]] name = "ecolor" -version = "0.22.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e479a7fa3f23d4e794f8b2f8b3568dd4e47886ad1b12c9c095e141cb591eb63" +checksum = "bc4feb366740ded31a004a0e4452fbf84e80ef432ecf8314c485210229672fd1" dependencies = [ "bytemuck", + "emath", "serde", ] [[package]] name = "eframe" -version = "0.22.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4596583a2c680c55b6feaa748f74890c4f9cb9c7cb69d6117110444cb65b2f" +checksum = "d0dfe0859f3fb1bc6424c57d41e10e9093fe938f426b691e42272c2f336d915c" dependencies = [ + "ahash", "bytemuck", - "cocoa", - "directories-next", + "document-features", "egui", "egui-wgpu", "egui-winit", - "image", + "home", + "image 0.25.6", "js-sys", "log", - "objc", + "objc2 0.5.2", + "objc2-app-kit", + "objc2-foundation 0.2.2", + "parking_lot", "percent-encoding", "pollster", + "profiling", "raw-window-handle", "ron", "serde", - "thiserror", + "static_assertions", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "wgpu", + "web-time", + "wgpu 24.0.5", "winapi", + "windows-sys 0.59.0", "winit", ] [[package]] name = "egui" -version = "0.22.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3aef8ec3ae1b772f340170c65bf27d5b8c28f543a0116c844d2ac08d01123e7" +checksum = "25dd34cec49ab55d85ebf70139cb1ccd29c977ef6b6ba4fe85489d6877ee9ef3" dependencies = [ "accesskit", "ahash", + "bitflags 2.9.1", + "emath", "epaint", "log", "nohash-hasher", + "profiling", "ron", "serde", ] [[package]] name = "egui-wgpu" -version = "0.22.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33caaedd8283779c787298af23d8754a7e88421ff32e89ad0040c855fc0b0224" +checksum = "d319dfef570f699b6e9114e235e862a2ddcf75f0d1a061de9e1328d92146d820" dependencies = [ + "ahash", "bytemuck", + "document-features", + "egui", "epaint", "log", - "thiserror", + "profiling", + "thiserror 1.0.51", "type-map", - "wgpu", + "web-time", + "wgpu 24.0.5", "winit", ] [[package]] name = "egui-winit" -version = "0.22.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a49155fd4a0a4fb21224407a91de0030847972ef90fc64edb63621caea61cb2" +checksum = "7d9dfbb78fe4eb9c3a39ad528b90ee5915c252e77bbab9d4ebc576541ab67e13" dependencies = [ + "ahash", "arboard", + "bytemuck", "egui", - "instant", "log", + "profiling", "raw-window-handle", "serde", "smithay-clipboard", + "web-time", "webbrowser", "winit", ] @@ -1155,9 +1175,9 @@ checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "emath" -version = "0.22.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3857d743a6e0741cdd60b622a74c7a36ea75f5f8f11b793b41d905d2c9721a4b" +checksum = "9e4cadcff7a5353ba72b7fea76bf2122b5ebdbc68e8155aa56dfdea90083fe1b" dependencies = [ "bytemuck", "serde", @@ -1198,22 +1218,29 @@ dependencies = [ [[package]] name = "epaint" -version = "0.22.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09333964d4d57f40a85338ba3ca5ed4716070ab184dcfed966b35491c5c64f3b" +checksum = "41fcc0f5a7c613afd2dee5e4b30c3e6acafb8ad6f0edb06068811f708a67c562" dependencies = [ "ab_glyph", "ahash", - "atomic_refcell", "bytemuck", "ecolor", "emath", + "epaint_default_fonts", "log", "nohash-hasher", "parking_lot", + "profiling", "serde", ] +[[package]] +name = "epaint_default_fonts" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7e7a64c02cf7a5b51e745a9e45f60660a286f151c238b9d397b3e923f5082f" + [[package]] name = "equivalent" version = "1.0.1" @@ -1222,9 +1249,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.8" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", "windows-sys 0.52.0", @@ -1290,15 +1317,14 @@ dependencies = [ [[package]] name = "exr" -version = "1.71.0" +version = "1.73.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "832a761f35ab3e6664babfbdc6cef35a4860e816ec3916dcfd0882954e98a8a8" +checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0" dependencies = [ "bit_field", - "flume", "half", "lebe", - "miniz_oxide", + "miniz_oxide 0.8.9", "rayon-core", "smallvec", "zune-inflate", @@ -1321,9 +1347,9 @@ checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "fdeflate" -version = "0.3.1" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64d6dafc854908ff5da46ff3f8f473c6984119a2876a383a860246dd7841a868" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" dependencies = [ "simd-adler32", ] @@ -1341,16 +1367,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" dependencies = [ "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "flume" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" -dependencies = [ - "spin", + "miniz_oxide 0.7.1", ] [[package]] @@ -1359,13 +1376,40 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "foreign-types" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ - "foreign-types-shared", + "foreign-types-shared 0.1.1", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared 0.3.1", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", ] [[package]] @@ -1374,6 +1418,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -1530,6 +1580,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "gethostname" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" +dependencies = [ + "libc", + "windows-targets 0.48.5", +] + [[package]] name = "getrandom" version = "0.2.11" @@ -1571,6 +1631,17 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + [[package]] name = "glob" version = "0.3.1" @@ -1591,9 +1662,9 @@ dependencies = [ [[package]] name = "glow" -version = "0.12.3" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca0fe580e4b60a8ab24a868bc08e2f03cbcb20d3d676601fa909386713333728" +checksum = "c5e5ea60d70410161c8bf5da3fdfeaa1c72ed2c15f8bbb9d19fe3a4fad085f08" dependencies = [ "js-sys", "slotmap", @@ -1601,56 +1672,64 @@ dependencies = [ "web-sys", ] +[[package]] +name = "glutin_wgl_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" +dependencies = [ + "gl_generator", +] + [[package]] name = "gpu-alloc" -version = "0.5.4" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22beaafc29b38204457ea030f6fb7a84c9e4dd1b86e311ba0542533453d87f62" +checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.9.1", "gpu-alloc-types", ] [[package]] name = "gpu-alloc-types" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54804d0d6bc9d7f26db4eaec1ad10def69b599315f487d32c334a80d1efe67a5" +checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.9.1", ] [[package]] name = "gpu-allocator" -version = "0.22.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce95f9e2e11c2c6fadfce42b5af60005db06576f231f5c92550fdded43c423e8" +checksum = "c151a2a5ef800297b4e79efa4f4bec035c5f51d5ae587287c9b952bdf734cacd" dependencies = [ - "backtrace", "log", - "thiserror", - "winapi", + "presser", + "thiserror 1.0.51", "windows", ] [[package]] name = "gpu-descriptor" -version = "0.2.4" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c" +checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.9.1", "gpu-descriptor-types", - "hashbrown 0.14.3", + "hashbrown 0.15.4", ] [[package]] name = "gpu-descriptor-types" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bf0b36e6f090b7e1d8a4b49c0cb81c1f8376f72198c65dd3ad9ff3556b8b78c" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.9.1", ] [[package]] @@ -1665,7 +1744,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.11", - "indexmap 2.1.0", + "indexmap 2.10.0", "slab", "tokio", "tokio-util", @@ -1674,11 +1753,13 @@ dependencies = [ [[package]] name = "half" -version = "2.2.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" dependencies = [ + "cfg-if", "crunchy", + "num-traits", ] [[package]] @@ -1689,27 +1770,11 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" -dependencies = [ - "ahash", - "allocator-api2", -] - -[[package]] -name = "hassle-rs" -version = "0.10.0" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1397650ee315e8891a0df210707f0fc61771b0cc518c3023896064c5407cb3b0" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" dependencies = [ - "bitflags 1.3.2", - "com-rs", - "libc", - "libloading 0.7.4", - "thiserror", - "widestring", - "winapi", + "foldhash", ] [[package]] @@ -1731,6 +1796,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.3.3" @@ -1886,7 +1957,7 @@ dependencies = [ "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows-core", + "windows-core 0.52.0", ] [[package]] @@ -1927,6 +1998,18 @@ dependencies = [ "tiff", ] +[[package]] +name = "image" +version = "0.25.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a" +dependencies = [ + "bytemuck", + "byteorder-lite", + "num-traits", + "png", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -1939,12 +2022,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.1.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", - "hashbrown 0.14.3", + "hashbrown 0.15.4", ] [[package]] @@ -1963,9 +2046,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", ] [[package]] @@ -1992,7 +2072,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", - "rustix 0.38.28", + "rustix 0.38.44", "windows-sys 0.48.0", ] @@ -2031,7 +2111,7 @@ dependencies = [ "combine", "jni-sys", "log", - "thiserror", + "thiserror 1.0.51", "walkdir", "windows-sys 0.45.0", ] @@ -2062,10 +2142,11 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.66" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -2084,17 +2165,23 @@ dependencies = [ [[package]] name = "khronos-egl" -version = "4.1.0" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c2352bd1d0bceb871cb9d40f24360c8133c11d7486b68b5381c1dd1a32015e3" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" dependencies = [ "libc", - "libloading 0.7.4", + "libloading", "pkg-config", ] [[package]] -name = "kv-log-macro" +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "kv-log-macro" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" @@ -2120,16 +2207,6 @@ version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" -[[package]] -name = "libloading" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" -dependencies = [ - "cfg-if", - "winapi", -] - [[package]] name = "libloading" version = "0.8.6" @@ -2137,19 +2214,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.52.0", + "windows-targets 0.48.5", ] [[package]] -name = "libredox" -version = "0.0.1" +name = "libm" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" -dependencies = [ - "bitflags 2.4.1", - "libc", - "redox_syscall 0.4.1", -] +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" @@ -2157,14 +2229,14 @@ version = "0.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.9.1", "libc", - "redox_syscall 0.4.1", + "redox_syscall", ] [[package]] name = "libwebrtc" -version = "0.3.10" +version = "0.3.12" dependencies = [ "cxx", "jni", @@ -2176,7 +2248,7 @@ dependencies = [ "parking_lot", "serde", "serde_json", - "thiserror", + "thiserror 1.0.51", "tokio", "wasm-bindgen", "wasm-bindgen-futures", @@ -2201,20 +2273,26 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" -version = "0.4.12" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "litrs" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" [[package]] name = "livekit" -version = "0.7.9" +version = "0.7.15" dependencies = [ "bmrng", "bytes", "chrono", "futures-util", "lazy_static", - "libloading 0.8.6", + "libloading", "libwebrtc", "livekit-api", "livekit-protocol", @@ -2225,13 +2303,13 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror", + "thiserror 1.0.51", "tokio", ] [[package]] name = "livekit-api" -version = "0.4.2" +version = "0.4.4" dependencies = [ "async-tungstenite", "base64", @@ -2251,7 +2329,7 @@ dependencies = [ "serde", "serde_json", "sha2", - "thiserror", + "thiserror 1.0.51", "tokio", "tokio-rustls", "tokio-tungstenite", @@ -2260,7 +2338,7 @@ dependencies = [ [[package]] name = "livekit-protocol" -version = "0.3.9" +version = "0.4.0" dependencies = [ "futures-util", "livekit-runtime", @@ -2270,7 +2348,7 @@ dependencies = [ "prost 0.12.3", "prost-types 0.12.3", "serde", - "thiserror", + "thiserror 1.0.51", "tokio", ] @@ -2333,22 +2411,13 @@ checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "memmap2" -version = "0.5.10" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +checksum = "483758ad303d734cec05e5c12b41d7e93e6a6390c5e9dae6bdeb7c1259012d28" dependencies = [ "libc", ] -[[package]] -name = "memoffset" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" -dependencies = [ - "autocfg", -] - [[package]] name = "memoffset" version = "0.7.1" @@ -2369,16 +2438,17 @@ dependencies = [ [[package]] name = "metal" -version = "0.24.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de11355d1f6781482d027a3b4d4de7825dcedb197bf573e0596d00008402d060" +checksum = "f569fb946490b5743ad69813cb19629130ce9374034abe31614a36402d18f99e" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.9.1", "block", "core-graphics-types", - "foreign-types", + "foreign-types 0.5.0", "log", "objc", + "paste", ] [[package]] @@ -2400,6 +2470,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", "simd-adler32", ] @@ -2410,7 +2489,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", - "log", "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.48.0", ] @@ -2436,24 +2514,51 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "naga" -version = "0.12.3" +version = "24.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbcc2e0513220fd2b598e6068608d4462db20322c0e77e47f6f488dfcfc279cb" +checksum = "e380993072e52eef724eddfcde0ed013b0c023c3f0417336ed041aa9f076994e" dependencies = [ + "arrayvec", "bit-set", - "bitflags 1.3.2", - "codespan-reporting", + "bitflags 2.9.1", + "cfg_aliases", + "codespan-reporting 0.11.1", "hexf-parse", - "indexmap 1.9.3", + "indexmap 2.10.0", "log", - "num-traits", "rustc-hash", "spirv", + "strum", "termcolor", - "thiserror", + "thiserror 2.0.12", "unicode-xid", ] +[[package]] +name = "naga" +version = "25.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b977c445f26e49757f9aca3631c3b8b836942cb278d69a92e7b80d3b24da632" +dependencies = [ + "arrayvec", + "bit-set", + "bitflags 2.9.1", + "cfg_aliases", + "codespan-reporting 0.12.0", + "half", + "hashbrown 0.15.4", + "hexf-parse", + "indexmap 2.10.0", + "log", + "num-traits", + "once_cell", + "rustc-hash", + "spirv", + "strum", + "thiserror 2.0.12", + "unicode-ident", +] + [[package]] name = "native-tls" version = "0.2.11" @@ -2474,16 +2579,17 @@ dependencies = [ [[package]] name = "ndk" -version = "0.7.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "451422b7e4718271c8b5b3aadf5adedba43dc76312454b387e98fae0fc951aa0" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.9.1", "jni-sys", - "ndk-sys", - "num_enum 0.5.11", + "log", + "ndk-sys 0.6.0+11769913", + "num_enum", "raw-window-handle", - "thiserror", + "thiserror 1.0.51", ] [[package]] @@ -2494,36 +2600,20 @@ checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" [[package]] name = "ndk-sys" -version = "0.4.1+23.1.7779620" +version = "0.5.0+25.2.9519653" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf2aae958bd232cac5069850591667ad422d263686d75b52a065f9badeee5a3" +checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" dependencies = [ "jni-sys", ] [[package]] -name = "nix" -version = "0.24.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" -dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "libc", - "memoffset 0.6.5", -] - -[[package]] -name = "nix" -version = "0.25.1" +name = "ndk-sys" +version = "0.6.0+11769913" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" dependencies = [ - "autocfg", - "bitflags 1.3.2", - "cfg-if", - "libc", - "memoffset 0.6.5", + "jni-sys", ] [[package]] @@ -2582,6 +2672,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -2596,39 +2687,19 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" -dependencies = [ - "num_enum_derive 0.5.11", -] - -[[package]] -name = "num_enum" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a015b430d3c108a207fd776d2e2196aaf8b1cf8cf93253e3a097ff3085076a1" -dependencies = [ - "num_enum_derive 0.6.1", -] - -[[package]] -name = "num_enum_derive" -version = "0.5.11" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 1.0.109", + "num_enum_derive", + "rustversion", ] [[package]] name = "num_enum_derive" -version = "0.6.1" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6" +checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -2643,7 +2714,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" dependencies = [ "malloc_buf", - "objc_exception", ] [[package]] @@ -2659,37 +2729,224 @@ dependencies = [ [[package]] name = "objc-sys" -version = "0.2.0-beta.2" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b9834c1e95694a05a828b59f55fa2afec6288359cda67146126b3f90a55d7" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" [[package]] name = "objc2" -version = "0.3.0-beta.3.patch-leaks.3" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e01640f9f2cb1220bbe80325e179e532cb3379ebcd1bf2279d703c19fe3a468" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" dependencies = [ - "block2", "objc-sys", "objc2-encode", ] +[[package]] +name = "objc2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88c6597e14493ab2e44ce58f2fdecf095a51f12ca57bec060a11c57332520551" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.9.1", + "block2", + "libc", + "objc2 0.5.2", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation 0.2.2", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +dependencies = [ + "bitflags 2.9.1", + "block2", + "objc2 0.5.2", + "objc2-core-location", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-contacts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" +dependencies = [ + "block2", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.9.1", + "block2", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal", +] + +[[package]] +name = "objc2-core-location" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +dependencies = [ + "block2", + "objc2 0.5.2", + "objc2-contacts", + "objc2-foundation 0.2.2", +] + [[package]] name = "objc2-encode" -version = "2.0.0-pre.2" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abfcac41015b00a120608fdaa6938c44cb983fee294351cc4bac7638b4e50512" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "objc-sys", + "bitflags 2.9.1", + "block2", + "dispatch", + "libc", + "objc2 0.5.2", ] [[package]] -name = "objc_exception" -version = "0.1.2" +name = "objc2-foundation" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" dependencies = [ - "cc", + "bitflags 2.9.1", + "objc2 0.6.1", +] + +[[package]] +name = "objc2-link-presentation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +dependencies = [ + "block2", + "objc2 0.5.2", + "objc2-app-kit", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.9.1", + "block2", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.9.1", + "block2", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal", +] + +[[package]] +name = "objc2-symbols" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" +dependencies = [ + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +dependencies = [ + "bitflags 2.9.1", + "block2", + "objc2 0.5.2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-image", + "objc2-core-location", + "objc2-foundation 0.2.2", + "objc2-link-presentation", + "objc2-quartz-core", + "objc2-symbols", + "objc2-uniform-type-identifiers", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-uniform-type-identifiers" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" +dependencies = [ + "block2", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" +dependencies = [ + "bitflags 2.9.1", + "block2", + "objc2 0.5.2", + "objc2-core-location", + "objc2-foundation 0.2.2", ] [[package]] @@ -2712,9 +2969,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "openssl" @@ -2722,9 +2979,9 @@ version = "0.10.61" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b8419dc8cc6d866deb801274bba2e6f8f6108c1bb7fcc10ee5ab864931dbb45" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.9.1", "cfg-if", - "foreign-types", + "foreign-types 0.3.2", "libc", "once_cell", "openssl-macros", @@ -2766,7 +3023,16 @@ version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52f0d54bde9774d3a51dcf281a5def240c71996bc6ca05d2c847ec8b2b216166" dependencies = [ - "libredox 0.0.2", + "libredox", +] + +[[package]] +name = "ordered-float" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" +dependencies = [ + "num-traits", ] [[package]] @@ -2804,7 +3070,7 @@ dependencies = [ "cfg-if", "libc", "petgraph", - "redox_syscall 0.4.1", + "redox_syscall", "smallvec", "thread-id", "windows-targets 0.48.5", @@ -2821,6 +3087,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pbjson" version = "0.6.0" @@ -2837,7 +3109,7 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2580e33f2292d34be285c5bc3dba5259542b083cfad6037b6d70345f24dcb735" dependencies = [ - "heck", + "heck 0.4.1", "itertools 0.11.0", "prost 0.12.3", "prost-types 0.12.3", @@ -2883,7 +3155,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset", - "indexmap 2.1.0", + "indexmap 2.10.0", ] [[package]] @@ -2937,15 +3209,15 @@ checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "png" -version = "0.17.10" +version = "0.17.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd75bf2d8dd3702b9707cdbc56a5b9ef42cec752eb8b3bafc01234558442aa64" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" dependencies = [ "bitflags 1.3.2", "crc32fast", "fdeflate", "flate2", - "miniz_oxide", + "miniz_oxide 0.8.9", ] [[package]] @@ -2973,16 +3245,22 @@ dependencies = [ "cfg-if", "concurrent-queue", "pin-project-lite", - "rustix 0.38.28", + "rustix 0.38.44", "tracing", "windows-sys 0.52.0", ] [[package]] name = "pollster" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" +checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" + +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "powerfmt" @@ -2996,6 +3274,12 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + [[package]] name = "prettyplease" version = "0.2.15" @@ -3027,9 +3311,9 @@ dependencies = [ [[package]] name = "profiling" -version = "1.0.12" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1de09527cd2ea2c2d59fb6c2f8c1ab8c71709ed9d1b6d60b0e1c9fbb6fdcb33c" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" [[package]] name = "prost" @@ -3058,7 +3342,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c55e02e35260070b6f716a2423c2ff1c3bb1642ddca6f99e1f26d06268a0e2d2" dependencies = [ "bytes", - "heck", + "heck 0.4.1", "itertools 0.11.0", "log", "multimap", @@ -3126,6 +3410,15 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "quick-xml" +version = "0.37.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" +dependencies = [ + "memchr", +] + [[package]] name = "quote" version = "1.0.40" @@ -3160,7 +3453,7 @@ checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", - "zerocopy 0.8.24", + "zerocopy", ] [[package]] @@ -3209,9 +3502,9 @@ checksum = "9c8a99fddc9f0ba0a85884b8d14e3592853e787d581ca1816c91349b10e4eeab" [[package]] name = "raw-window-handle" -version = "0.5.2" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" [[package]] name = "rayon" @@ -3233,15 +3526,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "redox_syscall" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "redox_syscall" version = "0.4.1" @@ -3251,17 +3535,6 @@ dependencies = [ "bitflags 1.3.2", ] -[[package]] -name = "redox_users" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" -dependencies = [ - "getrandom 0.2.11", - "libredox 0.0.1", - "thiserror", -] - [[package]] name = "regex" version = "1.10.2" @@ -3308,9 +3581,9 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "renderdoc-sys" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216080ab382b992234dda86873c18d4c48358f5cfcb70fd693d7f6f2131b628b" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" [[package]] name = "reqwest" @@ -3377,7 +3650,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ "base64", - "bitflags 2.4.1", + "bitflags 2.9.1", "serde", "serde_derive", ] @@ -3423,14 +3696,14 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.28" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.9.1", "errno", "libc", - "linux-raw-sys 0.4.12", + "linux-raw-sys 0.4.15", "windows-sys 0.52.0", ] @@ -3548,9 +3821,9 @@ dependencies = [ [[package]] name = "sctk-adwaita" -version = "0.5.4" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda4e97be1fd174ccc2aae81c8b694e803fa99b34e8fd0f057a9d70698e3ed09" +checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" dependencies = [ "ab_glyph", "log", @@ -3566,7 +3839,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" dependencies = [ "bitflags 1.3.2", - "core-foundation", + "core-foundation 0.9.4", "core-foundation-sys", "libc", "security-framework-sys", @@ -3703,31 +3976,47 @@ checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" [[package]] name = "smithay-client-toolkit" -version = "0.16.1" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870427e30b8f2cbe64bf43ec4b86e88fe39b0a84b3f15efd9c9c2d020bc86eb9" +checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.9.1", "calloop", - "dlib", - "lazy_static", + "calloop-wayland-source", + "cursor-icon", + "libc", "log", "memmap2", - "nix 0.24.3", - "pkg-config", + "rustix 0.38.44", + "thiserror 1.0.51", + "wayland-backend", "wayland-client", + "wayland-csd-frame", "wayland-cursor", "wayland-protocols", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", ] [[package]] name = "smithay-clipboard" -version = "0.6.6" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a345c870a1fae0b1b779085e81b51e614767c239e93503588e54c5b17f4b0e8" +checksum = "cc8216eec463674a0e90f29e0ae41a4db573ec5b56b1c6c1c71615d249b6d846" dependencies = [ + "libc", "smithay-client-toolkit", - "wayland-client", + "wayland-backend", +] + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", ] [[package]] @@ -3755,18 +4044,14 @@ name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] [[package]] name = "spirv" -version = "0.2.0+1.5.4" +version = "0.3.0+sdk-1.3.268.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "246bfa38fe3db3f1dfc8ca5a2cdeb7348c78be2112740cc0ec8ef18b6d94f830" +checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" dependencies = [ - "bitflags 1.3.2", - "num-traits", + "bitflags 2.9.1", ] [[package]] @@ -3787,6 +4072,28 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.100", +] + [[package]] name = "subtle" version = "2.5.0" @@ -3828,7 +4135,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ "bitflags 1.3.2", - "core-foundation", + "core-foundation 0.9.4", "system-configuration-sys", ] @@ -3850,16 +4157,16 @@ checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" dependencies = [ "cfg-if", "fastrand 2.0.1", - "redox_syscall 0.4.1", - "rustix 0.38.28", + "redox_syscall", + "rustix 0.38.44", "windows-sys 0.48.0", ] [[package]] name = "termcolor" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ "winapi-util", ] @@ -3870,7 +4177,16 @@ version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.51", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", ] [[package]] @@ -3884,6 +4200,17 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "thread-id" version = "4.2.1" @@ -3935,23 +4262,23 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "tiny-skia" -version = "0.8.4" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8493a203431061e901613751931f047d1971337153f96d0e5e363d6dbf6a67" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" dependencies = [ "arrayref", "arrayvec", "bytemuck", "cfg-if", - "png", + "log", "tiny-skia-path", ] [[package]] name = "tiny-skia-path" -version = "0.8.4" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adbfb5d3f3dd57a0e11d12f4f13d4ebbbc1b5c15b7ab0a156d030b21da5f677c" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" dependencies = [ "arrayref", "bytemuck", @@ -4089,7 +4416,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.1.0", + "indexmap 2.10.0", "toml_datetime", "winnow", ] @@ -4230,7 +4557,7 @@ dependencies = [ "rand 0.8.5", "rustls", "sha1", - "thiserror", + "thiserror 1.0.51", "url", "utf-8", ] @@ -4250,7 +4577,7 @@ dependencies = [ "native-tls", "rand 0.8.5", "sha1", - "thiserror", + "thiserror 1.0.51", "url", "utf-8", ] @@ -4291,6 +4618,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + [[package]] name = "unicode-width" version = "0.1.11" @@ -4299,9 +4632,9 @@ checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "unicode-xid" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "untrusted" @@ -4344,12 +4677,6 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" -[[package]] -name = "vec_map" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" - [[package]] name = "version_check" version = "0.9.4" @@ -4398,23 +4725,24 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.89" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", + "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.89" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", "syn 2.0.100", @@ -4423,21 +4751,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.39" +version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.89" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4445,9 +4774,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.89" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", @@ -4458,88 +4787,137 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.89" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] -name = "wayland-client" -version = "0.29.5" +name = "wayland-backend" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715" +checksum = "fe770181423e5fc79d3e2a7f4410b7799d5aab1de4372853de3c6aa13ca24121" dependencies = [ - "bitflags 1.3.2", + "cc", "downcast-rs", - "libc", - "nix 0.24.3", + "rustix 0.38.44", "scoped-tls", - "wayland-commons", - "wayland-scanner", + "smallvec", "wayland-sys", ] [[package]] -name = "wayland-commons" -version = "0.29.5" +name = "wayland-client" +version = "0.31.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902" +checksum = "978fa7c67b0847dbd6a9f350ca2569174974cd4082737054dbb7fbb79d7d9a61" dependencies = [ - "nix 0.24.3", - "once_cell", - "smallvec", - "wayland-sys", + "bitflags 2.9.1", + "rustix 0.38.44", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-csd-frame" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" +dependencies = [ + "bitflags 2.9.1", + "cursor-icon", + "wayland-backend", ] [[package]] name = "wayland-cursor" -version = "0.29.5" +version = "0.31.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661" +checksum = "a65317158dec28d00416cb16705934070aef4f8393353d41126c54264ae0f182" dependencies = [ - "nix 0.24.3", + "rustix 0.38.44", "wayland-client", "xcursor", ] [[package]] name = "wayland-protocols" -version = "0.29.5" +version = "0.32.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6" +checksum = "779075454e1e9a521794fed15886323ea0feda3f8b0fc1390f5398141310422a" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.9.1", + "wayland-backend", "wayland-client", - "wayland-commons", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-plasma" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fd38cdad69b56ace413c6bcc1fbf5acc5e2ef4af9d5f8f1f9570c0c83eae175" +dependencies = [ + "bitflags 2.9.1", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cb6cdc73399c0e06504c437fe3cf886f25568dd5454473d565085b36d6a8bbf" +dependencies = [ + "bitflags 2.9.1", + "wayland-backend", + "wayland-client", + "wayland-protocols", "wayland-scanner", ] [[package]] name = "wayland-scanner" -version = "0.29.5" +version = "0.31.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4303d8fa22ab852f789e75a967f0a2cdc430a607751c0499bada3e451cbd53" +checksum = "896fdafd5d28145fce7958917d69f2fd44469b1d4e861cb5961bcbeebc6d1484" dependencies = [ "proc-macro2", + "quick-xml", "quote", - "xml-rs", ] [[package]] name = "wayland-sys" -version = "0.29.5" +version = "0.31.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be12ce1a3c39ec7dba25594b97b42cb3195d54953ddb9d3d95a7c3902bc6e9d4" +checksum = "dbcebb399c77d5aa9fa5db874806ee7b4eba4e73650948e8f93963f128896615" dependencies = [ "dlib", - "lazy_static", + "log", + "once_cell", "pkg-config", ] [[package]] name = "web-sys" -version = "0.3.66" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ "js-sys", "wasm-bindgen", @@ -4547,17 +4925,16 @@ dependencies = [ [[package]] name = "webbrowser" -version = "0.8.12" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b2391658b02c27719fc5a0a73d6e696285138e8b12fba9d4baa70451023c71" +checksum = "aaf4f3c0ba838e82b4e5ccc4157003fb8c324ee24c058470ffb82820becbde98" dependencies = [ - "core-foundation", - "home", + "core-foundation 0.10.1", "jni", "log", "ndk-context", - "objc", - "raw-window-handle", + "objc2 0.6.1", + "objc2-foundation 0.3.1", "url", "web-sys", ] @@ -4579,7 +4956,7 @@ checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" [[package]] name = "webrtc-sys" -version = "0.3.7" +version = "0.3.9" dependencies = [ "cc", "cxx", @@ -4591,8 +4968,9 @@ dependencies = [ [[package]] name = "webrtc-sys-build" -version = "0.3.6" +version = "0.3.7" dependencies = [ + "anyhow", "fs2", "regex", "reqwest", @@ -4609,16 +4987,46 @@ checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" [[package]] name = "wgpu" -version = "0.16.3" +version = "24.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "480c965c9306872eb6255fa55e4b4953be55a8b64d57e61d7ff840d3dcc051cd" +checksum = "6b0b3436f0729f6cdf2e6e9201f3d39dc95813fad61d826c1ed07918b4539353" dependencies = [ "arrayvec", - "cfg-if", + "bitflags 2.9.1", + "cfg_aliases", + "document-features", + "js-sys", + "log", + "naga 24.0.0", + "parking_lot", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core 24.0.5", + "wgpu-hal 24.0.4", + "wgpu-types 24.0.0", +] + +[[package]] +name = "wgpu" +version = "25.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8fb398f119472be4d80bc3647339f56eb63b2a331f6a3d16e25d8144197dd9" +dependencies = [ + "arrayvec", + "bitflags 2.9.1", + "cfg_aliases", + "document-features", + "hashbrown 0.15.4", "js-sys", "log", - "naga", + "naga 25.0.1", "parking_lot", + "portable-atomic", "profiling", "raw-window-handle", "smallvec", @@ -4626,84 +5034,206 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "wgpu-core", - "wgpu-hal", - "wgpu-types", + "wgpu-core 25.0.2", + "wgpu-hal 25.0.2", + "wgpu-types 25.0.0", +] + +[[package]] +name = "wgpu-core" +version = "24.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f0aa306497a238d169b9dc70659105b4a096859a34894544ca81719242e1499" +dependencies = [ + "arrayvec", + "bit-vec", + "bitflags 2.9.1", + "cfg_aliases", + "document-features", + "indexmap 2.10.0", + "log", + "naga 24.0.0", + "once_cell", + "parking_lot", + "profiling", + "raw-window-handle", + "rustc-hash", + "smallvec", + "thiserror 2.0.12", + "wgpu-hal 24.0.4", + "wgpu-types 24.0.0", ] [[package]] name = "wgpu-core" -version = "0.16.1" +version = "25.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f478237b4bf0d5b70a39898a66fa67ca3a007d79f2520485b8b0c3dfc46f8c2" +checksum = "f7b882196f8368511d613c6aeec80655160db6646aebddf8328879a88d54e500" dependencies = [ "arrayvec", + "bit-set", "bit-vec", - "bitflags 2.4.1", - "codespan-reporting", + "bitflags 2.9.1", + "cfg_aliases", + "document-features", + "hashbrown 0.15.4", + "indexmap 2.10.0", + "log", + "naga 25.0.1", + "once_cell", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "rustc-hash", + "smallvec", + "thiserror 2.0.12", + "wgpu-core-deps-apple", + "wgpu-core-deps-emscripten", + "wgpu-core-deps-windows-linux-android", + "wgpu-hal 25.0.2", + "wgpu-types 25.0.0", +] + +[[package]] +name = "wgpu-core-deps-apple" +version = "25.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfd488b3239b6b7b185c3b045c39ca6bf8af34467a4c5de4e0b1a564135d093d" +dependencies = [ + "wgpu-hal 25.0.2", +] + +[[package]] +name = "wgpu-core-deps-emscripten" +version = "25.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f09ad7aceb3818e52539acc679f049d3475775586f3f4e311c30165cf2c00445" +dependencies = [ + "wgpu-hal 25.0.2", +] + +[[package]] +name = "wgpu-core-deps-windows-linux-android" +version = "25.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cba5fb5f7f9c98baa7c889d444f63ace25574833df56f5b817985f641af58e46" +dependencies = [ + "wgpu-hal 25.0.2", +] + +[[package]] +name = "wgpu-hal" +version = "24.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f112f464674ca69f3533248508ee30cb84c67cf06c25ff6800685f5e0294e259" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bitflags 2.9.1", + "block", + "bytemuck", + "cfg_aliases", + "core-graphics-types", + "glow", + "glutin_wgl_sys", + "gpu-alloc", + "gpu-descriptor", + "js-sys", + "khronos-egl", + "libc", + "libloading", "log", - "naga", + "metal", + "naga 24.0.0", + "ndk-sys 0.5.0+25.2.9519653", + "objc", + "once_cell", + "ordered-float", "parking_lot", "profiling", "raw-window-handle", + "renderdoc-sys", "rustc-hash", "smallvec", - "thiserror", + "thiserror 2.0.12", + "wasm-bindgen", "web-sys", - "wgpu-hal", - "wgpu-types", + "wgpu-types 24.0.0", + "windows", ] [[package]] name = "wgpu-hal" -version = "0.16.2" +version = "25.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ecb3258078e936deee14fd4e0febe1cfe9bbb5ffef165cb60218d2ee5eb4448" +checksum = "f968767fe4d3d33747bbd1473ccd55bf0f6451f55d733b5597e67b5deab4ad17" dependencies = [ "android_system_properties", "arrayvec", "ash", "bit-set", - "bitflags 2.4.1", + "bitflags 2.9.1", "block", + "bytemuck", + "cfg-if", + "cfg_aliases", "core-graphics-types", - "d3d12", - "foreign-types", "glow", + "glutin_wgl_sys", "gpu-alloc", "gpu-allocator", "gpu-descriptor", - "hassle-rs", + "hashbrown 0.15.4", "js-sys", "khronos-egl", "libc", - "libloading 0.8.6", + "libloading", "log", "metal", - "naga", + "naga 25.0.1", + "ndk-sys 0.5.0+25.2.9519653", "objc", + "ordered-float", "parking_lot", + "portable-atomic", "profiling", "range-alloc", "raw-window-handle", "renderdoc-sys", - "rustc-hash", "smallvec", - "thiserror", + "thiserror 2.0.12", "wasm-bindgen", "web-sys", - "wgpu-types", - "winapi", + "wgpu-types 25.0.0", + "windows", + "windows-core 0.58.0", ] [[package]] name = "wgpu-types" -version = "0.16.1" +version = "24.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c153280bb108c2979eb5c7391cb18c56642dd3c072e55f52065e13e2a1252a" +checksum = "50ac044c0e76c03a0378e7786ac505d010a873665e2d51383dcff8dd227dc69c" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.9.1", "js-sys", + "log", + "web-sys", +] + +[[package]] +name = "wgpu-types" +version = "25.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aa49460c2a8ee8edba3fca54325540d904dd85b2e086ada762767e17d06e8bc" +dependencies = [ + "bitflags 2.9.1", + "bytemuck", + "js-sys", + "log", + "thiserror 2.0.12", "web-sys", ] @@ -4717,13 +5247,13 @@ dependencies = [ "egui-wgpu", "env_logger", "futures", - "image", + "image 0.24.7", "livekit", "log", "parking_lot", "serde", "tokio", - "wgpu", + "wgpu 25.0.2", "winit", ] @@ -4736,15 +5266,9 @@ dependencies = [ "either", "home", "once_cell", - "rustix 0.38.28", + "rustix 0.38.44", ] -[[package]] -name = "widestring" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" - [[package]] name = "winapi" version = "0.3.9" @@ -4787,11 +5311,12 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.44.0" +version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" dependencies = [ - "windows-targets 0.42.2", + "windows-core 0.58.0", + "windows-targets 0.52.6", ] [[package]] @@ -4800,7 +5325,61 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", ] [[package]] @@ -4827,7 +5406,16 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -4862,17 +5450,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -4889,9 +5478,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -4907,9 +5496,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -4925,9 +5514,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -4943,9 +5538,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -4961,9 +5556,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -4979,9 +5574,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -4997,43 +5592,60 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winit" -version = "0.28.7" +version = "0.30.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9596d90b45384f5281384ab204224876e8e8bf7d58366d9b795ad99aa9894b94" +checksum = "a4409c10174df8779dc29a4788cac85ed84024ccbc1743b776b21a520ee1aaf4" dependencies = [ + "ahash", "android-activity", - "bitflags 1.3.2", + "atomic-waker", + "bitflags 2.9.1", + "block2", + "bytemuck", + "calloop", "cfg_aliases", - "core-foundation", - "core-graphics", - "dispatch", - "instant", + "concurrent-queue", + "core-foundation 0.9.4", + "core-graphics 0.23.2", + "cursor-icon", + "dpi", + "js-sys", "libc", - "log", - "mio", + "memmap2", "ndk", - "objc2", - "once_cell", + "objc2 0.5.2", + "objc2-app-kit", + "objc2-foundation 0.2.2", + "objc2-ui-kit", "orbclient", "percent-encoding", + "pin-project", "raw-window-handle", - "redox_syscall 0.3.5", + "redox_syscall", + "rustix 0.38.44", "sctk-adwaita", "smithay-client-toolkit", + "smol_str", + "tracing", + "unicode-segmentation", "wasm-bindgen", + "wasm-bindgen-futures", + "wayland-backend", "wayland-client", - "wayland-commons", "wayland-protocols", - "wayland-scanner", + "wayland-protocols-plasma", "web-sys", - "windows-sys 0.45.0", + "web-time", + "windows-sys 0.52.0", "x11-dl", + "x11rb 0.13.1", + "xkbcommon-dl", ] [[package]] @@ -5061,7 +5673,7 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.9.1", ] [[package]] @@ -5081,11 +5693,26 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1641b26d4dec61337c35a1b1aaf9e3cba8f46f0b43636c609ab0291a648040a" dependencies = [ - "gethostname", - "nix 0.26.4", + "gethostname 0.3.0", + "nix", "winapi", "winapi-wsapoll", - "x11rb-protocol", + "x11rb-protocol 0.12.0", +] + +[[package]] +name = "x11rb" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" +dependencies = [ + "as-raw-xcb-connection", + "gethostname 0.4.3", + "libc", + "libloading", + "once_cell", + "rustix 0.38.44", + "x11rb-protocol 0.13.1", ] [[package]] @@ -5094,9 +5721,15 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82d6c3f9a0fb6701fab8f6cea9b0c0bd5d6876f1f89f7fada07e558077c344bc" dependencies = [ - "nix 0.26.4", + "nix", ] +[[package]] +name = "x11rb-protocol" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" + [[package]] name = "xcursor" version = "0.3.5" @@ -5104,38 +5737,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a0ccd7b4a5345edfcd0c3535718a4e9ff7798ffc536bb5b5a0e26ff84732911" [[package]] -name = "xml-rs" -version = "0.8.19" +name = "xkbcommon-dl" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" +dependencies = [ + "bitflags 2.9.1", + "dlib", + "log", + "once_cell", + "xkeysym", +] [[package]] -name = "zerocopy" -version = "0.7.31" +name = "xkeysym" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c4061bedbb353041c12f413700357bec76df2c7e2ca8e4df8bac24c6bf68e3d" -dependencies = [ - "zerocopy-derive 0.7.31", -] +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" [[package]] -name = "zerocopy" -version = "0.8.24" +name = "xml-rs" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" -dependencies = [ - "zerocopy-derive 0.8.24", -] +checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a" [[package]] -name = "zerocopy-derive" -version = "0.7.31" +name = "zerocopy" +version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3c129550b3e6de3fd0ba67ba5c81818f9805e58b8d7fee80a3a59d2c9fc601a" +checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", + "zerocopy-derive", ] [[package]] diff --git a/examples/basic_text_stream/Cargo.lock b/examples/basic_text_stream/Cargo.lock index 53fd5abc0..1ca93190d 100644 --- a/examples/basic_text_stream/Cargo.lock +++ b/examples/basic_text_stream/Cargo.lock @@ -107,6 +107,152 @@ version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb812ffb58524bdd10860d7d974e2f01cc0950c2438a74ee5ec2e2280c6c4ffa" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +dependencies = [ + "async-channel 2.5.0", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite", + "once_cell", +] + +[[package]] +name = "async-io" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19634d6336019ef220f09fd31168ce5c184b295cbf80345437cc36094ef223ca" +dependencies = [ + "async-lock", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix 1.0.8", + "slab", + "windows-sys 0.60.2", +] + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener 5.4.0", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-native-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9343dc5acf07e79ff82d0c37899f079db3534d99f189a1837c8e549c99405bec" +dependencies = [ + "futures-util", + "native-tls", + "thiserror", + "url", +] + +[[package]] +name = "async-std" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "730294c1c08c2e0f85759590518f6333f0d5a0a766a27d519c1b244c3dfd8a24" +dependencies = [ + "async-channel 1.9.0", + "async-global-executor", + "async-io", + "async-lock", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-tungstenite" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cca750b12e02c389c1694d35c16539f88b8bbaa5945934fdc1b41a776688589" +dependencies = [ + "async-native-tls", + "async-std", + "futures-io", + "futures-util", + "log", + "pin-project-lite", + "tungstenite 0.21.0", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.1.0" @@ -172,6 +318,19 @@ dependencies = [ "generic-array", ] +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel 2.5.0", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + [[package]] name = "bmrng" version = "0.5.2" @@ -293,6 +452,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -465,14 +633,41 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.8" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", "windows-sys 0.52.0", ] +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener 5.4.0", + "pin-project-lite", +] + [[package]] name = "fastrand" version = "2.0.1" @@ -501,6 +696,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -541,6 +751,19 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +[[package]] +name = "futures-lite" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.30" @@ -628,6 +851,18 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "h2" version = "0.3.24" @@ -639,7 +874,7 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http", + "http 0.2.11", "indexmap", "slab", "tokio", @@ -665,6 +900,12 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd5256b483761cd23699d0da46cc6fd2ee3be420bbe6d020ae4a091e70b7e9fd" +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + [[package]] name = "hmac" version = "0.12.1" @@ -694,6 +935,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http-body" version = "0.4.6" @@ -701,7 +953,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", - "http", + "http 0.2.11", "pin-project-lite", ] @@ -728,7 +980,7 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http", + "http 0.2.11", "http-body", "httparse", "httpdate", @@ -748,13 +1000,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", - "http", + "http 0.2.11", "hyper", "rustls", "tokio", "tokio-rustls", ] +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "iana-time-zone" version = "0.1.61" @@ -911,6 +1176,15 @@ dependencies = [ "serde_json", ] +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -935,7 +1209,7 @@ dependencies = [ [[package]] name = "libwebrtc" -version = "0.3.10" +version = "0.3.12" dependencies = [ "cxx", "jni", @@ -970,9 +1244,15 @@ version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + [[package]] name = "livekit" -version = "0.7.8" +version = "0.7.15" dependencies = [ "bmrng", "bytes", @@ -996,11 +1276,12 @@ dependencies = [ [[package]] name = "livekit-api" -version = "0.4.2" +version = "0.4.4" dependencies = [ + "async-tungstenite", "base64", "futures-util", - "http", + "http 0.2.11", "jsonwebtoken", "livekit-protocol", "livekit-runtime", @@ -1022,7 +1303,7 @@ dependencies = [ [[package]] name = "livekit-protocol" -version = "0.3.9" +version = "0.4.0" dependencies = [ "futures-util", "livekit-runtime", @@ -1059,6 +1340,9 @@ name = "log" version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" +dependencies = [ + "value-bag", +] [[package]] name = "memchr" @@ -1098,6 +1382,23 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -1119,7 +1420,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.6", "libc", ] @@ -1138,12 +1439,56 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "openssl" +version = "0.10.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" +dependencies = [ + "bitflags 2.4.2", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "openssl-probe" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "openssl-sys" +version = "0.9.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.1" @@ -1255,12 +1600,37 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + [[package]] name = "pkg-config" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "polling" +version = "3.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee9b2fa7a4517d2c91ff5bc6c297a427a96749d15f98fcdbb22c05571a4d4b7" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.5.2", + "pin-project-lite", + "rustix 1.0.8", + "windows-sys 0.60.2", +] + [[package]] name = "portable-atomic" version = "1.11.0" @@ -1480,14 +1850,16 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http", + "http 0.2.11", "http-body", "hyper", "hyper-rustls", + "hyper-tls", "ipnet", "js-sys", "log", "mime", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -1500,6 +1872,7 @@ dependencies = [ "sync_wrapper", "system-configuration", "tokio", + "tokio-native-tls", "tokio-rustls", "tower-service", "url", @@ -1539,7 +1912,20 @@ dependencies = [ "bitflags 2.4.2", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.13", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustix" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +dependencies = [ + "bitflags 2.4.2", + "errno", + "libc", + "linux-raw-sys 0.9.4", "windows-sys 0.52.0", ] @@ -1818,7 +2204,7 @@ checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67" dependencies = [ "cfg-if", "fastrand", - "rustix", + "rustix 0.38.31", "windows-sys 0.52.0", ] @@ -1915,6 +2301,16 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.24.1" @@ -1944,8 +2340,10 @@ checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" dependencies = [ "futures-util", "log", + "native-tls", "tokio", - "tungstenite", + "tokio-native-tls", + "tungstenite 0.20.1", ] [[package]] @@ -2002,9 +2400,30 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http", + "http 0.2.11", "httparse", "log", + "native-tls", + "rand 0.8.5", + "sha1", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.3.1", + "httparse", + "log", + "native-tls", "rand 0.8.5", "sha1", "thiserror", @@ -2074,6 +2493,18 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "value-bag" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" @@ -2192,7 +2623,7 @@ dependencies = [ [[package]] name = "webrtc-sys" -version = "0.3.7" +version = "0.3.9" dependencies = [ "cc", "cxx", @@ -2204,8 +2635,9 @@ dependencies = [ [[package]] name = "webrtc-sys-build" -version = "0.3.6" +version = "0.3.7" dependencies = [ + "anyhow", "fs2", "regex", "reqwest", @@ -2223,7 +2655,7 @@ dependencies = [ "either", "home", "once_cell", - "rustix", + "rustix 0.38.31", ] [[package]] @@ -2293,6 +2725,15 @@ dependencies = [ "windows-targets 0.52.0", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -2338,6 +2779,22 @@ dependencies = [ "windows_x86_64_msvc 0.52.0", ] +[[package]] +name = "windows-targets" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -2356,6 +2813,12 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -2374,6 +2837,12 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -2392,6 +2861,18 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -2410,6 +2891,12 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -2428,6 +2915,12 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -2446,6 +2939,12 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -2464,6 +2963,12 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "winreg" version = "0.50.0" diff --git a/examples/basic_text_stream/Cargo.toml b/examples/basic_text_stream/Cargo.toml index 6e7d06a3f..70bf819e0 100644 --- a/examples/basic_text_stream/Cargo.toml +++ b/examples/basic_text_stream/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" [dependencies] tokio = { version = "1", features = ["full"] } futures-util = { version = "0.3", default-features = false, features = ["sink"] } -livekit = { path = "../../livekit" } +livekit = { path = "../../livekit", features = ["native-tls"] } log = "0.4.26" env_logger = "0.11.7" diff --git a/examples/play_from_disk/Cargo.lock b/examples/play_from_disk/Cargo.lock index f06220cd3..e9f301d91 100644 --- a/examples/play_from_disk/Cargo.lock +++ b/examples/play_from_disk/Cargo.lock @@ -58,6 +58,152 @@ version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb812ffb58524bdd10860d7d974e2f01cc0950c2438a74ee5ec2e2280c6c4ffa" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +dependencies = [ + "async-channel 2.5.0", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite", + "once_cell", +] + +[[package]] +name = "async-io" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19634d6336019ef220f09fd31168ce5c184b295cbf80345437cc36094ef223ca" +dependencies = [ + "async-lock", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix 1.0.8", + "slab", + "windows-sys 0.60.2", +] + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener 5.4.0", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-native-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9343dc5acf07e79ff82d0c37899f079db3534d99f189a1837c8e549c99405bec" +dependencies = [ + "futures-util", + "native-tls", + "thiserror", + "url", +] + +[[package]] +name = "async-std" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "730294c1c08c2e0f85759590518f6333f0d5a0a766a27d519c1b244c3dfd8a24" +dependencies = [ + "async-channel 1.9.0", + "async-global-executor", + "async-io", + "async-lock", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-tungstenite" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cca750b12e02c389c1694d35c16539f88b8bbaa5945934fdc1b41a776688589" +dependencies = [ + "async-native-tls", + "async-std", + "futures-io", + "futures-util", + "log", + "pin-project-lite", + "tungstenite 0.21.0", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.1.0" @@ -112,6 +258,29 @@ dependencies = [ "generic-array", ] +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel 2.5.0", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "bmrng" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54df9073108f1558f90ae6c5bf5ab9c917c4185f5527b280c87a993cbead0ac" +dependencies = [ + "futures-core", + "tokio", +] + [[package]] name = "bumpalo" version = "3.15.1" @@ -126,9 +295,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "bzip2" @@ -217,6 +386,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -379,14 +557,41 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.8" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", "windows-sys 0.52.0", ] +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener 5.4.0", + "pin-project-lite", +] + [[package]] name = "fastrand" version = "2.0.1" @@ -415,6 +620,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -455,6 +675,19 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +[[package]] +name = "futures-lite" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.30" @@ -514,10 +747,22 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + [[package]] name = "gimli" version = "0.28.1" @@ -530,6 +775,18 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "h2" version = "0.3.24" @@ -541,7 +798,7 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http", + "http 0.2.11", "indexmap", "slab", "tokio", @@ -567,6 +824,12 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd5256b483761cd23699d0da46cc6fd2ee3be420bbe6d020ae4a091e70b7e9fd" +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + [[package]] name = "hmac" version = "0.12.1" @@ -596,6 +859,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http-body" version = "0.4.6" @@ -603,7 +877,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", - "http", + "http 0.2.11", "pin-project-lite", ] @@ -636,7 +910,7 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http", + "http 0.2.11", "http-body", "httparse", "httpdate", @@ -656,13 +930,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", - "http", + "http 0.2.11", "hyper", "rustls", "tokio", "tokio-rustls", ] +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "iana-time-zone" version = "0.1.61" @@ -727,7 +1014,7 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.6", "libc", "windows-sys 0.52.0", ] @@ -800,6 +1087,15 @@ dependencies = [ "serde_json", ] +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -808,9 +1104,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "libloading" @@ -824,7 +1120,7 @@ dependencies = [ [[package]] name = "libwebrtc" -version = "0.3.9" +version = "0.3.12" dependencies = [ "cxx", "jni", @@ -859,10 +1155,18 @@ version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + [[package]] name = "livekit" -version = "0.7.3" +version = "0.7.15" dependencies = [ + "bmrng", + "bytes", "chrono", "futures-util", "lazy_static", @@ -883,11 +1187,12 @@ dependencies = [ [[package]] name = "livekit-api" -version = "0.4.1" +version = "0.4.4" dependencies = [ + "async-tungstenite", "base64", "futures-util", - "http", + "http 0.2.11", "jsonwebtoken", "livekit-protocol", "livekit-runtime", @@ -895,6 +1200,7 @@ dependencies = [ "parking_lot", "pbjson-types", "prost", + "rand 0.9.2", "reqwest", "scopeguard", "serde", @@ -908,7 +1214,7 @@ dependencies = [ [[package]] name = "livekit-protocol" -version = "0.3.7" +version = "0.4.0" dependencies = [ "futures-util", "livekit-runtime", @@ -924,7 +1230,7 @@ dependencies = [ [[package]] name = "livekit-runtime" -version = "0.3.1" +version = "0.4.0" dependencies = [ "tokio", "tokio-stream", @@ -945,6 +1251,9 @@ name = "log" version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +dependencies = [ + "value-bag", +] [[package]] name = "memchr" @@ -974,7 +1283,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.48.0", ] @@ -984,6 +1293,23 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -1005,7 +1331,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.6", "libc", ] @@ -1024,12 +1350,56 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "openssl" +version = "0.10.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" +dependencies = [ + "bitflags 2.4.2", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "openssl-probe" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "openssl-sys" +version = "0.9.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.1" @@ -1060,7 +1430,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" dependencies = [ "base64ct", - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -1141,6 +1511,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + [[package]] name = "pkg-config" version = "0.3.30" @@ -1158,6 +1539,20 @@ dependencies = [ "tokio", ] +[[package]] +name = "polling" +version = "3.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee9b2fa7a4517d2c91ff5bc6c297a427a96749d15f98fcdbb22c05571a4d4b7" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.5.2", + "pin-project-lite", + "rustix 1.0.8", + "windows-sys 0.60.2", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -1252,6 +1647,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "rand" version = "0.8.5" @@ -1259,8 +1660,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", ] [[package]] @@ -1270,7 +1681,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", ] [[package]] @@ -1279,7 +1700,16 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.12", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", ] [[package]] @@ -1332,14 +1762,16 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http", + "http 0.2.11", "http-body", "hyper", "hyper-rustls", + "hyper-tls", "ipnet", "js-sys", "log", "mime", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -1352,6 +1784,7 @@ dependencies = [ "sync_wrapper", "system-configuration", "tokio", + "tokio-native-tls", "tokio-rustls", "tower-service", "url", @@ -1369,7 +1802,7 @@ checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", "cfg-if", - "getrandom", + "getrandom 0.2.12", "libc", "spin", "untrusted", @@ -1391,7 +1824,20 @@ dependencies = [ "bitflags 2.4.2", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.13", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustix" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +dependencies = [ + "bitflags 2.4.2", + "errno", + "libc", + "linux-raw-sys 0.9.4", "windows-sys 0.52.0", ] @@ -1670,7 +2116,7 @@ checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67" dependencies = [ "cfg-if", "fastrand", - "rustix", + "rustix 0.38.31", "windows-sys 0.52.0", ] @@ -1767,6 +2213,16 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.24.1" @@ -1796,8 +2252,10 @@ checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" dependencies = [ "futures-util", "log", + "native-tls", "tokio", - "tungstenite", + "tokio-native-tls", + "tungstenite 0.20.1", ] [[package]] @@ -1854,10 +2312,31 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http", + "http 0.2.11", "httparse", "log", - "rand", + "native-tls", + "rand 0.8.5", + "sha1", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.3.1", + "httparse", + "log", + "native-tls", + "rand 0.8.5", "sha1", "thiserror", "url", @@ -1920,6 +2399,18 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "value-bag" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" @@ -1951,6 +2442,15 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasm-bindgen" version = "0.2.91" @@ -2029,7 +2529,7 @@ dependencies = [ [[package]] name = "webrtc-sys" -version = "0.3.6" +version = "0.3.9" dependencies = [ "cc", "cxx", @@ -2041,8 +2541,9 @@ dependencies = [ [[package]] name = "webrtc-sys-build" -version = "0.3.6" +version = "0.3.7" dependencies = [ + "anyhow", "fs2", "regex", "reqwest", @@ -2060,7 +2561,7 @@ dependencies = [ "either", "home", "once_cell", - "rustix", + "rustix 0.38.31", ] [[package]] @@ -2130,6 +2631,15 @@ dependencies = [ "windows-targets 0.52.0", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -2175,6 +2685,22 @@ dependencies = [ "windows_x86_64_msvc 0.52.0", ] +[[package]] +name = "windows-targets" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -2193,6 +2719,12 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -2211,6 +2743,12 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -2229,6 +2767,18 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -2247,6 +2797,12 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -2265,6 +2821,12 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -2283,6 +2845,12 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -2301,6 +2869,12 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "winreg" version = "0.50.0" @@ -2311,6 +2885,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.4.2", +] + [[package]] name = "zip" version = "0.6.6" diff --git a/examples/play_from_disk/Cargo.toml b/examples/play_from_disk/Cargo.toml index aa50dac9f..a72c155d2 100644 --- a/examples/play_from_disk/Cargo.toml +++ b/examples/play_from_disk/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" [dependencies] tokio = { version = "1", features = ["full"] } -livekit = { path = "../../livekit" } +livekit = { path = "../../livekit", features = ["native-tls"] } thiserror = "1.0.47" log = "0.4.20" env_logger = "0.10.0" diff --git a/examples/save_to_disk/Cargo.toml b/examples/save_to_disk/Cargo.toml index ca0ac59b5..df8c642ed 100644 --- a/examples/save_to_disk/Cargo.toml +++ b/examples/save_to_disk/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" [dependencies] tokio = { version = "1", features = ["full"] } -livekit = { path = "../../livekit" } +livekit = { path = "../../livekit", features = ["native-tls"] } bytes = "1.4.0" tokio-util = "0.7.8" futures = "0.3.28" From 163aece293e6084212023de3388cb242b9637384 Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Thu, 31 Jul 2025 16:19:47 +0100 Subject: [PATCH 229/274] Handle region fetch errors (#686) Map non-200 HTTP status codes to client error --- livekit-api/src/signal_client/region.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/livekit-api/src/signal_client/region.rs b/livekit-api/src/signal_client/region.rs index 09b710105..e431a6771 100644 --- a/livekit-api/src/signal_client/region.rs +++ b/livekit-api/src/signal_client/region.rs @@ -34,6 +34,13 @@ impl RegionUrlProvider { .send() .await .map_err(|e| SignalError::RegionError(e.to_string()))?; + + if !res.status().is_success() { + return Err(SignalError::Client( + res.status(), + res.text().await.unwrap_or_default(), + )); + } let res = res .json::() .await From d10e537d6772864654e7c49a6245cf50616ed751 Mon Sep 17 00:00:00 2001 From: _index <105852101+indexds@users.noreply.github.com> Date: Thu, 31 Jul 2025 17:24:21 +0200 Subject: [PATCH 230/274] Expose departure_timeout in CreateRoomRequest (#685) * feat: expose departure_timeout * fix: typo --- livekit-api/src/services/room.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/livekit-api/src/services/room.rs b/livekit-api/src/services/room.rs index e1643d162..edf2ba8ff 100644 --- a/livekit-api/src/services/room.rs +++ b/livekit-api/src/services/room.rs @@ -24,6 +24,7 @@ const SVC: &str = "RoomService"; #[derive(Debug, Clone, Default)] pub struct CreateRoomOptions { pub empty_timeout: u32, + pub departure_timeout: u32, pub max_participants: u32, pub node_id: String, pub metadata: String, @@ -78,6 +79,7 @@ impl RoomClient { proto::CreateRoomRequest { name: name.to_owned(), empty_timeout: options.empty_timeout, + departure_timeout: options.departure_timeout, max_participants: options.max_participants, node_id: options.node_id, metadata: options.metadata, From ab25c30eb1e036001c76db22fb01cfafeab79698 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 31 Jul 2025 17:55:39 +0100 Subject: [PATCH 231/274] chore: release (#687) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- Cargo.lock | 6 +++--- Cargo.toml | 6 +++--- livekit-api/CHANGELOG.md | 15 +++++++++++++++ livekit-api/Cargo.toml | 2 +- livekit-ffi/CHANGELOG.md | 14 ++++++++++++++ livekit-ffi/Cargo.toml | 2 +- livekit/CHANGELOG.md | 14 ++++++++++++++ livekit/Cargo.toml | 2 +- 8 files changed, 52 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0f9cad6ac..b6c575546 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1616,7 +1616,7 @@ checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "livekit" -version = "0.7.15" +version = "0.7.16" dependencies = [ "bmrng", "bytes", @@ -1640,7 +1640,7 @@ dependencies = [ [[package]] name = "livekit-api" -version = "0.4.4" +version = "0.4.5" dependencies = [ "async-tungstenite", "base64", @@ -1670,7 +1670,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.12.30" +version = "0.12.31" dependencies = [ "bytes", "console-subscriber", diff --git a/Cargo.toml b/Cargo.toml index 1e482dc00..329837bef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,11 +18,11 @@ members = [ imgproc = { version = "0.3.12", path = "imgproc" } yuv-sys = { version = "0.3.7", path = "yuv-sys" } libwebrtc = { version = "0.3.12", path = "libwebrtc" } -livekit-api = { version = "0.4.4", path = "livekit-api" } -livekit-ffi = { version = "0.12.30", path = "livekit-ffi" } +livekit-api = { version = "0.4.5", path = "livekit-api" } +livekit-ffi = { version = "0.12.31", path = "livekit-ffi" } livekit-protocol = { version = "0.4.0", path = "livekit-protocol" } livekit-runtime = { version = "0.4.0", path = "livekit-runtime" } -livekit = { version = "0.7.15", path = "livekit" } +livekit = { version = "0.7.16", path = "livekit" } soxr-sys = { version = "0.1.0", path = "soxr-sys" } webrtc-sys-build = { version = "0.3.7", path = "webrtc-sys/build" } webrtc-sys = { version = "0.3.9", path = "webrtc-sys" } diff --git a/livekit-api/CHANGELOG.md b/livekit-api/CHANGELOG.md index 368f990ae..3f2c04b5e 100644 --- a/livekit-api/CHANGELOG.md +++ b/livekit-api/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.4.5](https://github.com/livekit/rust-sdks/compare/rust-sdks/livekit-api@0.4.4...rust-sdks/livekit-api@0.4.5) - 2025-07-31 + +### Other + +- Expose departure_timeout in CreateRoomRequest ([#685](https://github.com/livekit/rust-sdks/pull/685)) +- Handle region fetch errors ([#686](https://github.com/livekit/rust-sdks/pull/686)) +# Changelog + ## [0.4.4](https://github.com/livekit/rust-sdks/compare/rust-sdks/livekit-api@0.4.3...rust-sdks/livekit-api@0.4.4) - 2025-06-17 ### Other diff --git a/livekit-api/Cargo.toml b/livekit-api/Cargo.toml index d93973e06..2c537147c 100644 --- a/livekit-api/Cargo.toml +++ b/livekit-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-api" -version = "0.4.4" +version = "0.4.5" license = "Apache-2.0" description = "Rust Server SDK for LiveKit" edition = "2021" diff --git a/livekit-ffi/CHANGELOG.md b/livekit-ffi/CHANGELOG.md index 7b14ceee5..7e939a849 100644 --- a/livekit-ffi/CHANGELOG.md +++ b/livekit-ffi/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.12.31](https://github.com/livekit/rust-sdks/compare/rust-sdks/livekit-ffi@0.12.30...rust-sdks/livekit-ffi@0.12.31) - 2025-07-31 + +### Other + +- updated the following local packages: livekit-api, livekit +# Changelog + ## [0.12.30](https://github.com/livekit/rust-sdks/compare/rust-sdks/livekit-ffi@0.12.29...rust-sdks/livekit-ffi@0.12.30) - 2025-07-18 ### Fixed diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 5d8a0932a..3e49dff9d 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.12.30" +version = "0.12.31" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" diff --git a/livekit/CHANGELOG.md b/livekit/CHANGELOG.md index 1a4d38944..ab73617b5 100644 --- a/livekit/CHANGELOG.md +++ b/livekit/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.7.16](https://github.com/livekit/rust-sdks/compare/rust-sdks/livekit@0.7.15...rust-sdks/livekit@0.7.16) - 2025-07-31 + +### Other + +- updated the following local packages: livekit-api +# Changelog + ## [0.7.15](https://github.com/livekit/rust-sdks/compare/rust-sdks/livekit@0.7.14...rust-sdks/livekit@0.7.15) - 2025-07-16 ### Other diff --git a/livekit/Cargo.toml b/livekit/Cargo.toml index 52bdb91ac..c476164bb 100644 --- a/livekit/Cargo.toml +++ b/livekit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit" -version = "0.7.15" +version = "0.7.16" edition = "2021" license = "Apache-2.0" description = "Rust Client SDK for LiveKit" From 9907ad649863727319fbc29210dd955acd8b6825 Mon Sep 17 00:00:00 2001 From: CloudWebRTC Date: Mon, 25 Aug 2025 12:38:58 +0800 Subject: [PATCH 232/274] feat: VA-API support for linux. (#638) * feat: VA-API support for linux. * more changes. * update. * wip. * wip. * update. * vaapi encoder factory. * update. * update. * wip. * First working commit. * add support vaapi detection for factory. * Fix reset encoder for dynamic resolution. * update * fix rc mode. * support profile parse. * update. * rename. * auto detect h264 support. * cargo fmt. * rename display name. * Update tests.yml * Change rate mode to CBR. * feat: NVIDIA HW Codec. * more. * more changes. * add nv h264 decoder impl. * Add CudaContext. * update. * feat: win32 support. * support win32 build. * Update tests.yml * cargo fmt. * Temporarily disable win32 compilation. * Update tests.yml * fix. * Use Implib.so to lazy load cuda/nvcuvid so. * Use Implib.so to lazy load libva/liva-drm so. * Update tests.yml * Update tests.yml * Update tests.yml * Update tests.yml * fix. * benchmark for h264 sw/hw encode. * add cpu info. * update. * update. * update. * update. * update. * Update ffi-builds.yml * Update ffi-builds.yml * fix compile for arm64. * Update ffi-builds.yml * cargo fmt. * Add log printing for vaapi support. * revert changes. * Update ffi-builds.yml * Update ffi-builds.yml * fix. * Update ffi-builds.yml * add cuda include path. * Update ffi-builds.yml * revert changes. * add more logs print for hw codec init. * update. * update. * Update build_linux.sh --- .github/workflows/ffi-builds.yml | 6 +- .github/workflows/tests.yml | 2 +- examples/wgpu_room/src/logo_track.rs | 3 +- webrtc-sys/build.rs | 52 +- webrtc-sys/libwebrtc/build_linux.sh | 4 - .../NvCodec/NvCodec/NvDecoder/NvDecoder.cpp | 850 + .../NvCodec/NvCodec/NvDecoder/NvDecoder.h | 362 + .../NvCodec/NvCodec/NvEncoder/NvEncoder.cpp | 1076 + .../NvCodec/NvCodec/NvEncoder/NvEncoder.h | 505 + .../NvCodec/NvEncoder/NvEncoderCuda.cpp | 271 + .../NvCodec/NvCodec/NvEncoder/NvEncoderCuda.h | 123 + webrtc-sys/src/nvidia/NvCodec/README.txt | 2 + .../src/nvidia/NvCodec/include/Utils/Logger.h | 240 + .../NvCodec/include/Utils/NvCodecUtils.h | 537 + .../src/nvidia/NvCodec/include/cuviddec.h | 1188 + .../src/nvidia/NvCodec/include/nvEncodeAPI.h | 4280 +++ .../src/nvidia/NvCodec/include/nvcuvid.h | 501 + webrtc-sys/src/nvidia/cuda_context.cpp | 123 + webrtc-sys/src/nvidia/cuda_context.h | 26 + webrtc-sys/src/nvidia/h264_decoder_impl.cpp | 186 + webrtc-sys/src/nvidia/h264_decoder_impl.h | 57 + webrtc-sys/src/nvidia/h264_encoder_impl.cpp | 455 + webrtc-sys/src/nvidia/h264_encoder_impl.h | 98 + .../src/nvidia/implib/libcuda.so.init.c | 903 + .../src/nvidia/implib/libcuda.so.tramp.S | 22564 ++++++++++++++++ .../src/nvidia/implib/libnvcuvid.so.init.c | 282 + .../src/nvidia/implib/libnvcuvid.so.tramp.S | 1450 + .../src/nvidia/nvidia_decoder_factory.cpp | 116 + .../src/nvidia/nvidia_decoder_factory.h | 34 + .../src/nvidia/nvidia_encoder_factory.cpp | 75 + .../src/nvidia/nvidia_encoder_factory.h | 42 + webrtc-sys/src/vaapi/h264_encoder_impl.cpp | 315 + webrtc-sys/src/vaapi/h264_encoder_impl.h | 82 + .../src/vaapi/implib/libva-drm.so.init.c | 245 + .../src/vaapi/implib/libva-drm.so.tramp.S | 192 + webrtc-sys/src/vaapi/implib/libva.so.init.c | 333 + webrtc-sys/src/vaapi/implib/libva.so.tramp.S | 3184 +++ webrtc-sys/src/vaapi/vaapi_display_drm.cpp | 135 + webrtc-sys/src/vaapi/vaapi_display_drm.h | 35 + webrtc-sys/src/vaapi/vaapi_display_win32.cpp | 192 + webrtc-sys/src/vaapi/vaapi_display_win32.h | 33 + .../src/vaapi/vaapi_encoder_factory.cpp | 76 + webrtc-sys/src/vaapi/vaapi_encoder_factory.h | 40 + .../src/vaapi/vaapi_h264_encoder_wrapper.cpp | 1880 ++ .../src/vaapi/vaapi_h264_encoder_wrapper.h | 127 + webrtc-sys/src/video_decoder_factory.cpp | 10 +- webrtc-sys/src/video_encoder_factory.cpp | 19 +- webrtc-sys/test/CMakeLists.txt | 92 + webrtc-sys/test/benchmark.cc | 448 + webrtc-sys/test/benchmark.h | 212 + webrtc-sys/test/benchmark_nvidia.cc | 51 + webrtc-sys/test/benchmark_nvidia.h | 24 + webrtc-sys/test/benchmark_openh264.cc | 35 + webrtc-sys/test/benchmark_openh264.h | 22 + webrtc-sys/test/benchmark_vaapi.cc | 49 + webrtc-sys/test/benchmark_vaapi.h | 24 + webrtc-sys/test/cpu/cpu_linux.cc | 208 + webrtc-sys/test/cpu/cpu_linux.h | 53 + webrtc-sys/test/cpu/cpu_wrapper.h | 55 + webrtc-sys/test/fileutils.cc | 209 + webrtc-sys/test/fileutils.h | 152 + webrtc-sys/test/test_main.cc | 20 + webrtc-sys/test/video_source.cc | 432 + webrtc-sys/test/video_source.h | 109 + 64 files changed, 45492 insertions(+), 14 deletions(-) create mode 100644 webrtc-sys/src/nvidia/NvCodec/NvCodec/NvDecoder/NvDecoder.cpp create mode 100644 webrtc-sys/src/nvidia/NvCodec/NvCodec/NvDecoder/NvDecoder.h create mode 100644 webrtc-sys/src/nvidia/NvCodec/NvCodec/NvEncoder/NvEncoder.cpp create mode 100644 webrtc-sys/src/nvidia/NvCodec/NvCodec/NvEncoder/NvEncoder.h create mode 100644 webrtc-sys/src/nvidia/NvCodec/NvCodec/NvEncoder/NvEncoderCuda.cpp create mode 100644 webrtc-sys/src/nvidia/NvCodec/NvCodec/NvEncoder/NvEncoderCuda.h create mode 100644 webrtc-sys/src/nvidia/NvCodec/README.txt create mode 100644 webrtc-sys/src/nvidia/NvCodec/include/Utils/Logger.h create mode 100644 webrtc-sys/src/nvidia/NvCodec/include/Utils/NvCodecUtils.h create mode 100644 webrtc-sys/src/nvidia/NvCodec/include/cuviddec.h create mode 100644 webrtc-sys/src/nvidia/NvCodec/include/nvEncodeAPI.h create mode 100644 webrtc-sys/src/nvidia/NvCodec/include/nvcuvid.h create mode 100644 webrtc-sys/src/nvidia/cuda_context.cpp create mode 100644 webrtc-sys/src/nvidia/cuda_context.h create mode 100644 webrtc-sys/src/nvidia/h264_decoder_impl.cpp create mode 100644 webrtc-sys/src/nvidia/h264_decoder_impl.h create mode 100644 webrtc-sys/src/nvidia/h264_encoder_impl.cpp create mode 100644 webrtc-sys/src/nvidia/h264_encoder_impl.h create mode 100644 webrtc-sys/src/nvidia/implib/libcuda.so.init.c create mode 100644 webrtc-sys/src/nvidia/implib/libcuda.so.tramp.S create mode 100644 webrtc-sys/src/nvidia/implib/libnvcuvid.so.init.c create mode 100644 webrtc-sys/src/nvidia/implib/libnvcuvid.so.tramp.S create mode 100644 webrtc-sys/src/nvidia/nvidia_decoder_factory.cpp create mode 100644 webrtc-sys/src/nvidia/nvidia_decoder_factory.h create mode 100644 webrtc-sys/src/nvidia/nvidia_encoder_factory.cpp create mode 100644 webrtc-sys/src/nvidia/nvidia_encoder_factory.h create mode 100644 webrtc-sys/src/vaapi/h264_encoder_impl.cpp create mode 100644 webrtc-sys/src/vaapi/h264_encoder_impl.h create mode 100644 webrtc-sys/src/vaapi/implib/libva-drm.so.init.c create mode 100644 webrtc-sys/src/vaapi/implib/libva-drm.so.tramp.S create mode 100644 webrtc-sys/src/vaapi/implib/libva.so.init.c create mode 100644 webrtc-sys/src/vaapi/implib/libva.so.tramp.S create mode 100644 webrtc-sys/src/vaapi/vaapi_display_drm.cpp create mode 100644 webrtc-sys/src/vaapi/vaapi_display_drm.h create mode 100644 webrtc-sys/src/vaapi/vaapi_display_win32.cpp create mode 100644 webrtc-sys/src/vaapi/vaapi_display_win32.h create mode 100644 webrtc-sys/src/vaapi/vaapi_encoder_factory.cpp create mode 100644 webrtc-sys/src/vaapi/vaapi_encoder_factory.h create mode 100644 webrtc-sys/src/vaapi/vaapi_h264_encoder_wrapper.cpp create mode 100644 webrtc-sys/src/vaapi/vaapi_h264_encoder_wrapper.h create mode 100644 webrtc-sys/test/CMakeLists.txt create mode 100644 webrtc-sys/test/benchmark.cc create mode 100644 webrtc-sys/test/benchmark.h create mode 100644 webrtc-sys/test/benchmark_nvidia.cc create mode 100644 webrtc-sys/test/benchmark_nvidia.h create mode 100644 webrtc-sys/test/benchmark_openh264.cc create mode 100644 webrtc-sys/test/benchmark_openh264.h create mode 100644 webrtc-sys/test/benchmark_vaapi.cc create mode 100644 webrtc-sys/test/benchmark_vaapi.h create mode 100644 webrtc-sys/test/cpu/cpu_linux.cc create mode 100644 webrtc-sys/test/cpu/cpu_linux.h create mode 100644 webrtc-sys/test/cpu/cpu_wrapper.h create mode 100644 webrtc-sys/test/fileutils.cc create mode 100644 webrtc-sys/test/fileutils.h create mode 100644 webrtc-sys/test/test_main.cc create mode 100644 webrtc-sys/test/video_source.cc create mode 100644 webrtc-sys/test/video_source.h diff --git a/.github/workflows/ffi-builds.yml b/.github/workflows/ffi-builds.yml index ab49e8338..59dce6ec3 100644 --- a/.github/workflows/ffi-builds.yml +++ b/.github/workflows/ffi-builds.yml @@ -74,7 +74,7 @@ jobs: buildargs: --no-default-features --features "rustls-tls-webpki-roots" - os: ubuntu-latest platform: linux - build_image: quay.io/pypa/manylinux_2_28_x86_64 + build_image: sameli/manylinux_2_28_x86_64_cuda_12.3 dylib: liblivekit_ffi.so target: x86_64-unknown-linux-gnu name: ffi-linux-x86_64 @@ -154,7 +154,7 @@ jobs: yum install clang -y; \ yum groupinstall 'Development Tools' -y; \ clang --version; \ - yum install openssl-devel libX11-devel mesa-libGL-devel libXext-devel -y; \ + yum install openssl-devel libX11-devel mesa-libGL-devel libXext-devel libva-devel libdrm-devel -y; \ curl --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y; \ cd livekit-ffi && cargo build --release --target ${{ matrix.target }} ${{ matrix.buildargs }}" @@ -167,7 +167,7 @@ jobs: cd livekit-ffi/ ln -sf $ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/lib/${{ matrix.ndk_arch }}/{libunwind.so,libc++abi.a} $ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/lib/ cargo install cargo-ndk - cargo ndk --bindgen --target ${{ matrix.target }} build --release ${{ matrix.buildargs }} + cargo ndk --target ${{ matrix.target }} build --release ${{ matrix.buildargs }} - name: Copy/Build licenses run: | diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d0ded4622..7f398403f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -50,7 +50,7 @@ jobs: if: ${{ matrix.os == 'ubuntu-latest' }} run: | sudo apt update -y - sudo apt install -y libssl-dev libx11-dev libgl1-mesa-dev libxext-dev + sudo apt install -y libssl-dev libx11-dev libgl1-mesa-dev libxext-dev libva-dev libdrm-dev libnvidia-decode-570 libnvidia-compute-570 nvidia-cuda-dev - uses: actions/checkout@v3 with: diff --git a/examples/wgpu_room/src/logo_track.rs b/examples/wgpu_room/src/logo_track.rs index bcd348521..138db41d4 100644 --- a/examples/wgpu_room/src/logo_track.rs +++ b/examples/wgpu_room/src/logo_track.rs @@ -77,7 +77,8 @@ impl LogoTrack { LocalTrack::Video(track.clone()), TrackPublishOptions { source: TrackSource::Camera, - //simulcast: false, + simulcast: false, + video_codec: VideoCodec::H264, ..Default::default() }, ) diff --git a/webrtc-sys/build.rs b/webrtc-sys/build.rs index 9b32db1d7..d95f519d0 100644 --- a/webrtc-sys/build.rs +++ b/webrtc-sys/build.rs @@ -110,6 +110,7 @@ fn main() { println!("cargo:rustc-link-lib=static=webrtc"); let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap(); + let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap(); match target_os.as_str() { "windows" => { println!("cargo:rustc-link-lib=dylib=msdmo"); @@ -128,7 +129,23 @@ fn main() { println!("cargo:rustc-link-lib=dylib=dwmapi"); println!("cargo:rustc-link-lib=dylib=shcore"); - builder.flag("/std:c++20").flag("/EHsc"); + //let path = env::current_dir().unwrap(); + //println!("cargo:rustc-link-search=native={}/vaapi-windows/x64/lib", path.display()); + //println!("cargo:rustc-link-lib=dylib=va"); + //println!("cargo:rustc-link-lib=dylib=va_win32"); + + builder + //.include("./vaapi-windows/DirectX-Headers-1.0/include") + //.include(path::PathBuf::from("./vaapi-windows/x64/include")) + //.file("vaapi-windows/DirectX-Headers-1.0/src/dxguids.cpp") + //.file("src/vaapi/vaapi_display_win32.cpp") + //.file("src/vaapi/vaapi_h264_encoder_wrapper.cpp") + //.file("src/vaapi/vaapi_encoder_factory.cpp") + //.file("src/vaapi/h264_encoder_impl.cpp") + .flag("/std:c++20") + //.flag("/wd4819") + //.flag("/wd4068") + .flag("/EHsc"); } "linux" => { println!("cargo:rustc-link-lib=dylib=rt"); @@ -136,6 +153,39 @@ fn main() { println!("cargo:rustc-link-lib=dylib=pthread"); println!("cargo:rustc-link-lib=dylib=m"); + match target_arch.as_str() { + "x86_64" => { + builder + .file("src/vaapi/vaapi_display_drm.cpp") + .file("src/vaapi/vaapi_h264_encoder_wrapper.cpp") + .file("src/vaapi/vaapi_encoder_factory.cpp") + .file("src/vaapi/h264_encoder_impl.cpp") + .file("src/vaapi/implib/libva-drm.so.init.c") + .file("src/vaapi/implib/libva-drm.so.tramp.S") + .file("src/vaapi/implib/libva.so.init.c") + .file("src/vaapi/implib/libva.so.tramp.S"); + + builder + .flag("-I/usr/local/cuda-12.3/targets/x86_64-linux/include") + .flag("-Isrc/nvidia/NvCodec/include") + .flag("-Isrc/nvidia/NvCodec/NvCodec") + .file("src/nvidia/NvCodec/NvCodec/NvDecoder/NvDecoder.cpp") + .file("src/nvidia/NvCodec/NvCodec/NvEncoder/NvEncoder.cpp") + .file("src/nvidia/NvCodec/NvCodec/NvEncoder/NvEncoderCuda.cpp") + .file("src/nvidia/h264_encoder_impl.cpp") + .file("src/nvidia/h264_decoder_impl.cpp") + .file("src/nvidia/nvidia_decoder_factory.cpp") + .file("src/nvidia/nvidia_encoder_factory.cpp") + .file("src/nvidia/cuda_context.cpp") + .file("src/nvidia/implib/libcuda.so.init.c") + .file("src/nvidia/implib/libcuda.so.tramp.S") + .file("src/nvidia/implib/libnvcuvid.so.init.c") + .file("src/nvidia/implib/libnvcuvid.so.tramp.S") + .flag("-Wno-deprecated-declarations"); + } + _ => {} + } + builder.flag("-std=c++2a"); } "macos" => { diff --git a/webrtc-sys/libwebrtc/build_linux.sh b/webrtc-sys/libwebrtc/build_linux.sh index ef63dc744..b08e29890 100755 --- a/webrtc-sys/libwebrtc/build_linux.sh +++ b/webrtc-sys/libwebrtc/build_linux.sh @@ -108,10 +108,6 @@ args="is_debug=$debug \ use_rtti=true \ rtc_use_x11=false" -if [ "$debug" = "true" ]; then - args="${args} is_asan=true is_lsan=true"; -fi - # generate ninja files gn gen "$OUTPUT_DIR" --root="src" --args="${args}" diff --git a/webrtc-sys/src/nvidia/NvCodec/NvCodec/NvDecoder/NvDecoder.cpp b/webrtc-sys/src/nvidia/NvCodec/NvCodec/NvDecoder/NvDecoder.cpp new file mode 100644 index 000000000..1ee70c428 --- /dev/null +++ b/webrtc-sys/src/nvidia/NvCodec/NvCodec/NvDecoder/NvDecoder.cpp @@ -0,0 +1,850 @@ +/* +* Copyright 2017-2022 NVIDIA Corporation. All rights reserved. +* +* Please refer to the NVIDIA end user license agreement (EULA) associated +* with this source code for terms and conditions that govern your use of +* this software. Any use, reproduction, disclosure, or distribution of +* this software and related documentation outside the terms of the EULA +* is strictly prohibited. +* +*/ + +#include +#include +#include +#include + +#include "nvcuvid.h" +#include "NvDecoder.h" + +#include "Utils/Logger.h" +simplelogger::Logger* logger = simplelogger::LoggerFactory::CreateConsoleLogger(); + +#define START_TIMER auto start = std::chrono::high_resolution_clock::now(); + +#define STOP_TIMER(print_message) int64_t elapsedTime = std::chrono::duration_cast( \ + std::chrono::high_resolution_clock::now() - start).count(); \ + std::cout << print_message << \ + elapsedTime \ + << " ms " << std::endl; + +#define CUDA_DRVAPI_CALL( call ) \ + do \ + { \ + CUresult err__ = call; \ + if (err__ != CUDA_SUCCESS) \ + { \ + const char *szErrName = NULL; \ + cuGetErrorName(err__, &szErrName); \ + std::ostringstream errorLog; \ + errorLog << "CUDA driver API error " << szErrName ; \ + throw NVDECException::makeNVDECException(errorLog.str(), err__, __FUNCTION__, __FILE__, __LINE__); \ + } \ + } \ + while (0) + +static const char * GetVideoCodecString(cudaVideoCodec eCodec) { + static struct { + cudaVideoCodec eCodec; + const char *name; + } aCodecName [] = { + { cudaVideoCodec_MPEG1, "MPEG-1" }, + { cudaVideoCodec_MPEG2, "MPEG-2" }, + { cudaVideoCodec_MPEG4, "MPEG-4 (ASP)" }, + { cudaVideoCodec_VC1, "VC-1/WMV" }, + { cudaVideoCodec_H264, "AVC/H.264" }, + { cudaVideoCodec_JPEG, "M-JPEG" }, + { cudaVideoCodec_H264_SVC, "H.264/SVC" }, + { cudaVideoCodec_H264_MVC, "H.264/MVC" }, + { cudaVideoCodec_HEVC, "H.265/HEVC" }, + { cudaVideoCodec_VP8, "VP8" }, + { cudaVideoCodec_VP9, "VP9" }, + { cudaVideoCodec_AV1, "AV1" }, + { cudaVideoCodec_NumCodecs, "Invalid" }, + { cudaVideoCodec_YUV420, "YUV 4:2:0" }, + { cudaVideoCodec_YV12, "YV12 4:2:0" }, + { cudaVideoCodec_NV12, "NV12 4:2:0" }, + { cudaVideoCodec_YUYV, "YUYV 4:2:2" }, + { cudaVideoCodec_UYVY, "UYVY 4:2:2" }, + }; + + if (eCodec >= 0 && eCodec <= cudaVideoCodec_NumCodecs) { + return aCodecName[eCodec].name; + } + for (int i = cudaVideoCodec_NumCodecs + 1; i < sizeof(aCodecName) / sizeof(aCodecName[0]); i++) { + if (eCodec == aCodecName[i].eCodec) { + return aCodecName[eCodec].name; + } + } + return "Unknown"; +} + +static const char * GetVideoChromaFormatString(cudaVideoChromaFormat eChromaFormat) { + static struct { + cudaVideoChromaFormat eChromaFormat; + const char *name; + } aChromaFormatName[] = { + { cudaVideoChromaFormat_Monochrome, "YUV 400 (Monochrome)" }, + { cudaVideoChromaFormat_420, "YUV 420" }, + { cudaVideoChromaFormat_422, "YUV 422" }, + { cudaVideoChromaFormat_444, "YUV 444" }, + }; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wtautological-constant-out-of-range-compare" + if (eChromaFormat >= 0 && eChromaFormat < sizeof(aChromaFormatName) / sizeof(aChromaFormatName[0])) { + return aChromaFormatName[eChromaFormat].name; + } +#pragma clang diagnostic pop + return "Unknown"; +} + +static float GetChromaHeightFactor(cudaVideoSurfaceFormat eSurfaceFormat) +{ + float factor = 0.5; + switch (eSurfaceFormat) + { + case cudaVideoSurfaceFormat_NV12: + case cudaVideoSurfaceFormat_P016: + factor = 0.5; + break; + case cudaVideoSurfaceFormat_YUV444: + case cudaVideoSurfaceFormat_YUV444_16Bit: + factor = 1.0; + break; + } + + return factor; +} + +static int GetChromaPlaneCount(cudaVideoSurfaceFormat eSurfaceFormat) +{ + int numPlane = 1; + switch (eSurfaceFormat) + { + case cudaVideoSurfaceFormat_NV12: + case cudaVideoSurfaceFormat_P016: + numPlane = 1; + break; + case cudaVideoSurfaceFormat_YUV444: + case cudaVideoSurfaceFormat_YUV444_16Bit: + numPlane = 2; + break; + } + + return numPlane; +} + +std::map NvDecoder::sessionOverHead = { {0,0}, {1,0} }; + +/** +* @brief This function is used to get codec string from codec id +*/ +const char *NvDecoder::GetCodecString(cudaVideoCodec eCodec) +{ + return GetVideoCodecString(eCodec); +} + +/* Called when the parser encounters sequence header for AV1 SVC content +* return value interpretation: +* < 0 : fail, >=0: succeeded (bit 0-9: currOperatingPoint, bit 10-10: bDispAllLayer, bit 11-30: reserved, must be set 0) +*/ +int NvDecoder::GetOperatingPoint(CUVIDOPERATINGPOINTINFO *pOPInfo) +{ + if (pOPInfo->codec == cudaVideoCodec_AV1) + { + if (pOPInfo->av1.operating_points_cnt > 1) + { + // clip has SVC enabled + if (m_nOperatingPoint >= pOPInfo->av1.operating_points_cnt) + m_nOperatingPoint = 0; + + printf("AV1 SVC clip: operating point count %d ", pOPInfo->av1.operating_points_cnt); + printf("Selected operating point: %d, IDC 0x%x bOutputAllLayers %d\n", m_nOperatingPoint, pOPInfo->av1.operating_points_idc[m_nOperatingPoint], m_bDispAllLayers); + return (m_nOperatingPoint | (m_bDispAllLayers << 10)); + } + } + return -1; +} + +/* Return value from HandleVideoSequence() are interpreted as : +* 0: fail, 1: succeeded, > 1: override dpb size of parser (set by CUVIDPARSERPARAMS::ulMaxNumDecodeSurfaces while creating parser) +*/ +int NvDecoder::HandleVideoSequence(CUVIDEOFORMAT *pVideoFormat) +{ + START_TIMER + m_videoInfo.str(""); + m_videoInfo.clear(); + m_videoInfo << "Video Input Information" << std::endl + << "\tCodec : " << GetVideoCodecString(pVideoFormat->codec) << std::endl + << "\tFrame rate : " << pVideoFormat->frame_rate.numerator << "/" << pVideoFormat->frame_rate.denominator + << " = " << 1.0 * pVideoFormat->frame_rate.numerator / pVideoFormat->frame_rate.denominator << " fps" << std::endl + << "\tSequence : " << (pVideoFormat->progressive_sequence ? "Progressive" : "Interlaced") << std::endl + << "\tCoded size : [" << pVideoFormat->coded_width << ", " << pVideoFormat->coded_height << "]" << std::endl + << "\tDisplay area : [" << pVideoFormat->display_area.left << ", " << pVideoFormat->display_area.top << ", " + << pVideoFormat->display_area.right << ", " << pVideoFormat->display_area.bottom << "]" << std::endl + << "\tChroma : " << GetVideoChromaFormatString(pVideoFormat->chroma_format) << std::endl + << "\tBit depth : " << pVideoFormat->bit_depth_luma_minus8 + 8 + ; + m_videoInfo << std::endl; + + int nDecodeSurface = pVideoFormat->min_num_decode_surfaces; + + CUVIDDECODECAPS decodecaps; + memset(&decodecaps, 0, sizeof(decodecaps)); + + decodecaps.eCodecType = pVideoFormat->codec; + decodecaps.eChromaFormat = pVideoFormat->chroma_format; + decodecaps.nBitDepthMinus8 = pVideoFormat->bit_depth_luma_minus8; + + CUDA_DRVAPI_CALL(cuCtxPushCurrent(m_cuContext)); + NVDEC_API_CALL(cuvidGetDecoderCaps(&decodecaps)); + CUDA_DRVAPI_CALL(cuCtxPopCurrent(NULL)); + + if(!decodecaps.bIsSupported){ + NVDEC_THROW_ERROR("Codec not supported on this GPU", CUDA_ERROR_NOT_SUPPORTED); + return nDecodeSurface; + } + + if ((pVideoFormat->coded_width > decodecaps.nMaxWidth) || + (pVideoFormat->coded_height > decodecaps.nMaxHeight)){ + + std::ostringstream errorString; + errorString << std::endl + << "Resolution : " << pVideoFormat->coded_width << "x" << pVideoFormat->coded_height << std::endl + << "Max Supported (wxh) : " << decodecaps.nMaxWidth << "x" << decodecaps.nMaxHeight << std::endl + << "Resolution not supported on this GPU"; + + const std::string cErr = errorString.str(); + NVDEC_THROW_ERROR(cErr, CUDA_ERROR_NOT_SUPPORTED); + return nDecodeSurface; + } + + if ((pVideoFormat->coded_width>>4)*(pVideoFormat->coded_height>>4) > decodecaps.nMaxMBCount){ + + std::ostringstream errorString; + errorString << std::endl + << "MBCount : " << (pVideoFormat->coded_width >> 4)*(pVideoFormat->coded_height >> 4) << std::endl + << "Max Supported mbcnt : " << decodecaps.nMaxMBCount << std::endl + << "MBCount not supported on this GPU"; + + const std::string cErr = errorString.str(); + NVDEC_THROW_ERROR(cErr, CUDA_ERROR_NOT_SUPPORTED); + return nDecodeSurface; + } + + if (m_nWidth && m_nLumaHeight && m_nChromaHeight) { + + // cuvidCreateDecoder() has been called before, and now there's possible config change + return ReconfigureDecoder(pVideoFormat); + } + + // eCodec has been set in the constructor (for parser). Here it's set again for potential correction + m_eCodec = pVideoFormat->codec; + m_eChromaFormat = pVideoFormat->chroma_format; + m_nBitDepthMinus8 = pVideoFormat->bit_depth_luma_minus8; + m_nBPP = m_nBitDepthMinus8 > 0 ? 2 : 1; + + // Set the output surface format same as chroma format + if (m_eChromaFormat == cudaVideoChromaFormat_420 || cudaVideoChromaFormat_Monochrome) + m_eOutputFormat = pVideoFormat->bit_depth_luma_minus8 ? cudaVideoSurfaceFormat_P016 : cudaVideoSurfaceFormat_NV12; + else if (m_eChromaFormat == cudaVideoChromaFormat_444) + m_eOutputFormat = pVideoFormat->bit_depth_luma_minus8 ? cudaVideoSurfaceFormat_YUV444_16Bit : cudaVideoSurfaceFormat_YUV444; + else if (m_eChromaFormat == cudaVideoChromaFormat_422) + m_eOutputFormat = cudaVideoSurfaceFormat_NV12; // no 4:2:2 output format supported yet so make 420 default + + // Check if output format supported. If not, check falback options + if (!(decodecaps.nOutputFormatMask & (1 << m_eOutputFormat))) + { + if (decodecaps.nOutputFormatMask & (1 << cudaVideoSurfaceFormat_NV12)) + m_eOutputFormat = cudaVideoSurfaceFormat_NV12; + else if (decodecaps.nOutputFormatMask & (1 << cudaVideoSurfaceFormat_P016)) + m_eOutputFormat = cudaVideoSurfaceFormat_P016; + else if (decodecaps.nOutputFormatMask & (1 << cudaVideoSurfaceFormat_YUV444)) + m_eOutputFormat = cudaVideoSurfaceFormat_YUV444; + else if (decodecaps.nOutputFormatMask & (1 << cudaVideoSurfaceFormat_YUV444_16Bit)) + m_eOutputFormat = cudaVideoSurfaceFormat_YUV444_16Bit; + else + NVDEC_THROW_ERROR("No supported output format found", CUDA_ERROR_NOT_SUPPORTED); + } + m_videoFormat = *pVideoFormat; + + CUVIDDECODECREATEINFO videoDecodeCreateInfo = { 0 }; + videoDecodeCreateInfo.CodecType = pVideoFormat->codec; + videoDecodeCreateInfo.ChromaFormat = pVideoFormat->chroma_format; + videoDecodeCreateInfo.OutputFormat = m_eOutputFormat; + videoDecodeCreateInfo.bitDepthMinus8 = pVideoFormat->bit_depth_luma_minus8; + if (pVideoFormat->progressive_sequence) + videoDecodeCreateInfo.DeinterlaceMode = cudaVideoDeinterlaceMode_Weave; + else + videoDecodeCreateInfo.DeinterlaceMode = cudaVideoDeinterlaceMode_Adaptive; + videoDecodeCreateInfo.ulNumOutputSurfaces = 2; + // With PreferCUVID, JPEG is still decoded by CUDA while video is decoded by NVDEC hardware + videoDecodeCreateInfo.ulCreationFlags = cudaVideoCreate_PreferCUVID; + videoDecodeCreateInfo.ulNumDecodeSurfaces = nDecodeSurface; + videoDecodeCreateInfo.vidLock = m_ctxLock; + videoDecodeCreateInfo.ulWidth = pVideoFormat->coded_width; + videoDecodeCreateInfo.ulHeight = pVideoFormat->coded_height; + // AV1 has max width/height of sequence in sequence header + if (pVideoFormat->codec == cudaVideoCodec_AV1 && pVideoFormat->seqhdr_data_length > 0) + { + // dont overwrite if it is already set from cmdline or reconfig.txt + if (!(m_nMaxWidth > pVideoFormat->coded_width || m_nMaxHeight > pVideoFormat->coded_height)) + { + CUVIDEOFORMATEX *vidFormatEx = (CUVIDEOFORMATEX *)pVideoFormat; + m_nMaxWidth = vidFormatEx->av1.max_width; + m_nMaxHeight = vidFormatEx->av1.max_height; + } + } + if (m_nMaxWidth < (int)pVideoFormat->coded_width) + m_nMaxWidth = pVideoFormat->coded_width; + if (m_nMaxHeight < (int)pVideoFormat->coded_height) + m_nMaxHeight = pVideoFormat->coded_height; + videoDecodeCreateInfo.ulMaxWidth = m_nMaxWidth; + videoDecodeCreateInfo.ulMaxHeight = m_nMaxHeight; + + if (!(m_cropRect.r && m_cropRect.b) && !(m_resizeDim.w && m_resizeDim.h)) { + m_nWidth = pVideoFormat->display_area.right - pVideoFormat->display_area.left; + m_nLumaHeight = pVideoFormat->display_area.bottom - pVideoFormat->display_area.top; + videoDecodeCreateInfo.ulTargetWidth = pVideoFormat->coded_width; + videoDecodeCreateInfo.ulTargetHeight = pVideoFormat->coded_height; + } else { + if (m_resizeDim.w && m_resizeDim.h) { + videoDecodeCreateInfo.display_area.left = pVideoFormat->display_area.left; + videoDecodeCreateInfo.display_area.top = pVideoFormat->display_area.top; + videoDecodeCreateInfo.display_area.right = pVideoFormat->display_area.right; + videoDecodeCreateInfo.display_area.bottom = pVideoFormat->display_area.bottom; + m_nWidth = m_resizeDim.w; + m_nLumaHeight = m_resizeDim.h; + } + + if (m_cropRect.r && m_cropRect.b) { + videoDecodeCreateInfo.display_area.left = m_cropRect.l; + videoDecodeCreateInfo.display_area.top = m_cropRect.t; + videoDecodeCreateInfo.display_area.right = m_cropRect.r; + videoDecodeCreateInfo.display_area.bottom = m_cropRect.b; + m_nWidth = m_cropRect.r - m_cropRect.l; + m_nLumaHeight = m_cropRect.b - m_cropRect.t; + } + videoDecodeCreateInfo.ulTargetWidth = m_nWidth; + videoDecodeCreateInfo.ulTargetHeight = m_nLumaHeight; + } + + m_nChromaHeight = (int)(ceil(m_nLumaHeight * GetChromaHeightFactor(m_eOutputFormat))); + m_nNumChromaPlanes = GetChromaPlaneCount(m_eOutputFormat); + m_nSurfaceHeight = videoDecodeCreateInfo.ulTargetHeight; + m_nSurfaceWidth = videoDecodeCreateInfo.ulTargetWidth; + m_displayRect.b = videoDecodeCreateInfo.display_area.bottom; + m_displayRect.t = videoDecodeCreateInfo.display_area.top; + m_displayRect.l = videoDecodeCreateInfo.display_area.left; + m_displayRect.r = videoDecodeCreateInfo.display_area.right; + + m_videoInfo << "Video Decoding Params:" << std::endl + << "\tNum Surfaces : " << videoDecodeCreateInfo.ulNumDecodeSurfaces << std::endl + << "\tCrop : [" << videoDecodeCreateInfo.display_area.left << ", " << videoDecodeCreateInfo.display_area.top << ", " + << videoDecodeCreateInfo.display_area.right << ", " << videoDecodeCreateInfo.display_area.bottom << "]" << std::endl + << "\tResize : " << videoDecodeCreateInfo.ulTargetWidth << "x" << videoDecodeCreateInfo.ulTargetHeight << std::endl + << "\tDeinterlace : " << std::vector{"Weave", "Bob", "Adaptive"}[videoDecodeCreateInfo.DeinterlaceMode] + ; + m_videoInfo << std::endl; + + CUDA_DRVAPI_CALL(cuCtxPushCurrent(m_cuContext)); + NVDEC_API_CALL(cuvidCreateDecoder(&m_hDecoder, &videoDecodeCreateInfo)); + CUDA_DRVAPI_CALL(cuCtxPopCurrent(NULL)); + STOP_TIMER("Session Initialization Time: "); + NvDecoder::addDecoderSessionOverHead(getDecoderSessionID(), elapsedTime); + return nDecodeSurface; +} + +int NvDecoder::ReconfigureDecoder(CUVIDEOFORMAT *pVideoFormat) +{ + if (pVideoFormat->bit_depth_luma_minus8 != m_videoFormat.bit_depth_luma_minus8 || pVideoFormat->bit_depth_chroma_minus8 != m_videoFormat.bit_depth_chroma_minus8){ + + NVDEC_THROW_ERROR("Reconfigure Not supported for bit depth change", CUDA_ERROR_NOT_SUPPORTED); + } + + if (pVideoFormat->chroma_format != m_videoFormat.chroma_format) { + + NVDEC_THROW_ERROR("Reconfigure Not supported for chroma format change", CUDA_ERROR_NOT_SUPPORTED); + } + + bool bDecodeResChange = !(pVideoFormat->coded_width == m_videoFormat.coded_width && pVideoFormat->coded_height == m_videoFormat.coded_height); + bool bDisplayRectChange = !(pVideoFormat->display_area.bottom == m_videoFormat.display_area.bottom && pVideoFormat->display_area.top == m_videoFormat.display_area.top \ + && pVideoFormat->display_area.left == m_videoFormat.display_area.left && pVideoFormat->display_area.right == m_videoFormat.display_area.right); + + int nDecodeSurface = pVideoFormat->min_num_decode_surfaces; + + if ((pVideoFormat->coded_width > m_nMaxWidth) || (pVideoFormat->coded_height > m_nMaxHeight)) { + // For VP9, let driver handle the change if new width/height > maxwidth/maxheight + if ((m_eCodec != cudaVideoCodec_VP9) || m_bReconfigExternal) + { + NVDEC_THROW_ERROR("Reconfigure Not supported when width/height > maxwidth/maxheight", CUDA_ERROR_NOT_SUPPORTED); + } + return 1; + } + + if (!bDecodeResChange && !m_bReconfigExtPPChange) { + // if the coded_width/coded_height hasn't changed but display resolution has changed, then need to update width/height for + // correct output without cropping. Example : 1920x1080 vs 1920x1088 + if (bDisplayRectChange) + { + m_nWidth = pVideoFormat->display_area.right - pVideoFormat->display_area.left; + m_nLumaHeight = pVideoFormat->display_area.bottom - pVideoFormat->display_area.top; + m_nChromaHeight = (int)ceil(m_nLumaHeight * GetChromaHeightFactor(m_eOutputFormat)); + m_nNumChromaPlanes = GetChromaPlaneCount(m_eOutputFormat); + } + + // no need for reconfigureDecoder(). Just return + return 1; + } + + CUVIDRECONFIGUREDECODERINFO reconfigParams = { 0 }; + + reconfigParams.ulWidth = m_videoFormat.coded_width = pVideoFormat->coded_width; + reconfigParams.ulHeight = m_videoFormat.coded_height = pVideoFormat->coded_height; + + // Dont change display rect and get scaled output from decoder. This will help display app to present apps smoothly + reconfigParams.display_area.bottom = m_displayRect.b; + reconfigParams.display_area.top = m_displayRect.t; + reconfigParams.display_area.left = m_displayRect.l; + reconfigParams.display_area.right = m_displayRect.r; + reconfigParams.ulTargetWidth = m_nSurfaceWidth; + reconfigParams.ulTargetHeight = m_nSurfaceHeight; + + // If external reconfigure is called along with resolution change even if post processing params is not changed, + // do full reconfigure params update + if ((m_bReconfigExternal && bDecodeResChange) || m_bReconfigExtPPChange) { + // update display rect and target resolution if requested explicitely + m_bReconfigExternal = false; + m_bReconfigExtPPChange = false; + m_videoFormat = *pVideoFormat; + if (!(m_cropRect.r && m_cropRect.b) && !(m_resizeDim.w && m_resizeDim.h)) { + m_nWidth = pVideoFormat->display_area.right - pVideoFormat->display_area.left; + m_nLumaHeight = pVideoFormat->display_area.bottom - pVideoFormat->display_area.top; + reconfigParams.ulTargetWidth = pVideoFormat->coded_width; + reconfigParams.ulTargetHeight = pVideoFormat->coded_height; + } + else { + if (m_resizeDim.w && m_resizeDim.h) { + reconfigParams.display_area.left = pVideoFormat->display_area.left; + reconfigParams.display_area.top = pVideoFormat->display_area.top; + reconfigParams.display_area.right = pVideoFormat->display_area.right; + reconfigParams.display_area.bottom = pVideoFormat->display_area.bottom; + m_nWidth = m_resizeDim.w; + m_nLumaHeight = m_resizeDim.h; + } + + if (m_cropRect.r && m_cropRect.b) { + reconfigParams.display_area.left = m_cropRect.l; + reconfigParams.display_area.top = m_cropRect.t; + reconfigParams.display_area.right = m_cropRect.r; + reconfigParams.display_area.bottom = m_cropRect.b; + m_nWidth = m_cropRect.r - m_cropRect.l; + m_nLumaHeight = m_cropRect.b - m_cropRect.t; + } + reconfigParams.ulTargetWidth = m_nWidth; + reconfigParams.ulTargetHeight = m_nLumaHeight; + } + + m_nChromaHeight = (int)ceil(m_nLumaHeight * GetChromaHeightFactor(m_eOutputFormat)); + m_nNumChromaPlanes = GetChromaPlaneCount(m_eOutputFormat); + m_nSurfaceHeight = reconfigParams.ulTargetHeight; + m_nSurfaceWidth = reconfigParams.ulTargetWidth; + m_displayRect.b = reconfigParams.display_area.bottom; + m_displayRect.t = reconfigParams.display_area.top; + m_displayRect.l = reconfigParams.display_area.left; + m_displayRect.r = reconfigParams.display_area.right; + } + + reconfigParams.ulNumDecodeSurfaces = nDecodeSurface; + + START_TIMER + CUDA_DRVAPI_CALL(cuCtxPushCurrent(m_cuContext)); + NVDEC_API_CALL(cuvidReconfigureDecoder(m_hDecoder, &reconfigParams)); + CUDA_DRVAPI_CALL(cuCtxPopCurrent(NULL)); + STOP_TIMER("Session Reconfigure Time: "); + + return nDecodeSurface; +} + +int NvDecoder::setReconfigParams(const Rect *pCropRect, const Dim *pResizeDim) +{ + m_bReconfigExternal = true; + m_bReconfigExtPPChange = false; + if (pCropRect) + { + if (!((pCropRect->t == m_cropRect.t) && (pCropRect->l == m_cropRect.l) && + (pCropRect->b == m_cropRect.b) && (pCropRect->r == m_cropRect.r))) + { + m_bReconfigExtPPChange = true; + m_cropRect = *pCropRect; + } + } + if (pResizeDim) + { + if (!((pResizeDim->w == m_resizeDim.w) && (pResizeDim->h == m_resizeDim.h))) + { + m_bReconfigExtPPChange = true; + m_resizeDim = *pResizeDim; + } + } + + // Clear existing output buffers of different size + uint8_t *pFrame = NULL; + while (!m_vpFrame.empty()) + { + pFrame = m_vpFrame.back(); + m_vpFrame.pop_back(); + if (m_bUseDeviceFrame) + { + CUDA_DRVAPI_CALL(cuCtxPushCurrent(m_cuContext)); + CUDA_DRVAPI_CALL(cuMemFree((CUdeviceptr)pFrame)); + CUDA_DRVAPI_CALL(cuCtxPopCurrent(NULL)); + } + else + { + delete pFrame; + } + } + + return 1; +} + +/* Return value from HandlePictureDecode() are interpreted as: +* 0: fail, >=1: succeeded +*/ +int NvDecoder::HandlePictureDecode(CUVIDPICPARAMS *pPicParams) { + if (!m_hDecoder) + { + NVDEC_THROW_ERROR("Decoder not initialized.", CUDA_ERROR_NOT_INITIALIZED); + return false; + } + m_nPicNumInDecodeOrder[pPicParams->CurrPicIdx] = m_nDecodePicCnt++; + CUDA_DRVAPI_CALL(cuCtxPushCurrent(m_cuContext)); + NVDEC_API_CALL(cuvidDecodePicture(m_hDecoder, pPicParams)); + if (m_bForce_zero_latency && ((!pPicParams->field_pic_flag) || (pPicParams->second_field))) + { + CUVIDPARSERDISPINFO dispInfo; + memset(&dispInfo, 0, sizeof(dispInfo)); + dispInfo.picture_index = pPicParams->CurrPicIdx; + dispInfo.progressive_frame = !pPicParams->field_pic_flag; + dispInfo.top_field_first = pPicParams->bottom_field_flag ^ 1; + HandlePictureDisplay(&dispInfo); + } + CUDA_DRVAPI_CALL(cuCtxPopCurrent(NULL)); + return 1; +} + +/* Return value from HandlePictureDisplay() are interpreted as: +* 0: fail, >=1: succeeded +*/ +int NvDecoder::HandlePictureDisplay(CUVIDPARSERDISPINFO *pDispInfo) { + CUVIDPROCPARAMS videoProcessingParameters = {}; + videoProcessingParameters.progressive_frame = pDispInfo->progressive_frame; + videoProcessingParameters.second_field = pDispInfo->repeat_first_field + 1; + videoProcessingParameters.top_field_first = pDispInfo->top_field_first; + videoProcessingParameters.unpaired_field = pDispInfo->repeat_first_field < 0; + videoProcessingParameters.output_stream = m_cuvidStream; + + if (m_bExtractSEIMessage) + { + if (m_SEIMessagesDisplayOrder[pDispInfo->picture_index].pSEIData) + { + // Write SEI Message + uint8_t *seiBuffer = (uint8_t *)(m_SEIMessagesDisplayOrder[pDispInfo->picture_index].pSEIData); + uint32_t seiNumMessages = m_SEIMessagesDisplayOrder[pDispInfo->picture_index].sei_message_count; + CUSEIMESSAGE *seiMessagesInfo = m_SEIMessagesDisplayOrder[pDispInfo->picture_index].pSEIMessage; + if (m_fpSEI) + { + for (uint32_t i = 0; i < seiNumMessages; i++) + { + if (m_eCodec == cudaVideoCodec_H264 || cudaVideoCodec_H264_SVC || cudaVideoCodec_H264_MVC || cudaVideoCodec_HEVC) + { + switch (seiMessagesInfo[i].sei_message_type) + { + case SEI_TYPE_TIME_CODE: + { + HEVCSEITIMECODE *timecode = (HEVCSEITIMECODE *)seiBuffer; + fwrite(timecode, sizeof(HEVCSEITIMECODE), 1, m_fpSEI); + } + break; + case SEI_TYPE_USER_DATA_UNREGISTERED: + { + fwrite(seiBuffer, seiMessagesInfo[i].sei_message_size, 1, m_fpSEI); + } + break; + } + } + if (m_eCodec == cudaVideoCodec_AV1) + { + fwrite(seiBuffer, seiMessagesInfo[i].sei_message_size, 1, m_fpSEI); + } + seiBuffer += seiMessagesInfo[i].sei_message_size; + } + } + free(m_SEIMessagesDisplayOrder[pDispInfo->picture_index].pSEIData); + free(m_SEIMessagesDisplayOrder[pDispInfo->picture_index].pSEIMessage); + } + } + + CUdeviceptr dpSrcFrame = 0; + unsigned int nSrcPitch = 0; + CUDA_DRVAPI_CALL(cuCtxPushCurrent(m_cuContext)); + NVDEC_API_CALL(cuvidMapVideoFrame(m_hDecoder, pDispInfo->picture_index, &dpSrcFrame, + &nSrcPitch, &videoProcessingParameters)); + + CUVIDGETDECODESTATUS DecodeStatus; + memset(&DecodeStatus, 0, sizeof(DecodeStatus)); + CUresult result = cuvidGetDecodeStatus(m_hDecoder, pDispInfo->picture_index, &DecodeStatus); + if (result == CUDA_SUCCESS && (DecodeStatus.decodeStatus == cuvidDecodeStatus_Error || DecodeStatus.decodeStatus == cuvidDecodeStatus_Error_Concealed)) + { + printf("Decode Error occurred for picture %d\n", m_nPicNumInDecodeOrder[pDispInfo->picture_index]); + } + + uint8_t *pDecodedFrame = nullptr; + { + std::lock_guard lock(m_mtxVPFrame); + if ((unsigned)++m_nDecodedFrame > m_vpFrame.size()) + { + // Not enough frames in stock + m_nFrameAlloc++; + uint8_t *pFrame = NULL; + if (m_bUseDeviceFrame) + { + if (m_bDeviceFramePitched) + { + CUDA_DRVAPI_CALL(cuMemAllocPitch((CUdeviceptr *)&pFrame, &m_nDeviceFramePitch, GetWidth() * m_nBPP, m_nLumaHeight + (m_nChromaHeight * m_nNumChromaPlanes), 16)); + } + else + { + CUDA_DRVAPI_CALL(cuMemAlloc((CUdeviceptr *)&pFrame, GetFrameSize())); + } + } + else + { + pFrame = new uint8_t[GetFrameSize()]; + } + m_vpFrame.push_back(pFrame); + } + pDecodedFrame = m_vpFrame[m_nDecodedFrame - 1]; + } + + // Copy luma plane + CUDA_MEMCPY2D m = { 0 }; + m.srcMemoryType = CU_MEMORYTYPE_DEVICE; + m.srcDevice = dpSrcFrame; + m.srcPitch = nSrcPitch; + m.dstMemoryType = m_bUseDeviceFrame ? CU_MEMORYTYPE_DEVICE : CU_MEMORYTYPE_HOST; + m.dstDevice = (CUdeviceptr)(m.dstHost = pDecodedFrame); + m.dstPitch = m_nDeviceFramePitch ? m_nDeviceFramePitch : GetWidth() * m_nBPP; + m.WidthInBytes = GetWidth() * m_nBPP; + m.Height = m_nLumaHeight; + CUDA_DRVAPI_CALL(cuMemcpy2DAsync(&m, m_cuvidStream)); + + // Copy chroma plane + // NVDEC output has luma height aligned by 2. Adjust chroma offset by aligning height + m.srcDevice = (CUdeviceptr)((uint8_t *)dpSrcFrame + m.srcPitch * ((m_nSurfaceHeight + 1) & ~1)); + m.dstDevice = (CUdeviceptr)(m.dstHost = pDecodedFrame + m.dstPitch * m_nLumaHeight); + m.Height = m_nChromaHeight; + CUDA_DRVAPI_CALL(cuMemcpy2DAsync(&m, m_cuvidStream)); + + if (m_nNumChromaPlanes == 2) + { + m.srcDevice = (CUdeviceptr)((uint8_t *)dpSrcFrame + m.srcPitch * ((m_nSurfaceHeight + 1) & ~1) * 2); + m.dstDevice = (CUdeviceptr)(m.dstHost = pDecodedFrame + m.dstPitch * m_nLumaHeight * 2); + m.Height = m_nChromaHeight; + CUDA_DRVAPI_CALL(cuMemcpy2DAsync(&m, m_cuvidStream)); + } + CUDA_DRVAPI_CALL(cuStreamSynchronize(m_cuvidStream)); + CUDA_DRVAPI_CALL(cuCtxPopCurrent(NULL)); + + if ((int)m_vTimestamp.size() < m_nDecodedFrame) { + m_vTimestamp.resize(m_vpFrame.size()); + } + m_vTimestamp[m_nDecodedFrame - 1] = pDispInfo->timestamp; + + NVDEC_API_CALL(cuvidUnmapVideoFrame(m_hDecoder, dpSrcFrame)); + return 1; +} + +int NvDecoder::GetSEIMessage(CUVIDSEIMESSAGEINFO *pSEIMessageInfo) +{ + uint32_t seiNumMessages = pSEIMessageInfo->sei_message_count; + CUSEIMESSAGE *seiMessagesInfo = pSEIMessageInfo->pSEIMessage; + size_t totalSEIBufferSize = 0; + if ((pSEIMessageInfo->picIdx < 0) || (pSEIMessageInfo->picIdx >= MAX_FRM_CNT)) + { + printf("Invalid picture index (%d)\n", pSEIMessageInfo->picIdx); + return 0; + } + for (uint32_t i = 0; i < seiNumMessages; i++) + { + totalSEIBufferSize += seiMessagesInfo[i].sei_message_size; + } + if (!m_pCurrSEIMessage) + { + printf("Out of Memory, Allocation failed for m_pCurrSEIMessage\n"); + return 0; + } + m_pCurrSEIMessage->pSEIData = malloc(totalSEIBufferSize); + if (!m_pCurrSEIMessage->pSEIData) + { + printf("Out of Memory, Allocation failed for SEI Buffer\n"); + return 0; + } + memcpy(m_pCurrSEIMessage->pSEIData, pSEIMessageInfo->pSEIData, totalSEIBufferSize); + m_pCurrSEIMessage->pSEIMessage = (CUSEIMESSAGE *)malloc(sizeof(CUSEIMESSAGE) * seiNumMessages); + if (!m_pCurrSEIMessage->pSEIMessage) + { + free(m_pCurrSEIMessage->pSEIData); + m_pCurrSEIMessage->pSEIData = NULL; + return 0; + } + memcpy(m_pCurrSEIMessage->pSEIMessage, pSEIMessageInfo->pSEIMessage, sizeof(CUSEIMESSAGE) * seiNumMessages); + m_pCurrSEIMessage->sei_message_count = pSEIMessageInfo->sei_message_count; + m_SEIMessagesDisplayOrder[pSEIMessageInfo->picIdx] = *m_pCurrSEIMessage; + return 1; +} + +NvDecoder::NvDecoder(CUcontext cuContext, bool bUseDeviceFrame, cudaVideoCodec eCodec, bool bLowLatency, + bool bDeviceFramePitched, const Rect *pCropRect, const Dim *pResizeDim, bool extract_user_SEI_Message, + int maxWidth, int maxHeight, unsigned int clkRate, bool force_zero_latency) : + m_cuContext(cuContext), m_bUseDeviceFrame(bUseDeviceFrame), m_eCodec(eCodec), m_bDeviceFramePitched(bDeviceFramePitched), + m_bExtractSEIMessage(extract_user_SEI_Message), m_nMaxWidth (maxWidth), m_nMaxHeight(maxHeight), + m_bForce_zero_latency(force_zero_latency) +{ + if (pCropRect) m_cropRect = *pCropRect; + if (pResizeDim) m_resizeDim = *pResizeDim; + + NVDEC_API_CALL(cuvidCtxLockCreate(&m_ctxLock, cuContext)); + + ck(cuStreamCreate(&m_cuvidStream, CU_STREAM_DEFAULT)); + + decoderSessionID = 0; + + if (m_bExtractSEIMessage) + { + m_fpSEI = fopen("sei_message.txt", "wb"); + m_pCurrSEIMessage = new CUVIDSEIMESSAGEINFO; + memset(&m_SEIMessagesDisplayOrder, 0, sizeof(m_SEIMessagesDisplayOrder)); + } + CUVIDPARSERPARAMS videoParserParameters = {}; + videoParserParameters.CodecType = eCodec; + videoParserParameters.ulMaxNumDecodeSurfaces = 1; + videoParserParameters.ulClockRate = clkRate; + videoParserParameters.ulMaxDisplayDelay = bLowLatency ? 0 : 1; + videoParserParameters.pUserData = this; + videoParserParameters.pfnSequenceCallback = HandleVideoSequenceProc; + videoParserParameters.pfnDecodePicture = HandlePictureDecodeProc; + videoParserParameters.pfnDisplayPicture = m_bForce_zero_latency ? NULL : HandlePictureDisplayProc; + videoParserParameters.pfnGetOperatingPoint = HandleOperatingPointProc; + videoParserParameters.pfnGetSEIMsg = m_bExtractSEIMessage ? HandleSEIMessagesProc : NULL; + NVDEC_API_CALL(cuvidCreateVideoParser(&m_hParser, &videoParserParameters)); +} + +NvDecoder::~NvDecoder() { + + START_TIMER + + if (m_pCurrSEIMessage) { + delete m_pCurrSEIMessage; + m_pCurrSEIMessage = NULL; + } + + if (m_fpSEI) { + fclose(m_fpSEI); + m_fpSEI = NULL; + } + + if (m_hParser) { + cuvidDestroyVideoParser(m_hParser); + } + cuCtxPushCurrent(m_cuContext); + if (m_hDecoder) { + cuvidDestroyDecoder(m_hDecoder); + } + + std::lock_guard lock(m_mtxVPFrame); + + for (uint8_t *pFrame : m_vpFrame) + { + if (m_bUseDeviceFrame) + { + cuMemFree((CUdeviceptr)pFrame); + } + else + { + delete[] pFrame; + } + } + cuCtxPopCurrent(NULL); + + cuvidCtxLockDestroy(m_ctxLock); + + STOP_TIMER("Session Deinitialization Time: "); + + NvDecoder::addDecoderSessionOverHead(getDecoderSessionID(), elapsedTime); +} + +int NvDecoder::Decode(const uint8_t *pData, int nSize, int nFlags, int64_t nTimestamp) +{ + m_nDecodedFrame = 0; + m_nDecodedFrameReturned = 0; + CUVIDSOURCEDATAPACKET packet = { 0 }; + packet.payload = pData; + packet.payload_size = nSize; + packet.flags = nFlags | CUVID_PKT_TIMESTAMP; + packet.timestamp = nTimestamp; + if (!pData || nSize == 0) { + packet.flags |= CUVID_PKT_ENDOFSTREAM; + } + NVDEC_API_CALL(cuvidParseVideoData(m_hParser, &packet)); + + return m_nDecodedFrame; +} + +uint8_t* NvDecoder::GetFrame(int64_t* pTimestamp) +{ + if (m_nDecodedFrame > 0) + { + std::lock_guard lock(m_mtxVPFrame); + m_nDecodedFrame--; + if (pTimestamp) + *pTimestamp = m_vTimestamp[m_nDecodedFrameReturned]; + return m_vpFrame[m_nDecodedFrameReturned++]; + } + + return NULL; +} + +uint8_t* NvDecoder::GetLockedFrame(int64_t* pTimestamp) +{ + uint8_t *pFrame; + uint64_t timestamp; + if (m_nDecodedFrame > 0) { + std::lock_guard lock(m_mtxVPFrame); + m_nDecodedFrame--; + pFrame = m_vpFrame[0]; + m_vpFrame.erase(m_vpFrame.begin(), m_vpFrame.begin() + 1); + + timestamp = m_vTimestamp[0]; + m_vTimestamp.erase(m_vTimestamp.begin(), m_vTimestamp.begin() + 1); + + if (pTimestamp) + *pTimestamp = timestamp; + + return pFrame; + } + + return NULL; +} + +void NvDecoder::UnlockFrame(uint8_t **pFrame) +{ + std::lock_guard lock(m_mtxVPFrame); + m_vpFrame.insert(m_vpFrame.end(), &pFrame[0], &pFrame[1]); + + // add a dummy entry for timestamp + uint64_t timestamp[2] = {0}; + m_vTimestamp.insert(m_vTimestamp.end(), ×tamp[0], ×tamp[1]); +} diff --git a/webrtc-sys/src/nvidia/NvCodec/NvCodec/NvDecoder/NvDecoder.h b/webrtc-sys/src/nvidia/NvCodec/NvCodec/NvDecoder/NvDecoder.h new file mode 100644 index 000000000..34fd042f5 --- /dev/null +++ b/webrtc-sys/src/nvidia/NvCodec/NvCodec/NvDecoder/NvDecoder.h @@ -0,0 +1,362 @@ +/* +* Copyright 2017-2022 NVIDIA Corporation. All rights reserved. +* +* Please refer to the NVIDIA end user license agreement (EULA) associated +* with this source code for terms and conditions that govern your use of +* this software. Any use, reproduction, disclosure, or distribution of +* this software and related documentation outside the terms of the EULA +* is strictly prohibited. +* +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nvcuvid.h" +#include "Utils/NvCodecUtils.h" + +#define MAX_FRM_CNT 32 + +typedef enum{ + SEI_TYPE_TIME_CODE = 136, + SEI_TYPE_USER_DATA_UNREGISTERED = 5 +}SEI_H264_HEVC_PAYLOAD_TYPE; + +/** +* @brief Exception class for error reporting from the decode API. +*/ +class NVDECException : public std::exception +{ +public: + NVDECException(const std::string& errorStr, const CUresult errorCode) + : m_errorString(errorStr), m_errorCode(errorCode) {} + + virtual ~NVDECException() throw() {} + virtual const char* what() const throw() { return m_errorString.c_str(); } + CUresult getErrorCode() const { return m_errorCode; } + const std::string& getErrorString() const { return m_errorString; } + static NVDECException makeNVDECException(const std::string& errorStr, const CUresult errorCode, + const std::string& functionName, const std::string& fileName, int lineNo); +private: + std::string m_errorString; + CUresult m_errorCode; +}; + +inline NVDECException NVDECException::makeNVDECException(const std::string& errorStr, const CUresult errorCode, const std::string& functionName, + const std::string& fileName, int lineNo) +{ + std::ostringstream errorLog; + errorLog << functionName << " : " << errorStr << " at " << fileName << ":" << lineNo << std::endl; + NVDECException exception(errorLog.str(), errorCode); + return exception; +} + +#define NVDEC_THROW_ERROR( errorStr, errorCode ) \ + do \ + { \ + throw NVDECException::makeNVDECException(errorStr, errorCode, __FUNCTION__, __FILE__, __LINE__); \ + } while (0) + + +#define NVDEC_API_CALL( cuvidAPI ) \ + do \ + { \ + CUresult errorCode = cuvidAPI; \ + if( errorCode != CUDA_SUCCESS) \ + { \ + std::ostringstream errorLog; \ + errorLog << #cuvidAPI << " returned error " << errorCode; \ + throw NVDECException::makeNVDECException(errorLog.str(), errorCode, __FUNCTION__, __FILE__, __LINE__); \ + } \ + } while (0) + +struct Rect { + int l, t, r, b; +}; + +struct Dim { + int w, h; +}; + +/** +* @brief Base class for decoder interface. +*/ +class NvDecoder { + +public: + /** + * @brief This function is used to initialize the decoder session. + * Application must call this function to initialize the decoder, before + * starting to decode any frames. + */ + NvDecoder(CUcontext cuContext, bool bUseDeviceFrame, cudaVideoCodec eCodec, bool bLowLatency = false, + bool bDeviceFramePitched = false, const Rect *pCropRect = NULL, const Dim *pResizeDim = NULL, + bool extract_user_SEI_Message = false, int maxWidth = 0, int maxHeight = 0, unsigned int clkRate = 1000, + bool force_zero_latency = false); + ~NvDecoder(); + + /** + * @brief This function is used to get the current CUDA context. + */ + CUcontext GetContext() { return m_cuContext; } + + /** + * @brief This function is used to get the output frame width. + * NV12/P016 output format width is 2 byte aligned because of U and V interleave + */ + int GetWidth() { assert(m_nWidth); return (m_eOutputFormat == cudaVideoSurfaceFormat_NV12 || m_eOutputFormat == cudaVideoSurfaceFormat_P016) + ? (m_nWidth + 1) & ~1 : m_nWidth; } + + /** + * @brief This function is used to get the actual decode width + */ + int GetDecodeWidth() { assert(m_nWidth); return m_nWidth; } + + /** + * @brief This function is used to get the output frame height (Luma height). + */ + int GetHeight() { assert(m_nLumaHeight); return m_nLumaHeight; } + + /** + * @brief This function is used to get the current chroma height. + */ + int GetChromaHeight() { assert(m_nChromaHeight); return m_nChromaHeight; } + + /** + * @brief This function is used to get the number of chroma planes. + */ + int GetNumChromaPlanes() { assert(m_nNumChromaPlanes); return m_nNumChromaPlanes; } + + /** + * @brief This function is used to get the current frame size based on pixel format. + */ + int GetFrameSize() { assert(m_nWidth); return GetWidth() * (m_nLumaHeight + (m_nChromaHeight * m_nNumChromaPlanes)) * m_nBPP; } + + /** + * @brief This function is used to get the current frame Luma plane size. + */ + int GetLumaPlaneSize() { assert(m_nWidth); return GetWidth() * m_nLumaHeight * m_nBPP; } + + /** + * @brief This function is used to get the current frame chroma plane size. + */ + int GetChromaPlaneSize() { assert(m_nWidth); return GetWidth() * (m_nChromaHeight * m_nNumChromaPlanes) * m_nBPP; } + + /** + * @brief This function is used to get the pitch of the device buffer holding the decoded frame. + */ + int GetDeviceFramePitch() { assert(m_nWidth); return m_nDeviceFramePitch ? (int)m_nDeviceFramePitch : GetWidth() * m_nBPP; } + + /** + * @brief This function is used to get the bit depth associated with the pixel format. + */ + int GetBitDepth() { assert(m_nWidth); return m_nBitDepthMinus8 + 8; } + + /** + * @brief This function is used to get the bytes used per pixel. + */ + int GetBPP() { assert(m_nWidth); return m_nBPP; } + + /** + * @brief This function is used to get the YUV chroma format + */ + cudaVideoSurfaceFormat GetOutputFormat() { return m_eOutputFormat; } + + /** + * @brief This function is used to get information about the video stream (codec, display parameters etc) + */ + CUVIDEOFORMAT GetVideoFormatInfo() { assert(m_nWidth); return m_videoFormat; } + + /** + * @brief This function is used to get codec string from codec id + */ + const char *GetCodecString(cudaVideoCodec eCodec); + + /** + * @brief This function is used to print information about the video stream + */ + std::string GetVideoInfo() const { return m_videoInfo.str(); } + + /** + * @brief This function decodes a frame and returns the number of frames that are available for + * display. All frames that are available for display should be read before making a subsequent decode call. + * @param pData - pointer to the data buffer that is to be decoded + * @param nSize - size of the data buffer in bytes + * @param nFlags - CUvideopacketflags for setting decode options + * @param nTimestamp - presentation timestamp + */ + int Decode(const uint8_t *pData, int nSize, int nFlags = 0, int64_t nTimestamp = 0); + + /** + * @brief This function returns a decoded frame and timestamp. This function should be called in a loop for + * fetching all the frames that are available for display. + */ + uint8_t* GetFrame(int64_t* pTimestamp = nullptr); + + + /** + * @brief This function decodes a frame and returns the locked frame buffers + * This makes the buffers available for use by the application without the buffers + * getting overwritten, even if subsequent decode calls are made. The frame buffers + * remain locked, until UnlockFrame() is called + */ + uint8_t* GetLockedFrame(int64_t* pTimestamp = nullptr); + + /** + * @brief This function unlocks the frame buffer and makes the frame buffers available for write again + * @param ppFrame - pointer to array of frames that are to be unlocked + * @param nFrame - number of frames to be unlocked + */ + void UnlockFrame(uint8_t **pFrame); + + /** + * @brief This function allows app to set decoder reconfig params + * @param pCropRect - cropping rectangle coordinates + * @param pResizeDim - width and height of resized output + */ + int setReconfigParams(const Rect * pCropRect, const Dim * pResizeDim); + + /** + * @brief This function allows app to set operating point for AV1 SVC clips + * @param opPoint - operating point of an AV1 scalable bitstream + * @param bDispAllLayers - Output all decoded frames of an AV1 scalable bitstream + */ + void SetOperatingPoint(const uint32_t opPoint, const bool bDispAllLayers) { m_nOperatingPoint = opPoint; m_bDispAllLayers = bDispAllLayers; } + + // start a timer + void startTimer() { m_stDecode_time.Start(); } + + // stop the timer + double stopTimer() { return m_stDecode_time.Stop(); } + + void setDecoderSessionID(int sessionID) { decoderSessionID = sessionID; } + int getDecoderSessionID() { return decoderSessionID; } + + // Session overhead refers to decoder initialization and deinitialization time + static void addDecoderSessionOverHead(int sessionID, int64_t duration) { sessionOverHead[sessionID] += duration; } + static int64_t getDecoderSessionOverHead(int sessionID) { return sessionOverHead[sessionID]; } + +private: + int decoderSessionID; // Decoder session identifier. Used to gather session level stats. + static std::map sessionOverHead; // Records session overhead of initialization+deinitialization time. Format is (thread id, duration) + + /** + * @brief Callback function to be registered for getting a callback when decoding of sequence starts + */ + static int CUDAAPI HandleVideoSequenceProc(void *pUserData, CUVIDEOFORMAT *pVideoFormat) { return ((NvDecoder *)pUserData)->HandleVideoSequence(pVideoFormat); } + + /** + * @brief Callback function to be registered for getting a callback when a decoded frame is ready to be decoded + */ + static int CUDAAPI HandlePictureDecodeProc(void *pUserData, CUVIDPICPARAMS *pPicParams) { return ((NvDecoder *)pUserData)->HandlePictureDecode(pPicParams); } + + /** + * @brief Callback function to be registered for getting a callback when a decoded frame is available for display + */ + static int CUDAAPI HandlePictureDisplayProc(void *pUserData, CUVIDPARSERDISPINFO *pDispInfo) { return ((NvDecoder *)pUserData)->HandlePictureDisplay(pDispInfo); } + + /** + * @brief Callback function to be registered for getting a callback to get operating point when AV1 SVC sequence header start. + */ + static int CUDAAPI HandleOperatingPointProc(void *pUserData, CUVIDOPERATINGPOINTINFO *pOPInfo) { return ((NvDecoder *)pUserData)->GetOperatingPoint(pOPInfo); } + + /** + * @brief Callback function to be registered for getting a callback when all the unregistered user SEI Messages are parsed for a frame. + */ + static int CUDAAPI HandleSEIMessagesProc(void *pUserData, CUVIDSEIMESSAGEINFO *pSEIMessageInfo) { return ((NvDecoder *)pUserData)->GetSEIMessage(pSEIMessageInfo); } + + /** + * @brief This function gets called when a sequence is ready to be decoded. The function also gets called + when there is format change + */ + int HandleVideoSequence(CUVIDEOFORMAT *pVideoFormat); + + /** + * @brief This function gets called when a picture is ready to be decoded. cuvidDecodePicture is called from this function + * to decode the picture + */ + int HandlePictureDecode(CUVIDPICPARAMS *pPicParams); + + /** + * @brief This function gets called after a picture is decoded and available for display. Frames are fetched and stored in + internal buffer + */ + int HandlePictureDisplay(CUVIDPARSERDISPINFO *pDispInfo); + + /** + * @brief This function gets called when AV1 sequence encounter more than one operating points + */ + int GetOperatingPoint(CUVIDOPERATINGPOINTINFO *pOPInfo); + + /** + * @brief This function gets called when all unregistered user SEI messages are parsed for a frame + */ + int GetSEIMessage(CUVIDSEIMESSAGEINFO *pSEIMessageInfo); + + /** + * @brief This function reconfigure decoder if there is a change in sequence params. + */ + int ReconfigureDecoder(CUVIDEOFORMAT *pVideoFormat); + +private: + CUcontext m_cuContext = NULL; + CUvideoctxlock m_ctxLock; + CUvideoparser m_hParser = NULL; + CUvideodecoder m_hDecoder = NULL; + bool m_bUseDeviceFrame; + // dimension of the output + unsigned int m_nWidth = 0, m_nLumaHeight = 0, m_nChromaHeight = 0; + unsigned int m_nNumChromaPlanes = 0; + // height of the mapped surface + int m_nSurfaceHeight = 0; + int m_nSurfaceWidth = 0; + cudaVideoCodec m_eCodec = cudaVideoCodec_NumCodecs; + cudaVideoChromaFormat m_eChromaFormat = cudaVideoChromaFormat_420; + cudaVideoSurfaceFormat m_eOutputFormat = cudaVideoSurfaceFormat_NV12; + int m_nBitDepthMinus8 = 0; + int m_nBPP = 1; + CUVIDEOFORMAT m_videoFormat = {}; + Rect m_displayRect = {}; + // stock of frames + std::vector m_vpFrame; + // timestamps of decoded frames + std::vector m_vTimestamp; + int m_nDecodedFrame = 0, m_nDecodedFrameReturned = 0; + int m_nDecodePicCnt = 0, m_nPicNumInDecodeOrder[MAX_FRM_CNT]; + CUVIDSEIMESSAGEINFO *m_pCurrSEIMessage = NULL; + CUVIDSEIMESSAGEINFO m_SEIMessagesDisplayOrder[MAX_FRM_CNT]; + FILE *m_fpSEI = NULL; + bool m_bEndDecodeDone = false; + std::mutex m_mtxVPFrame; + int m_nFrameAlloc = 0; + CUstream m_cuvidStream = 0; + bool m_bDeviceFramePitched = false; + size_t m_nDeviceFramePitch = 0; + Rect m_cropRect = {}; + Dim m_resizeDim = {}; + + std::ostringstream m_videoInfo; + unsigned int m_nMaxWidth = 0, m_nMaxHeight = 0; + bool m_bReconfigExternal = false; + bool m_bReconfigExtPPChange = false; + StopWatch m_stDecode_time; + + unsigned int m_nOperatingPoint = 0; + bool m_bDispAllLayers = false; + // In H.264, there is an inherent display latency for video contents + // which do not have num_reorder_frames=0 in the VUI. This applies to + // All-Intra and IPPP sequences as well. If the user wants zero display + // latency for All-Intra and IPPP sequences, the below flag will enable + // the display callback immediately after the decode callback. + bool m_bForce_zero_latency = false; + bool m_bExtractSEIMessage = false; +}; diff --git a/webrtc-sys/src/nvidia/NvCodec/NvCodec/NvEncoder/NvEncoder.cpp b/webrtc-sys/src/nvidia/NvCodec/NvCodec/NvEncoder/NvEncoder.cpp new file mode 100644 index 000000000..4a530b87b --- /dev/null +++ b/webrtc-sys/src/nvidia/NvCodec/NvCodec/NvEncoder/NvEncoder.cpp @@ -0,0 +1,1076 @@ +/* + * Copyright 2017-2022 NVIDIA Corporation. All rights reserved. + * + * Please refer to the NVIDIA end user license agreement (EULA) associated + * with this source code for terms and conditions that govern your use of + * this software. Any use, reproduction, disclosure, or distribution of + * this software and related documentation outside the terms of the EULA + * is strictly prohibited. + * + */ + +#include "NvEncoder.h" + +#if defined(WIN32) +#include +#else +#include +#endif + +#ifndef _WIN32 +#include +static inline bool operator==(const GUID& guid1, const GUID& guid2) { + return !memcmp(&guid1, &guid2, sizeof(GUID)); +} + +static inline bool operator!=(const GUID& guid1, const GUID& guid2) { + return !(guid1 == guid2); +} +#endif + +NvEncoder::NvEncoder(NV_ENC_DEVICE_TYPE eDeviceType, + void* pDevice, + uint32_t nWidth, + uint32_t nHeight, + NV_ENC_BUFFER_FORMAT eBufferFormat, + uint32_t nExtraOutputDelay, + bool bMotionEstimationOnly, + bool bOutputInVideoMemory, + bool bDX12Encode, + bool bUseIVFContainer) + : m_pDevice(pDevice), + m_eDeviceType(eDeviceType), + m_nWidth(nWidth), + m_nHeight(nHeight), + m_nMaxEncodeWidth(nWidth), + m_nMaxEncodeHeight(nHeight), + m_eBufferFormat(eBufferFormat), + m_bMotionEstimationOnly(bMotionEstimationOnly), + m_bOutputInVideoMemory(bOutputInVideoMemory), + m_bIsDX12Encode(bDX12Encode), + m_bUseIVFContainer(bUseIVFContainer), + m_nExtraOutputDelay(nExtraOutputDelay), + m_hEncoder(nullptr) { + LoadNvEncApi(); + + if (!m_nvenc.nvEncOpenEncodeSession) { + m_nEncoderBuffer = 0; + NVENC_THROW_ERROR("EncodeAPI not found", NV_ENC_ERR_NO_ENCODE_DEVICE); + } + + NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS encodeSessionExParams = { + NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER}; + encodeSessionExParams.device = m_pDevice; + encodeSessionExParams.deviceType = m_eDeviceType; + encodeSessionExParams.apiVersion = NVENCAPI_VERSION; + void* hEncoder = NULL; + NVENC_API_CALL( + m_nvenc.nvEncOpenEncodeSessionEx(&encodeSessionExParams, &hEncoder)); + m_hEncoder = hEncoder; +} + +void NvEncoder::LoadNvEncApi() { +#if defined(_WIN32) +#if defined(_WIN64) + HMODULE hModule = LoadLibrary(TEXT("nvEncodeAPI64.dll")); +#else + HMODULE hModule = LoadLibrary(TEXT("nvEncodeAPI.dll")); +#endif +#else + void* hModule = dlopen("libnvidia-encode.so.1", RTLD_LAZY); +#endif + + if (hModule == NULL) { + NVENC_THROW_ERROR( + "NVENC library file is not found. Please ensure NV driver is installed", + NV_ENC_ERR_NO_ENCODE_DEVICE); + } + + m_hModule = hModule; + + typedef NVENCSTATUS(NVENCAPI * + NvEncodeAPIGetMaxSupportedVersion_Type)(uint32_t*); +#if defined(_WIN32) + NvEncodeAPIGetMaxSupportedVersion_Type NvEncodeAPIGetMaxSupportedVersion = + (NvEncodeAPIGetMaxSupportedVersion_Type)GetProcAddress( + hModule, "NvEncodeAPIGetMaxSupportedVersion"); +#else + NvEncodeAPIGetMaxSupportedVersion_Type NvEncodeAPIGetMaxSupportedVersion = + (NvEncodeAPIGetMaxSupportedVersion_Type)dlsym( + hModule, "NvEncodeAPIGetMaxSupportedVersion"); +#endif + + uint32_t version = 0; + uint32_t currentVersion = + (NVENCAPI_MAJOR_VERSION << 4) | NVENCAPI_MINOR_VERSION; + NVENC_API_CALL(NvEncodeAPIGetMaxSupportedVersion(&version)); + if (currentVersion > version) { + NVENC_THROW_ERROR( + "Current Driver Version does not support this NvEncodeAPI version, " + "please upgrade driver", + NV_ENC_ERR_INVALID_VERSION); + } + + typedef NVENCSTATUS(NVENCAPI * NvEncodeAPICreateInstance_Type)( + NV_ENCODE_API_FUNCTION_LIST*); +#if defined(_WIN32) + NvEncodeAPICreateInstance_Type NvEncodeAPICreateInstance = + (NvEncodeAPICreateInstance_Type)GetProcAddress( + hModule, "NvEncodeAPICreateInstance"); +#else + NvEncodeAPICreateInstance_Type NvEncodeAPICreateInstance = + (NvEncodeAPICreateInstance_Type)dlsym(hModule, + "NvEncodeAPICreateInstance"); +#endif + + if (!NvEncodeAPICreateInstance) { + NVENC_THROW_ERROR( + "Cannot find NvEncodeAPICreateInstance() entry in NVENC library", + NV_ENC_ERR_NO_ENCODE_DEVICE); + } + + m_nvenc = {NV_ENCODE_API_FUNCTION_LIST_VER}; + NVENC_API_CALL(NvEncodeAPICreateInstance(&m_nvenc)); +} + +NvEncoder::~NvEncoder() { + DestroyHWEncoder(); + if (m_hModule) { +#if defined(_WIN32) + FreeLibrary((HMODULE)m_hModule); +#else + dlclose(m_hModule); +#endif + m_hModule = nullptr; + } +} + +void NvEncoder::CreateDefaultEncoderParams( + NV_ENC_INITIALIZE_PARAMS* pIntializeParams, + GUID codecGuid, + GUID presetGuid, + NV_ENC_TUNING_INFO tuningInfo) { + if (!m_hEncoder) { + NVENC_THROW_ERROR("Encoder Initialization failed", + NV_ENC_ERR_NO_ENCODE_DEVICE); + return; + } + + if (pIntializeParams == nullptr || + pIntializeParams->encodeConfig == nullptr) { + NVENC_THROW_ERROR( + "pInitializeParams and pInitializeParams->encodeConfig can't be NULL", + NV_ENC_ERR_INVALID_PTR); + } + + memset(pIntializeParams->encodeConfig, 0, sizeof(NV_ENC_CONFIG)); + auto pEncodeConfig = pIntializeParams->encodeConfig; + memset(pIntializeParams, 0, sizeof(NV_ENC_INITIALIZE_PARAMS)); + pIntializeParams->encodeConfig = pEncodeConfig; + + pIntializeParams->encodeConfig->version = NV_ENC_CONFIG_VER; + pIntializeParams->version = NV_ENC_INITIALIZE_PARAMS_VER; + + pIntializeParams->encodeGUID = codecGuid; + pIntializeParams->presetGUID = presetGuid; + pIntializeParams->encodeWidth = m_nWidth; + pIntializeParams->encodeHeight = m_nHeight; + pIntializeParams->darWidth = m_nWidth; + pIntializeParams->darHeight = m_nHeight; + pIntializeParams->frameRateNum = 30; + pIntializeParams->frameRateDen = 1; + pIntializeParams->enablePTD = 1; + pIntializeParams->reportSliceOffsets = 0; + pIntializeParams->enableSubFrameWrite = 0; + pIntializeParams->maxEncodeWidth = m_nWidth; + pIntializeParams->maxEncodeHeight = m_nHeight; + pIntializeParams->enableMEOnlyMode = m_bMotionEstimationOnly; + pIntializeParams->enableOutputInVidmem = m_bOutputInVideoMemory; +#if defined(_WIN32) + if (!m_bOutputInVideoMemory) { + pIntializeParams->enableEncodeAsync = + GetCapabilityValue(codecGuid, NV_ENC_CAPS_ASYNC_ENCODE_SUPPORT); + } +#endif + + NV_ENC_PRESET_CONFIG presetConfig = {NV_ENC_PRESET_CONFIG_VER, + {NV_ENC_CONFIG_VER}}; + m_nvenc.nvEncGetEncodePresetConfig(m_hEncoder, codecGuid, presetGuid, + &presetConfig); + memcpy(pIntializeParams->encodeConfig, &presetConfig.presetCfg, + sizeof(NV_ENC_CONFIG)); + pIntializeParams->encodeConfig->frameIntervalP = 1; + pIntializeParams->encodeConfig->gopLength = NVENC_INFINITE_GOPLENGTH; + + pIntializeParams->encodeConfig->rcParams.rateControlMode = + NV_ENC_PARAMS_RC_CONSTQP; + + if (!m_bMotionEstimationOnly) { + pIntializeParams->tuningInfo = tuningInfo; + NV_ENC_PRESET_CONFIG presetConfig = {NV_ENC_PRESET_CONFIG_VER, + {NV_ENC_CONFIG_VER}}; + m_nvenc.nvEncGetEncodePresetConfigEx(m_hEncoder, codecGuid, presetGuid, + tuningInfo, &presetConfig); + memcpy(pIntializeParams->encodeConfig, &presetConfig.presetCfg, + sizeof(NV_ENC_CONFIG)); + } else { + m_encodeConfig.version = NV_ENC_CONFIG_VER; + m_encodeConfig.rcParams.rateControlMode = NV_ENC_PARAMS_RC_CONSTQP; + m_encodeConfig.rcParams.constQP = {28, 31, 25}; + } + + if (pIntializeParams->encodeGUID == NV_ENC_CODEC_H264_GUID) { + if (m_eBufferFormat == NV_ENC_BUFFER_FORMAT_YUV444 || + m_eBufferFormat == NV_ENC_BUFFER_FORMAT_YUV444_10BIT) { + pIntializeParams->encodeConfig->encodeCodecConfig.h264Config + .chromaFormatIDC = 3; + } + pIntializeParams->encodeConfig->encodeCodecConfig.h264Config.idrPeriod = + pIntializeParams->encodeConfig->gopLength; + } else if (pIntializeParams->encodeGUID == NV_ENC_CODEC_HEVC_GUID) { + pIntializeParams->encodeConfig->encodeCodecConfig.hevcConfig + .pixelBitDepthMinus8 = + (m_eBufferFormat == NV_ENC_BUFFER_FORMAT_YUV420_10BIT || + m_eBufferFormat == NV_ENC_BUFFER_FORMAT_YUV444_10BIT) + ? 2 + : 0; + if (m_eBufferFormat == NV_ENC_BUFFER_FORMAT_YUV444 || + m_eBufferFormat == NV_ENC_BUFFER_FORMAT_YUV444_10BIT) { + pIntializeParams->encodeConfig->encodeCodecConfig.hevcConfig + .chromaFormatIDC = 3; + } + pIntializeParams->encodeConfig->encodeCodecConfig.hevcConfig.idrPeriod = + pIntializeParams->encodeConfig->gopLength; + } else if (pIntializeParams->encodeGUID == NV_ENC_CODEC_AV1_GUID) { + pIntializeParams->encodeConfig->encodeCodecConfig.av1Config + .pixelBitDepthMinus8 = + (m_eBufferFormat == NV_ENC_BUFFER_FORMAT_YUV420_10BIT) ? 2 : 0; + pIntializeParams->encodeConfig->encodeCodecConfig.av1Config + .inputPixelBitDepthMinus8 = + (m_eBufferFormat == NV_ENC_BUFFER_FORMAT_YUV420_10BIT) ? 2 : 0; + pIntializeParams->encodeConfig->encodeCodecConfig.av1Config + .chromaFormatIDC = 1; + pIntializeParams->encodeConfig->encodeCodecConfig.av1Config.idrPeriod = + pIntializeParams->encodeConfig->gopLength; + if (m_bOutputInVideoMemory) { + pIntializeParams->encodeConfig->frameIntervalP = 1; + } + } + + if (m_bIsDX12Encode) { + pIntializeParams->bufferFormat = m_eBufferFormat; + } + + return; +} + +void NvEncoder::CreateEncoder(const NV_ENC_INITIALIZE_PARAMS* pEncoderParams) { + if (!m_hEncoder) { + NVENC_THROW_ERROR("Encoder Initialization failed", + NV_ENC_ERR_NO_ENCODE_DEVICE); + } + + if (!pEncoderParams) { + NVENC_THROW_ERROR("Invalid NV_ENC_INITIALIZE_PARAMS ptr", + NV_ENC_ERR_INVALID_PTR); + } + + if (pEncoderParams->encodeWidth == 0 || pEncoderParams->encodeHeight == 0) { + NVENC_THROW_ERROR("Invalid encoder width and height", + NV_ENC_ERR_INVALID_PARAM); + } + + if (pEncoderParams->encodeGUID != NV_ENC_CODEC_H264_GUID && + pEncoderParams->encodeGUID != NV_ENC_CODEC_HEVC_GUID && + pEncoderParams->encodeGUID != NV_ENC_CODEC_AV1_GUID) { + NVENC_THROW_ERROR("Invalid codec guid", NV_ENC_ERR_INVALID_PARAM); + } + + if (pEncoderParams->encodeGUID == NV_ENC_CODEC_H264_GUID) { + if (m_eBufferFormat == NV_ENC_BUFFER_FORMAT_YUV420_10BIT || + m_eBufferFormat == NV_ENC_BUFFER_FORMAT_YUV444_10BIT) { + NVENC_THROW_ERROR("10-bit format isn't supported by H264 encoder", + NV_ENC_ERR_INVALID_PARAM); + } + } + + if (pEncoderParams->encodeGUID == NV_ENC_CODEC_AV1_GUID) { + if (m_eBufferFormat == NV_ENC_BUFFER_FORMAT_YUV444 || + m_eBufferFormat == NV_ENC_BUFFER_FORMAT_YUV444_10BIT) { + NVENC_THROW_ERROR("YUV444 format isn't supported by AV1 encoder", + NV_ENC_ERR_INVALID_PARAM); + } + } + + // set other necessary params if not set yet + if (pEncoderParams->encodeGUID == NV_ENC_CODEC_H264_GUID) { + if ((m_eBufferFormat == NV_ENC_BUFFER_FORMAT_YUV444) && + (pEncoderParams->encodeConfig->encodeCodecConfig.h264Config + .chromaFormatIDC != 3)) { + NVENC_THROW_ERROR("Invalid ChromaFormatIDC", NV_ENC_ERR_INVALID_PARAM); + } + } + + if (pEncoderParams->encodeGUID == NV_ENC_CODEC_HEVC_GUID) { + bool yuv10BitFormat = + (m_eBufferFormat == NV_ENC_BUFFER_FORMAT_YUV420_10BIT || + m_eBufferFormat == NV_ENC_BUFFER_FORMAT_YUV444_10BIT) + ? true + : false; + if (yuv10BitFormat && pEncoderParams->encodeConfig->encodeCodecConfig + .hevcConfig.pixelBitDepthMinus8 != 2) { + NVENC_THROW_ERROR("Invalid PixelBitdepth", NV_ENC_ERR_INVALID_PARAM); + } + + if ((m_eBufferFormat == NV_ENC_BUFFER_FORMAT_YUV444 || + m_eBufferFormat == NV_ENC_BUFFER_FORMAT_YUV444_10BIT) && + (pEncoderParams->encodeConfig->encodeCodecConfig.hevcConfig + .chromaFormatIDC != 3)) { + NVENC_THROW_ERROR("Invalid ChromaFormatIDC", NV_ENC_ERR_INVALID_PARAM); + } + } + + if (pEncoderParams->encodeGUID == NV_ENC_CODEC_AV1_GUID) { + bool yuv10BitFormat = + (m_eBufferFormat == NV_ENC_BUFFER_FORMAT_YUV420_10BIT) ? true : false; + if (yuv10BitFormat && pEncoderParams->encodeConfig->encodeCodecConfig + .av1Config.pixelBitDepthMinus8 != 2) { + NVENC_THROW_ERROR("Invalid PixelBitdepth", NV_ENC_ERR_INVALID_PARAM); + } + + if (pEncoderParams->encodeConfig->encodeCodecConfig.av1Config + .chromaFormatIDC != 1) { + NVENC_THROW_ERROR("Invalid ChromaFormatIDC", NV_ENC_ERR_INVALID_PARAM); + } + + if (m_bOutputInVideoMemory && + pEncoderParams->encodeConfig->frameIntervalP > 1) { + NVENC_THROW_ERROR( + "Alt Ref frames not supported for AV1 in case of OutputInVideoMemory", + NV_ENC_ERR_INVALID_PARAM); + } + } + + memcpy(&m_initializeParams, pEncoderParams, sizeof(m_initializeParams)); + m_initializeParams.version = NV_ENC_INITIALIZE_PARAMS_VER; + + if (pEncoderParams->encodeConfig) { + memcpy(&m_encodeConfig, pEncoderParams->encodeConfig, + sizeof(m_encodeConfig)); + m_encodeConfig.version = NV_ENC_CONFIG_VER; + } else { + NV_ENC_PRESET_CONFIG presetConfig = {NV_ENC_PRESET_CONFIG_VER, + {NV_ENC_CONFIG_VER}}; + if (!m_bMotionEstimationOnly) { + m_nvenc.nvEncGetEncodePresetConfigEx( + m_hEncoder, pEncoderParams->encodeGUID, pEncoderParams->presetGUID, + pEncoderParams->tuningInfo, &presetConfig); + memcpy(&m_encodeConfig, &presetConfig.presetCfg, sizeof(NV_ENC_CONFIG)); + if (m_bOutputInVideoMemory && + pEncoderParams->encodeGUID == NV_ENC_CODEC_AV1_GUID) { + m_encodeConfig.frameIntervalP = 1; + } + } else { + m_encodeConfig.version = NV_ENC_CONFIG_VER; + m_encodeConfig.rcParams.rateControlMode = NV_ENC_PARAMS_RC_CONSTQP; + m_encodeConfig.rcParams.constQP = {28, 31, 25}; + } + } + m_initializeParams.encodeConfig = &m_encodeConfig; + m_initializeParams.bufferFormat = m_eBufferFormat; + + try { + NVENC_API_CALL( + m_nvenc.nvEncInitializeEncoder(m_hEncoder, &m_initializeParams)); + } catch (const NVENCException& e) { + DestroyHWEncoder(); + std::cout << "nvEncInitializeEncoder API failed" << e.getErrorCode() + << " - " << e.getErrorString() << std::endl; + } + + m_bEncoderInitialized = true; + m_nWidth = m_initializeParams.encodeWidth; + m_nHeight = m_initializeParams.encodeHeight; + m_nMaxEncodeWidth = m_initializeParams.maxEncodeWidth; + m_nMaxEncodeHeight = m_initializeParams.maxEncodeHeight; + + m_nEncoderBuffer = m_encodeConfig.frameIntervalP + + m_encodeConfig.rcParams.lookaheadDepth + + m_nExtraOutputDelay; + m_nOutputDelay = m_nEncoderBuffer - 1; + + if (!m_bOutputInVideoMemory) { + m_vpCompletionEvent.resize(m_nEncoderBuffer, nullptr); + } + +#if defined(_WIN32) + for (uint32_t i = 0; i < m_vpCompletionEvent.size(); i++) { + m_vpCompletionEvent[i] = CreateEvent(NULL, FALSE, FALSE, NULL); + if (!m_bIsDX12Encode) { + NV_ENC_EVENT_PARAMS eventParams = {NV_ENC_EVENT_PARAMS_VER}; + eventParams.completionEvent = m_vpCompletionEvent[i]; + m_nvenc.nvEncRegisterAsyncEvent(m_hEncoder, &eventParams); + } + } +#endif + + m_vMappedInputBuffers.resize(m_nEncoderBuffer, nullptr); + + if (m_bMotionEstimationOnly) { + m_vMappedRefBuffers.resize(m_nEncoderBuffer, nullptr); + + if (!m_bOutputInVideoMemory) { + InitializeMVOutputBuffer(); + } + } else { + if (!m_bOutputInVideoMemory && !m_bIsDX12Encode) { + m_vBitstreamOutputBuffer.resize(m_nEncoderBuffer, nullptr); + InitializeBitstreamBuffer(); + } + } + + AllocateInputBuffers(m_nEncoderBuffer); +} + +void NvEncoder::DestroyEncoder() { + if (!m_hEncoder) { + return; + } + + ReleaseInputBuffers(); + + DestroyHWEncoder(); +} + +void NvEncoder::DestroyHWEncoder() { + if (!m_hEncoder) { + return; + } + +#if defined(_WIN32) + for (uint32_t i = 0; i < m_vpCompletionEvent.size(); i++) { + if (m_vpCompletionEvent[i]) { + if (!m_bIsDX12Encode) { + NV_ENC_EVENT_PARAMS eventParams = {NV_ENC_EVENT_PARAMS_VER}; + eventParams.completionEvent = m_vpCompletionEvent[i]; + m_nvenc.nvEncUnregisterAsyncEvent(m_hEncoder, &eventParams); + } + CloseHandle(m_vpCompletionEvent[i]); + } + } + m_vpCompletionEvent.clear(); +#endif + + if (m_bMotionEstimationOnly) { + DestroyMVOutputBuffer(); + } else { + if (!m_bIsDX12Encode) + DestroyBitstreamBuffer(); + } + + m_nvenc.nvEncDestroyEncoder(m_hEncoder); + + m_hEncoder = nullptr; + + m_bEncoderInitialized = false; +} + +const NvEncInputFrame* NvEncoder::GetNextInputFrame() { + int i = m_iToSend % m_nEncoderBuffer; + return &m_vInputFrames[i]; +} + +const NvEncInputFrame* NvEncoder::GetNextReferenceFrame() { + int i = m_iToSend % m_nEncoderBuffer; + return &m_vReferenceFrames[i]; +} + +void NvEncoder::MapResources(uint32_t bfrIdx) { + NV_ENC_MAP_INPUT_RESOURCE mapInputResource = {NV_ENC_MAP_INPUT_RESOURCE_VER}; + + mapInputResource.registeredResource = m_vRegisteredResources[bfrIdx]; + NVENC_API_CALL(m_nvenc.nvEncMapInputResource(m_hEncoder, &mapInputResource)); + m_vMappedInputBuffers[bfrIdx] = mapInputResource.mappedResource; + + if (m_bMotionEstimationOnly) { + mapInputResource.registeredResource = + m_vRegisteredResourcesForReference[bfrIdx]; + NVENC_API_CALL( + m_nvenc.nvEncMapInputResource(m_hEncoder, &mapInputResource)); + m_vMappedRefBuffers[bfrIdx] = mapInputResource.mappedResource; + } +} + +void NvEncoder::EncodeFrame(std::vector>& vPacket, + NV_ENC_PIC_PARAMS* pPicParams) { + vPacket.clear(); + if (!IsHWEncoderInitialized()) { + NVENC_THROW_ERROR("Encoder device not found", NV_ENC_ERR_NO_ENCODE_DEVICE); + } + + int bfrIdx = m_iToSend % m_nEncoderBuffer; + + MapResources(bfrIdx); + + NVENCSTATUS nvStatus = DoEncode(m_vMappedInputBuffers[bfrIdx], + m_vBitstreamOutputBuffer[bfrIdx], pPicParams); + + if (nvStatus == NV_ENC_SUCCESS || nvStatus == NV_ENC_ERR_NEED_MORE_INPUT) { + m_iToSend++; + GetEncodedPacket(m_vBitstreamOutputBuffer, vPacket, true); + } else { + NVENC_THROW_ERROR("nvEncEncodePicture API failed", nvStatus); + } +} + +void NvEncoder::RunMotionEstimation(std::vector& mvData) { + if (!m_hEncoder) { + NVENC_THROW_ERROR("Encoder Initialization failed", + NV_ENC_ERR_NO_ENCODE_DEVICE); + return; + } + + const uint32_t bfrIdx = m_iToSend % m_nEncoderBuffer; + + MapResources(bfrIdx); + + NVENCSTATUS nvStatus = DoMotionEstimation(m_vMappedInputBuffers[bfrIdx], + m_vMappedRefBuffers[bfrIdx], + m_vMVDataOutputBuffer[bfrIdx]); + + if (nvStatus == NV_ENC_SUCCESS) { + m_iToSend++; + std::vector> vPacket; + GetEncodedPacket(m_vMVDataOutputBuffer, vPacket, true); + if (vPacket.size() != 1) { + NVENC_THROW_ERROR( + "GetEncodedPacket() doesn't return one (and only one) MVData", + NV_ENC_ERR_GENERIC); + } + mvData = vPacket[0]; + } else { + NVENC_THROW_ERROR("nvEncEncodePicture API failed", nvStatus); + } +} + +void NvEncoder::GetSequenceParams(std::vector& seqParams) { + uint8_t spsppsData[1024]; // Assume maximum spspps data is 1KB or less + memset(spsppsData, 0, sizeof(spsppsData)); + NV_ENC_SEQUENCE_PARAM_PAYLOAD payload = {NV_ENC_SEQUENCE_PARAM_PAYLOAD_VER}; + uint32_t spsppsSize = 0; + + payload.spsppsBuffer = spsppsData; + payload.inBufferSize = sizeof(spsppsData); + payload.outSPSPPSPayloadSize = &spsppsSize; + NVENC_API_CALL(m_nvenc.nvEncGetSequenceParams(m_hEncoder, &payload)); + seqParams.clear(); + seqParams.insert(seqParams.end(), &spsppsData[0], &spsppsData[spsppsSize]); +} + +NVENCSTATUS NvEncoder::DoEncode(NV_ENC_INPUT_PTR inputBuffer, + NV_ENC_OUTPUT_PTR outputBuffer, + NV_ENC_PIC_PARAMS* pPicParams) { + NV_ENC_PIC_PARAMS picParams = {}; + if (pPicParams) { + picParams = *pPicParams; + } + picParams.version = NV_ENC_PIC_PARAMS_VER; + picParams.pictureStruct = NV_ENC_PIC_STRUCT_FRAME; + picParams.inputBuffer = inputBuffer; + picParams.bufferFmt = GetPixelFormat(); + picParams.inputWidth = GetEncodeWidth(); + picParams.inputHeight = GetEncodeHeight(); + picParams.outputBitstream = outputBuffer; + picParams.completionEvent = GetCompletionEvent(m_iToSend % m_nEncoderBuffer); + NVENCSTATUS nvStatus = m_nvenc.nvEncEncodePicture(m_hEncoder, &picParams); + + return nvStatus; +} + +void NvEncoder::SendEOS() { + NV_ENC_PIC_PARAMS picParams = {NV_ENC_PIC_PARAMS_VER}; + picParams.encodePicFlags = NV_ENC_PIC_FLAG_EOS; + picParams.completionEvent = GetCompletionEvent(m_iToSend % m_nEncoderBuffer); + NVENC_API_CALL(m_nvenc.nvEncEncodePicture(m_hEncoder, &picParams)); +} + +void NvEncoder::EndEncode(std::vector>& vPacket) { + vPacket.clear(); + if (!IsHWEncoderInitialized()) { + NVENC_THROW_ERROR("Encoder device not initialized", + NV_ENC_ERR_ENCODER_NOT_INITIALIZED); + } + + SendEOS(); + + GetEncodedPacket(m_vBitstreamOutputBuffer, vPacket, false); +} + +void NvEncoder::GetEncodedPacket(std::vector& vOutputBuffer, + std::vector>& vPacket, + bool bOutputDelay) { + unsigned i = 0; + int iEnd = bOutputDelay ? m_iToSend - m_nOutputDelay : m_iToSend; + for (; m_iGot < iEnd; m_iGot++) { + WaitForCompletionEvent(m_iGot % m_nEncoderBuffer); + NV_ENC_LOCK_BITSTREAM lockBitstreamData = {NV_ENC_LOCK_BITSTREAM_VER}; + lockBitstreamData.outputBitstream = + vOutputBuffer[m_iGot % m_nEncoderBuffer]; + lockBitstreamData.doNotWait = false; + NVENC_API_CALL(m_nvenc.nvEncLockBitstream(m_hEncoder, &lockBitstreamData)); + + uint8_t* pData = (uint8_t*)lockBitstreamData.bitstreamBufferPtr; + if (vPacket.size() < i + 1) { + vPacket.push_back(std::vector()); + } + vPacket[i].clear(); + + if ((m_initializeParams.encodeGUID == NV_ENC_CODEC_AV1_GUID) && + (m_bUseIVFContainer)) { + if (m_bWriteIVFFileHeader) { + m_IVFUtils.WriteFileHeader(vPacket[i], MAKE_FOURCC('A', 'V', '0', '1'), + m_initializeParams.encodeWidth, + m_initializeParams.encodeHeight, + m_initializeParams.frameRateNum, + m_initializeParams.frameRateDen, 0xFFFF); + m_bWriteIVFFileHeader = false; + } + + m_IVFUtils.WriteFrameHeader(vPacket[i], + lockBitstreamData.bitstreamSizeInBytes, + lockBitstreamData.outputTimeStamp); + } + vPacket[i].insert(vPacket[i].end(), &pData[0], + &pData[lockBitstreamData.bitstreamSizeInBytes]); + + i++; + + NVENC_API_CALL(m_nvenc.nvEncUnlockBitstream( + m_hEncoder, lockBitstreamData.outputBitstream)); + + if (m_vMappedInputBuffers[m_iGot % m_nEncoderBuffer]) { + NVENC_API_CALL(m_nvenc.nvEncUnmapInputResource( + m_hEncoder, m_vMappedInputBuffers[m_iGot % m_nEncoderBuffer])); + m_vMappedInputBuffers[m_iGot % m_nEncoderBuffer] = nullptr; + } + + if (m_bMotionEstimationOnly && + m_vMappedRefBuffers[m_iGot % m_nEncoderBuffer]) { + NVENC_API_CALL(m_nvenc.nvEncUnmapInputResource( + m_hEncoder, m_vMappedRefBuffers[m_iGot % m_nEncoderBuffer])); + m_vMappedRefBuffers[m_iGot % m_nEncoderBuffer] = nullptr; + } + } +} + +bool NvEncoder::Reconfigure( + const NV_ENC_RECONFIGURE_PARAMS* pReconfigureParams) { + NVENC_API_CALL(m_nvenc.nvEncReconfigureEncoder( + m_hEncoder, const_cast(pReconfigureParams))); + + memcpy(&m_initializeParams, &(pReconfigureParams->reInitEncodeParams), + sizeof(m_initializeParams)); + if (pReconfigureParams->reInitEncodeParams.encodeConfig) { + memcpy(&m_encodeConfig, pReconfigureParams->reInitEncodeParams.encodeConfig, + sizeof(m_encodeConfig)); + } + + m_nWidth = m_initializeParams.encodeWidth; + m_nHeight = m_initializeParams.encodeHeight; + m_nMaxEncodeWidth = m_initializeParams.maxEncodeWidth; + m_nMaxEncodeHeight = m_initializeParams.maxEncodeHeight; + + return true; +} + +NV_ENC_REGISTERED_PTR NvEncoder::RegisterResource( + void* pBuffer, + NV_ENC_INPUT_RESOURCE_TYPE eResourceType, + int width, + int height, + int pitch, + NV_ENC_BUFFER_FORMAT bufferFormat, + NV_ENC_BUFFER_USAGE bufferUsage, + NV_ENC_FENCE_POINT_D3D12* pInputFencePoint) { + NV_ENC_REGISTER_RESOURCE registerResource = {NV_ENC_REGISTER_RESOURCE_VER}; + registerResource.resourceType = eResourceType; + registerResource.resourceToRegister = pBuffer; + registerResource.width = width; + registerResource.height = height; + registerResource.pitch = pitch; + registerResource.bufferFormat = bufferFormat; + registerResource.bufferUsage = bufferUsage; + registerResource.pInputFencePoint = pInputFencePoint; + NVENC_API_CALL(m_nvenc.nvEncRegisterResource(m_hEncoder, ®isterResource)); + + return registerResource.registeredResource; +} + +void NvEncoder::RegisterInputResources(std::vector inputframes, + NV_ENC_INPUT_RESOURCE_TYPE eResourceType, + int width, + int height, + int pitch, + NV_ENC_BUFFER_FORMAT bufferFormat, + bool bReferenceFrame) { + for (uint32_t i = 0; i < inputframes.size(); ++i) { + NV_ENC_REGISTERED_PTR registeredPtr = + RegisterResource(inputframes[i], eResourceType, width, height, pitch, + bufferFormat, NV_ENC_INPUT_IMAGE); + + std::vector _chromaOffsets; + NvEncoder::GetChromaSubPlaneOffsets(bufferFormat, pitch, height, + _chromaOffsets); + NvEncInputFrame inputframe = {}; + inputframe.inputPtr = (void*)inputframes[i]; + inputframe.chromaOffsets[0] = 0; + inputframe.chromaOffsets[1] = 0; + for (uint32_t ch = 0; ch < _chromaOffsets.size(); ch++) { + inputframe.chromaOffsets[ch] = _chromaOffsets[ch]; + } + inputframe.numChromaPlanes = NvEncoder::GetNumChromaPlanes(bufferFormat); + inputframe.pitch = pitch; + inputframe.chromaPitch = NvEncoder::GetChromaPitch(bufferFormat, pitch); + inputframe.bufferFormat = bufferFormat; + inputframe.resourceType = eResourceType; + + if (bReferenceFrame) { + m_vRegisteredResourcesForReference.push_back(registeredPtr); + m_vReferenceFrames.push_back(inputframe); + } else { + m_vRegisteredResources.push_back(registeredPtr); + m_vInputFrames.push_back(inputframe); + } + } +} + +void NvEncoder::FlushEncoder() { + if (!m_bMotionEstimationOnly && !m_bOutputInVideoMemory) { + // Incase of error it is possible for buffers still mapped to encoder. + // flush the encoder queue and then unmapped it if any surface is still + // mapped + try { + std::vector> vPacket; + EndEncode(vPacket); + } catch (...) { + } + } +} + +void NvEncoder::UnregisterInputResources() { + FlushEncoder(); + + if (m_bMotionEstimationOnly) { + for (uint32_t i = 0; i < m_vMappedRefBuffers.size(); ++i) { + if (m_vMappedRefBuffers[i]) { + m_nvenc.nvEncUnmapInputResource(m_hEncoder, m_vMappedRefBuffers[i]); + } + } + } + m_vMappedRefBuffers.clear(); + + for (uint32_t i = 0; i < m_vMappedInputBuffers.size(); ++i) { + if (m_vMappedInputBuffers[i]) { + m_nvenc.nvEncUnmapInputResource(m_hEncoder, m_vMappedInputBuffers[i]); + } + } + m_vMappedInputBuffers.clear(); + + for (uint32_t i = 0; i < m_vRegisteredResources.size(); ++i) { + if (m_vRegisteredResources[i]) { + m_nvenc.nvEncUnregisterResource(m_hEncoder, m_vRegisteredResources[i]); + } + } + m_vRegisteredResources.clear(); + + for (uint32_t i = 0; i < m_vRegisteredResourcesForReference.size(); ++i) { + if (m_vRegisteredResourcesForReference[i]) { + m_nvenc.nvEncUnregisterResource(m_hEncoder, + m_vRegisteredResourcesForReference[i]); + } + } + m_vRegisteredResourcesForReference.clear(); +} + +void NvEncoder::WaitForCompletionEvent(int iEvent) { +#if defined(_WIN32) + // Check if we are in async mode. If not, don't wait for event; + NV_ENC_CONFIG sEncodeConfig = {0}; + NV_ENC_INITIALIZE_PARAMS sInitializeParams = {0}; + sInitializeParams.encodeConfig = &sEncodeConfig; + GetInitializeParams(&sInitializeParams); + + if (0U == sInitializeParams.enableEncodeAsync) { + return; + } +#ifdef DEBUG + WaitForSingleObject(m_vpCompletionEvent[iEvent], INFINITE); +#else + // wait for 20s which is infinite on terms of gpu time + if (WaitForSingleObject(m_vpCompletionEvent[iEvent], 20000) == WAIT_FAILED) { + NVENC_THROW_ERROR("Failed to encode frame", NV_ENC_ERR_GENERIC); + } +#endif +#endif +} + +uint32_t NvEncoder::GetWidthInBytes(const NV_ENC_BUFFER_FORMAT bufferFormat, + const uint32_t width) { + switch (bufferFormat) { + case NV_ENC_BUFFER_FORMAT_NV12: + case NV_ENC_BUFFER_FORMAT_YV12: + case NV_ENC_BUFFER_FORMAT_IYUV: + case NV_ENC_BUFFER_FORMAT_YUV444: + return width; + case NV_ENC_BUFFER_FORMAT_YUV420_10BIT: + case NV_ENC_BUFFER_FORMAT_YUV444_10BIT: + return width * 2; + case NV_ENC_BUFFER_FORMAT_ARGB: + case NV_ENC_BUFFER_FORMAT_ARGB10: + case NV_ENC_BUFFER_FORMAT_AYUV: + case NV_ENC_BUFFER_FORMAT_ABGR: + case NV_ENC_BUFFER_FORMAT_ABGR10: + return width * 4; + default: + NVENC_THROW_ERROR("Invalid Buffer format", NV_ENC_ERR_INVALID_PARAM); + return 0; + } +} + +uint32_t NvEncoder::GetNumChromaPlanes( + const NV_ENC_BUFFER_FORMAT bufferFormat) { + switch (bufferFormat) { + case NV_ENC_BUFFER_FORMAT_NV12: + case NV_ENC_BUFFER_FORMAT_YUV420_10BIT: + return 1; + case NV_ENC_BUFFER_FORMAT_YV12: + case NV_ENC_BUFFER_FORMAT_IYUV: + case NV_ENC_BUFFER_FORMAT_YUV444: + case NV_ENC_BUFFER_FORMAT_YUV444_10BIT: + return 2; + case NV_ENC_BUFFER_FORMAT_ARGB: + case NV_ENC_BUFFER_FORMAT_ARGB10: + case NV_ENC_BUFFER_FORMAT_AYUV: + case NV_ENC_BUFFER_FORMAT_ABGR: + case NV_ENC_BUFFER_FORMAT_ABGR10: + return 0; + default: + NVENC_THROW_ERROR("Invalid Buffer format", NV_ENC_ERR_INVALID_PARAM); + return -1; + } +} + +uint32_t NvEncoder::GetChromaPitch(const NV_ENC_BUFFER_FORMAT bufferFormat, + const uint32_t lumaPitch) { + switch (bufferFormat) { + case NV_ENC_BUFFER_FORMAT_NV12: + case NV_ENC_BUFFER_FORMAT_YUV420_10BIT: + case NV_ENC_BUFFER_FORMAT_YUV444: + case NV_ENC_BUFFER_FORMAT_YUV444_10BIT: + return lumaPitch; + case NV_ENC_BUFFER_FORMAT_YV12: + case NV_ENC_BUFFER_FORMAT_IYUV: + return (lumaPitch + 1) / 2; + case NV_ENC_BUFFER_FORMAT_ARGB: + case NV_ENC_BUFFER_FORMAT_ARGB10: + case NV_ENC_BUFFER_FORMAT_AYUV: + case NV_ENC_BUFFER_FORMAT_ABGR: + case NV_ENC_BUFFER_FORMAT_ABGR10: + return 0; + default: + NVENC_THROW_ERROR("Invalid Buffer format", NV_ENC_ERR_INVALID_PARAM); + return -1; + } +} + +void NvEncoder::GetChromaSubPlaneOffsets( + const NV_ENC_BUFFER_FORMAT bufferFormat, + const uint32_t pitch, + const uint32_t height, + std::vector& chromaOffsets) { + chromaOffsets.clear(); + switch (bufferFormat) { + case NV_ENC_BUFFER_FORMAT_NV12: + case NV_ENC_BUFFER_FORMAT_YUV420_10BIT: + chromaOffsets.push_back(pitch * height); + return; + case NV_ENC_BUFFER_FORMAT_YV12: + case NV_ENC_BUFFER_FORMAT_IYUV: + chromaOffsets.push_back(pitch * height); + chromaOffsets.push_back(chromaOffsets[0] + + (NvEncoder::GetChromaPitch(bufferFormat, pitch) * + GetChromaHeight(bufferFormat, height))); + return; + case NV_ENC_BUFFER_FORMAT_YUV444: + case NV_ENC_BUFFER_FORMAT_YUV444_10BIT: + chromaOffsets.push_back(pitch * height); + chromaOffsets.push_back(chromaOffsets[0] + (pitch * height)); + return; + case NV_ENC_BUFFER_FORMAT_ARGB: + case NV_ENC_BUFFER_FORMAT_ARGB10: + case NV_ENC_BUFFER_FORMAT_AYUV: + case NV_ENC_BUFFER_FORMAT_ABGR: + case NV_ENC_BUFFER_FORMAT_ABGR10: + return; + default: + NVENC_THROW_ERROR("Invalid Buffer format", NV_ENC_ERR_INVALID_PARAM); + return; + } +} + +uint32_t NvEncoder::GetChromaHeight(const NV_ENC_BUFFER_FORMAT bufferFormat, + const uint32_t lumaHeight) { + switch (bufferFormat) { + case NV_ENC_BUFFER_FORMAT_YV12: + case NV_ENC_BUFFER_FORMAT_IYUV: + case NV_ENC_BUFFER_FORMAT_NV12: + case NV_ENC_BUFFER_FORMAT_YUV420_10BIT: + return (lumaHeight + 1) / 2; + case NV_ENC_BUFFER_FORMAT_YUV444: + case NV_ENC_BUFFER_FORMAT_YUV444_10BIT: + return lumaHeight; + case NV_ENC_BUFFER_FORMAT_ARGB: + case NV_ENC_BUFFER_FORMAT_ARGB10: + case NV_ENC_BUFFER_FORMAT_AYUV: + case NV_ENC_BUFFER_FORMAT_ABGR: + case NV_ENC_BUFFER_FORMAT_ABGR10: + return 0; + default: + NVENC_THROW_ERROR("Invalid Buffer format", NV_ENC_ERR_INVALID_PARAM); + return 0; + } +} + +uint32_t NvEncoder::GetChromaWidthInBytes( + const NV_ENC_BUFFER_FORMAT bufferFormat, + const uint32_t lumaWidth) { + switch (bufferFormat) { + case NV_ENC_BUFFER_FORMAT_YV12: + case NV_ENC_BUFFER_FORMAT_IYUV: + return (lumaWidth + 1) / 2; + case NV_ENC_BUFFER_FORMAT_NV12: + return lumaWidth; + case NV_ENC_BUFFER_FORMAT_YUV420_10BIT: + return 2 * lumaWidth; + case NV_ENC_BUFFER_FORMAT_YUV444: + return lumaWidth; + case NV_ENC_BUFFER_FORMAT_YUV444_10BIT: + return 2 * lumaWidth; + case NV_ENC_BUFFER_FORMAT_ARGB: + case NV_ENC_BUFFER_FORMAT_ARGB10: + case NV_ENC_BUFFER_FORMAT_AYUV: + case NV_ENC_BUFFER_FORMAT_ABGR: + case NV_ENC_BUFFER_FORMAT_ABGR10: + return 0; + default: + NVENC_THROW_ERROR("Invalid Buffer format", NV_ENC_ERR_INVALID_PARAM); + return 0; + } +} + +int NvEncoder::GetCapabilityValue(GUID guidCodec, NV_ENC_CAPS capsToQuery) { + if (!m_hEncoder) { + return 0; + } + NV_ENC_CAPS_PARAM capsParam = {NV_ENC_CAPS_PARAM_VER}; + capsParam.capsToQuery = capsToQuery; + int v; + m_nvenc.nvEncGetEncodeCaps(m_hEncoder, guidCodec, &capsParam, &v); + return v; +} + +int NvEncoder::GetFrameSize() const { + switch (GetPixelFormat()) { + case NV_ENC_BUFFER_FORMAT_YV12: + case NV_ENC_BUFFER_FORMAT_IYUV: + case NV_ENC_BUFFER_FORMAT_NV12: + return GetEncodeWidth() * + (GetEncodeHeight() + (GetEncodeHeight() + 1) / 2); + case NV_ENC_BUFFER_FORMAT_YUV420_10BIT: + return 2 * GetEncodeWidth() * + (GetEncodeHeight() + (GetEncodeHeight() + 1) / 2); + case NV_ENC_BUFFER_FORMAT_YUV444: + return GetEncodeWidth() * GetEncodeHeight() * 3; + case NV_ENC_BUFFER_FORMAT_YUV444_10BIT: + return 2 * GetEncodeWidth() * GetEncodeHeight() * 3; + case NV_ENC_BUFFER_FORMAT_ARGB: + case NV_ENC_BUFFER_FORMAT_ARGB10: + case NV_ENC_BUFFER_FORMAT_AYUV: + case NV_ENC_BUFFER_FORMAT_ABGR: + case NV_ENC_BUFFER_FORMAT_ABGR10: + return 4 * GetEncodeWidth() * GetEncodeHeight(); + default: + NVENC_THROW_ERROR("Invalid Buffer format", NV_ENC_ERR_INVALID_PARAM); + return 0; + } +} + +void NvEncoder::GetInitializeParams( + NV_ENC_INITIALIZE_PARAMS* pInitializeParams) { + if (!pInitializeParams || !pInitializeParams->encodeConfig) { + NVENC_THROW_ERROR( + "Both pInitializeParams and pInitializeParams->encodeConfig can't be " + "NULL", + NV_ENC_ERR_INVALID_PTR); + } + NV_ENC_CONFIG* pEncodeConfig = pInitializeParams->encodeConfig; + *pEncodeConfig = m_encodeConfig; + *pInitializeParams = m_initializeParams; + pInitializeParams->encodeConfig = pEncodeConfig; +} + +void NvEncoder::InitializeBitstreamBuffer() { + for (int i = 0; i < m_nEncoderBuffer; i++) { + NV_ENC_CREATE_BITSTREAM_BUFFER createBitstreamBuffer = { + NV_ENC_CREATE_BITSTREAM_BUFFER_VER}; + NVENC_API_CALL( + m_nvenc.nvEncCreateBitstreamBuffer(m_hEncoder, &createBitstreamBuffer)); + m_vBitstreamOutputBuffer[i] = createBitstreamBuffer.bitstreamBuffer; + } +} + +void NvEncoder::DestroyBitstreamBuffer() { + for (uint32_t i = 0; i < m_vBitstreamOutputBuffer.size(); i++) { + if (m_vBitstreamOutputBuffer[i]) { + m_nvenc.nvEncDestroyBitstreamBuffer(m_hEncoder, + m_vBitstreamOutputBuffer[i]); + } + } + + m_vBitstreamOutputBuffer.clear(); +} + +void NvEncoder::InitializeMVOutputBuffer() { + for (int i = 0; i < m_nEncoderBuffer; i++) { + NV_ENC_CREATE_MV_BUFFER createMVBuffer = {NV_ENC_CREATE_MV_BUFFER_VER}; + NVENC_API_CALL(m_nvenc.nvEncCreateMVBuffer(m_hEncoder, &createMVBuffer)); + m_vMVDataOutputBuffer.push_back(createMVBuffer.mvBuffer); + } +} + +void NvEncoder::DestroyMVOutputBuffer() { + for (uint32_t i = 0; i < m_vMVDataOutputBuffer.size(); i++) { + if (m_vMVDataOutputBuffer[i]) { + m_nvenc.nvEncDestroyMVBuffer(m_hEncoder, m_vMVDataOutputBuffer[i]); + } + } + + m_vMVDataOutputBuffer.clear(); +} + +NVENCSTATUS NvEncoder::DoMotionEstimation( + NV_ENC_INPUT_PTR inputBuffer, + NV_ENC_INPUT_PTR inputBufferForReference, + NV_ENC_OUTPUT_PTR outputBuffer) { + NV_ENC_MEONLY_PARAMS meParams = {NV_ENC_MEONLY_PARAMS_VER}; + meParams.inputBuffer = inputBuffer; + meParams.referenceFrame = inputBufferForReference; + meParams.inputWidth = GetEncodeWidth(); + meParams.inputHeight = GetEncodeHeight(); + meParams.mvBuffer = outputBuffer; + meParams.completionEvent = GetCompletionEvent(m_iToSend % m_nEncoderBuffer); + NVENCSTATUS nvStatus = + m_nvenc.nvEncRunMotionEstimationOnly(m_hEncoder, &meParams); + + return nvStatus; +} diff --git a/webrtc-sys/src/nvidia/NvCodec/NvCodec/NvEncoder/NvEncoder.h b/webrtc-sys/src/nvidia/NvCodec/NvCodec/NvEncoder/NvEncoder.h new file mode 100644 index 000000000..98c17911d --- /dev/null +++ b/webrtc-sys/src/nvidia/NvCodec/NvCodec/NvEncoder/NvEncoder.h @@ -0,0 +1,505 @@ +/* + * Copyright 2017-2022 NVIDIA Corporation. All rights reserved. + * + * Please refer to the NVIDIA end user license agreement (EULA) associated + * with this source code for terms and conditions that govern your use of + * this software. Any use, reproduction, disclosure, or distribution of + * this software and related documentation outside the terms of the EULA + * is strictly prohibited. + * + */ + +#pragma once + +#include +#include + +#include +#include +#include +#include +#include + +#include "Utils/NvCodecUtils.h" +#include "nvEncodeAPI.h" + +/** + * @brief Exception class for error reporting from NvEncodeAPI calls. + */ +class NVENCException : public std::exception { + public: + NVENCException(const std::string& errorStr, const NVENCSTATUS errorCode) + : m_errorString(errorStr), m_errorCode(errorCode) {} + + virtual ~NVENCException() throw() {} + virtual const char* what() const throw() { return m_errorString.c_str(); } + NVENCSTATUS getErrorCode() const { return m_errorCode; } + const std::string& getErrorString() const { return m_errorString; } + static NVENCException makeNVENCException(const std::string& errorStr, + const NVENCSTATUS errorCode, + const std::string& functionName, + const std::string& fileName, + int lineNo); + + private: + std::string m_errorString; + NVENCSTATUS m_errorCode; +}; + +inline NVENCException NVENCException::makeNVENCException( + const std::string& errorStr, + const NVENCSTATUS errorCode, + const std::string& functionName, + const std::string& fileName, + int lineNo) { + std::ostringstream errorLog; + errorLog << functionName << " : " << errorStr << " at " << fileName << ":" + << lineNo << std::endl; + NVENCException exception(errorLog.str(), errorCode); + return exception; +} + +#define NVENC_THROW_ERROR(errorStr, errorCode) \ + do { \ + throw NVENCException::makeNVENCException( \ + errorStr, errorCode, __FUNCTION__, __FILE__, __LINE__); \ + } while (0) + +#define NVENC_API_CALL(nvencAPI) \ + do { \ + NVENCSTATUS errorCode = nvencAPI; \ + if (errorCode != NV_ENC_SUCCESS) { \ + std::ostringstream errorLog; \ + errorLog << #nvencAPI << " returned error " << errorCode; \ + throw NVENCException::makeNVENCException( \ + errorLog.str(), errorCode, __FUNCTION__, __FILE__, __LINE__); \ + } \ + } while (0) + +struct NvEncInputFrame { + void* inputPtr = nullptr; + uint32_t chromaOffsets[2]; + uint32_t numChromaPlanes; + uint32_t pitch; + uint32_t chromaPitch; + NV_ENC_BUFFER_FORMAT bufferFormat; + NV_ENC_INPUT_RESOURCE_TYPE resourceType; +}; + +/** + * @brief Shared base class for different encoder interfaces. + */ +class NvEncoder { + public: + /** + * @brief This function is used to initialize the encoder session. + * Application must call this function to initialize the encoder, before + * starting to encode any frames. + */ + virtual void CreateEncoder(const NV_ENC_INITIALIZE_PARAMS* pEncodeParams); + + /** + * @brief This function is used to destroy the encoder session. + * Application must call this function to destroy the encoder session and + * clean up any allocated resources. The application must call EndEncode() + * function to get any queued encoded frames before calling DestroyEncoder(). + */ + virtual void DestroyEncoder(); + + /** + * @brief This function is used to reconfigure an existing encoder session. + * Application can use this function to dynamically change the bitrate, + * resolution and other QOS parameters. If the application changes the + * resolution, it must set NV_ENC_RECONFIGURE_PARAMS::forceIDR. + */ + bool Reconfigure(const NV_ENC_RECONFIGURE_PARAMS* pReconfigureParams); + + /** + * @brief This function is used to get the next available input buffer. + * Applications must call this function to obtain a pointer to the next + * input buffer. The application must copy the uncompressed data to the + * input buffer and then call EncodeFrame() function to encode it. + */ + const NvEncInputFrame* GetNextInputFrame(); + + /** + * @brief This function is used to encode a frame. + * Applications must call EncodeFrame() function to encode the uncompressed + * data, which has been copied to an input buffer obtained from the + * GetNextInputFrame() function. + */ + virtual void EncodeFrame(std::vector>& vPacket, + NV_ENC_PIC_PARAMS* pPicParams = nullptr); + + /** + * @brief This function to flush the encoder queue. + * The encoder might be queuing frames for B picture encoding or lookahead; + * the application must call EndEncode() to get all the queued encoded frames + * from the encoder. The application must call this function before + * destroying an encoder session. + */ + virtual void EndEncode(std::vector>& vPacket); + + /** + * @brief This function is used to query hardware encoder capabilities. + * Applications can call this function to query capabilities like maximum + * encode dimensions, support for lookahead or the ME-only mode etc. + */ + int GetCapabilityValue(GUID guidCodec, NV_ENC_CAPS capsToQuery); + + /** + * @brief This function is used to get the current device on which encoder + * is running. + */ + void* GetDevice() const { return m_pDevice; } + + /** + * @brief This function is used to get the current device type which encoder + * is running. + */ + NV_ENC_DEVICE_TYPE GetDeviceType() const { return m_eDeviceType; } + + /** + * @brief This function is used to get the current encode width. + * The encode width can be modified by Reconfigure() function. + */ + int GetEncodeWidth() const { return m_nWidth; } + + /** + * @brief This function is used to get the current encode height. + * The encode height can be modified by Reconfigure() function. + */ + int GetEncodeHeight() const { return m_nHeight; } + + /** + * @brief This function is used to get the current frame size based on + * pixel format. + */ + int GetFrameSize() const; + + /** + * @brief This function is used to initialize config parameters based on + * given codec and preset guids. + * The application can call this function to get the default configuration + * for a certain preset. The application can either use these parameters + * directly or override them with application-specific settings before + * using them in CreateEncoder() function. + */ + void CreateDefaultEncoderParams( + NV_ENC_INITIALIZE_PARAMS* pIntializeParams, + GUID codecGuid, + GUID presetGuid, + NV_ENC_TUNING_INFO tuningInfo = NV_ENC_TUNING_INFO_UNDEFINED); + + /** + * @brief This function is used to get the current initialization + * parameters, which had been used to configure the encoder session. The + * initialization parameters are modified if the application calls + * Reconfigure() function. + */ + void GetInitializeParams(NV_ENC_INITIALIZE_PARAMS* pInitializeParams); + + /** + * @brief This function is used to run motion estimation + * This is used to run motion estimation on a a pair of frames. The + * application must copy the reference frame data to the buffer obtained + * by calling GetNextReferenceFrame(), and copy the input frame data to + * the buffer obtained by calling GetNextInputFrame() before calling the + * RunMotionEstimation() function. + */ + void RunMotionEstimation(std::vector& mvData); + + /** + * @brief This function is used to get an available reference frame. + * Application must call this function to get a pointer to reference buffer, + * to be used in the subsequent RunMotionEstimation() function. + */ + const NvEncInputFrame* GetNextReferenceFrame(); + + /** + * @brief This function is used to get sequence and picture parameter + * headers. Application can call this function after encoder is initialized to + * get SPS and PPS nalus for the current encoder instance. The sequence header + * data might change when application calls Reconfigure() function. + */ + void GetSequenceParams(std::vector& seqParams); + + /** + * @brief NvEncoder class virtual destructor. + */ + virtual ~NvEncoder(); + + public: + /** + * @brief This a static function to get chroma offsets for YUV planar + * formats. + */ + static void GetChromaSubPlaneOffsets(const NV_ENC_BUFFER_FORMAT bufferFormat, + const uint32_t pitch, + const uint32_t height, + std::vector& chromaOffsets); + /** + * @brief This a static function to get the chroma plane pitch for YUV planar + * formats. + */ + static uint32_t GetChromaPitch(const NV_ENC_BUFFER_FORMAT bufferFormat, + const uint32_t lumaPitch); + + /** + * @brief This a static function to get the number of chroma planes for YUV + * planar formats. + */ + static uint32_t GetNumChromaPlanes(const NV_ENC_BUFFER_FORMAT bufferFormat); + + /** + * @brief This a static function to get the chroma plane width in bytes for + * YUV planar formats. + */ + static uint32_t GetChromaWidthInBytes(const NV_ENC_BUFFER_FORMAT bufferFormat, + const uint32_t lumaWidth); + + /** + * @brief This a static function to get the chroma planes height in bytes for + * YUV planar formats. + */ + static uint32_t GetChromaHeight(const NV_ENC_BUFFER_FORMAT bufferFormat, + const uint32_t lumaHeight); + + /** + * @brief This a static function to get the width in bytes for the frame. + * For YUV planar format this is the width in bytes of the luma plane. + */ + static uint32_t GetWidthInBytes(const NV_ENC_BUFFER_FORMAT bufferFormat, + const uint32_t width); + + /** + * @brief This function returns the number of allocated buffers. + */ + uint32_t GetEncoderBufferCount() const { return m_nEncoderBuffer; } + + protected: + /** + * @brief NvEncoder class constructor. + * NvEncoder class constructor cannot be called directly by the application. + */ + NvEncoder(NV_ENC_DEVICE_TYPE eDeviceType, + void* pDevice, + uint32_t nWidth, + uint32_t nHeight, + NV_ENC_BUFFER_FORMAT eBufferFormat, + uint32_t nOutputDelay, + bool bMotionEstimationOnly, + bool bOutputInVideoMemory = false, + bool bDX12Encode = false, + bool bUseIVFContainer = true); + + /** + * @brief This function is used to check if hardware encoder is properly + * initialized. + */ + bool IsHWEncoderInitialized() const { + return m_hEncoder != NULL && m_bEncoderInitialized; + } + + /** + * @brief This function is used to register CUDA, D3D or OpenGL input buffers + * with NvEncodeAPI. This is non public function and is called by derived + * class for allocating and registering input buffers. + */ + void RegisterInputResources(std::vector inputframes, + NV_ENC_INPUT_RESOURCE_TYPE eResourceType, + int width, + int height, + int pitch, + NV_ENC_BUFFER_FORMAT bufferFormat, + bool bReferenceFrame = false); + + /** + * @brief This function is used to unregister resources which had been + * previously registered for encoding using RegisterInputResources() function. + */ + void UnregisterInputResources(); + + /** + * @brief This function is used to register CUDA, D3D or OpenGL input or + * output buffers with NvEncodeAPI. + */ + NV_ENC_REGISTERED_PTR RegisterResource( + void* pBuffer, + NV_ENC_INPUT_RESOURCE_TYPE eResourceType, + int width, + int height, + int pitch, + NV_ENC_BUFFER_FORMAT bufferFormat, + NV_ENC_BUFFER_USAGE bufferUsage = NV_ENC_INPUT_IMAGE, + NV_ENC_FENCE_POINT_D3D12* pInputFencePoint = NULL); + + /** + * @brief This function returns maximum width used to open the encoder + * session. All encode input buffers are allocated using maximum dimensions. + */ + uint32_t GetMaxEncodeWidth() const { return m_nMaxEncodeWidth; } + + /** + * @brief This function returns maximum height used to open the encoder + * session. All encode input buffers are allocated using maximum dimensions. + */ + uint32_t GetMaxEncodeHeight() const { return m_nMaxEncodeHeight; } + + /** + * @brief This function returns the completion event. + */ + void* GetCompletionEvent(uint32_t eventIdx) { + return (m_vpCompletionEvent.size() == m_nEncoderBuffer) + ? m_vpCompletionEvent[eventIdx] + : nullptr; + } + + /** + * @brief This function returns the current pixel format. + */ + NV_ENC_BUFFER_FORMAT GetPixelFormat() const { return m_eBufferFormat; } + + /** + * @brief This function is used to submit the encode commands to the + * NVENC hardware. + */ + NVENCSTATUS DoEncode(NV_ENC_INPUT_PTR inputBuffer, + NV_ENC_OUTPUT_PTR outputBuffer, + NV_ENC_PIC_PARAMS* pPicParams); + + /** + * @brief This function is used to submit the encode commands to the + * NVENC hardware for ME only mode. + */ + NVENCSTATUS DoMotionEstimation(NV_ENC_INPUT_PTR inputBuffer, + NV_ENC_INPUT_PTR inputBufferForReference, + NV_ENC_OUTPUT_PTR outputBuffer); + + /** + * @brief This function is used to map the input buffers to NvEncodeAPI. + */ + void MapResources(uint32_t bfrIdx); + + /** + * @brief This function is used to wait for completion of encode command. + */ + void WaitForCompletionEvent(int iEvent); + + /** + * @brief This function is used to send EOS to HW encoder. + */ + void SendEOS(); + + private: + /** + * @brief This is a private function which is used to check if there is any + buffering done by encoder. + * The encoder generally buffers data to encode B frames or for lookahead + * or pipelining. + */ + bool IsZeroDelay() { return m_nOutputDelay == 0; } + + /** + * @brief This is a private function which is used to load the encode api + * shared library. + */ + void LoadNvEncApi(); + + /** + * @brief This is a private function which is used to get the output packets + * from the encoder HW. + * This is called by DoEncode() function. If there is buffering enabled, + * this may return without any output data. + */ + void GetEncodedPacket(std::vector& vOutputBuffer, + std::vector>& vPacket, + bool bOutputDelay); + + /** + * @brief This is a private function which is used to initialize the + * bitstream buffers. This is only used in the encoding mode. + */ + void InitializeBitstreamBuffer(); + + /** + * @brief This is a private function which is used to destroy the bitstream + * buffers. This is only used in the encoding mode. + */ + void DestroyBitstreamBuffer(); + + /** + * @brief This is a private function which is used to initialize MV output + * buffers. This is only used in ME-only Mode. + */ + void InitializeMVOutputBuffer(); + + /** + * @brief This is a private function which is used to destroy MV output + * buffers. This is only used in ME-only Mode. + */ + void DestroyMVOutputBuffer(); + + /** + * @brief This is a private function which is used to destroy HW encoder. + */ + void DestroyHWEncoder(); + + /** + * @brief This function is used to flush the encoder queue. + */ + void FlushEncoder(); + + private: + /** + * @brief This is a pure virtual function which is used to allocate input + * buffers. The derived classes must implement this function. + */ + virtual void AllocateInputBuffers(int32_t numInputBuffers) = 0; + + /** + * @brief This is a pure virtual function which is used to destroy input + * buffers. The derived classes must implement this function. + */ + virtual void ReleaseInputBuffers() = 0; + + protected: + bool m_bMotionEstimationOnly = false; + bool m_bOutputInVideoMemory = false; + bool m_bIsDX12Encode = false; + void* m_hEncoder = nullptr; + NV_ENCODE_API_FUNCTION_LIST m_nvenc; + NV_ENC_INITIALIZE_PARAMS m_initializeParams = {}; + std::vector m_vInputFrames; + std::vector m_vRegisteredResources; + std::vector m_vReferenceFrames; + std::vector m_vRegisteredResourcesForReference; + std::vector m_vMappedInputBuffers; + std::vector m_vMappedRefBuffers; + std::vector m_vpCompletionEvent; + + int32_t m_iToSend = 0; + int32_t m_iGot = 0; + int32_t m_nEncoderBuffer = 0; + int32_t m_nOutputDelay = 0; + IVFUtils m_IVFUtils; + bool m_bWriteIVFFileHeader = true; + bool m_bUseIVFContainer = true; + + private: + uint32_t m_nWidth; + uint32_t m_nHeight; + NV_ENC_BUFFER_FORMAT m_eBufferFormat; + void* m_pDevice; + NV_ENC_DEVICE_TYPE m_eDeviceType; + NV_ENC_CONFIG m_encodeConfig = {}; + bool m_bEncoderInitialized = false; + uint32_t m_nExtraOutputDelay = + 3; // To ensure encode and graphics can work in parallel, + // m_nExtraOutputDelay should be set to at least 1 + std::vector m_vBitstreamOutputBuffer; + std::vector m_vMVDataOutputBuffer; + uint32_t m_nMaxEncodeWidth = 0; + uint32_t m_nMaxEncodeHeight = 0; + void* m_hModule = nullptr; +}; diff --git a/webrtc-sys/src/nvidia/NvCodec/NvCodec/NvEncoder/NvEncoderCuda.cpp b/webrtc-sys/src/nvidia/NvCodec/NvCodec/NvEncoder/NvEncoderCuda.cpp new file mode 100644 index 000000000..5a3523ccb --- /dev/null +++ b/webrtc-sys/src/nvidia/NvCodec/NvCodec/NvEncoder/NvEncoderCuda.cpp @@ -0,0 +1,271 @@ +/* + * Copyright 2017-2022 NVIDIA Corporation. All rights reserved. + * + * Please refer to the NVIDIA end user license agreement (EULA) associated + * with this source code for terms and conditions that govern your use of + * this software. Any use, reproduction, disclosure, or distribution of + * this software and related documentation outside the terms of the EULA + * is strictly prohibited. + * + */ + +#include "NvEncoderCuda.h" + +NvEncoderCuda::NvEncoderCuda(CUcontext cuContext, + uint32_t nWidth, + uint32_t nHeight, + NV_ENC_BUFFER_FORMAT eBufferFormat, + uint32_t nExtraOutputDelay, + bool bMotionEstimationOnly, + bool bOutputInVideoMemory, + bool bUseIVFContainer) + : NvEncoder(NV_ENC_DEVICE_TYPE_CUDA, + cuContext, + nWidth, + nHeight, + eBufferFormat, + nExtraOutputDelay, + bMotionEstimationOnly, + bOutputInVideoMemory, + false, + bUseIVFContainer), + m_cuContext(cuContext) { + if (!m_hEncoder) { + NVENC_THROW_ERROR("Encoder Initialization failed", + NV_ENC_ERR_INVALID_DEVICE); + } + + if (!m_cuContext) { + NVENC_THROW_ERROR("Invalid Cuda Context", NV_ENC_ERR_INVALID_DEVICE); + } +} + +NvEncoderCuda::~NvEncoderCuda() { + ReleaseCudaResources(); +} + +void NvEncoderCuda::AllocateInputBuffers(int32_t numInputBuffers) { + if (!IsHWEncoderInitialized()) { + NVENC_THROW_ERROR("Encoder intialization failed", + NV_ENC_ERR_ENCODER_NOT_INITIALIZED); + } + + // for MEOnly mode we need to allocate seperate set of buffers for reference + // frame + int numCount = m_bMotionEstimationOnly ? 2 : 1; + + for (int count = 0; count < numCount; count++) { + CUDA_DRVAPI_CALL(cuCtxPushCurrent(m_cuContext)); + std::vector inputFrames; + for (int i = 0; i < numInputBuffers; i++) { + CUdeviceptr pDeviceFrame; + uint32_t chromaHeight = + GetNumChromaPlanes(GetPixelFormat()) * + GetChromaHeight(GetPixelFormat(), GetMaxEncodeHeight()); + if (GetPixelFormat() == NV_ENC_BUFFER_FORMAT_YV12 || + GetPixelFormat() == NV_ENC_BUFFER_FORMAT_IYUV) + chromaHeight = GetChromaHeight(GetPixelFormat(), GetMaxEncodeHeight()); + CUDA_DRVAPI_CALL(cuMemAllocPitch( + (CUdeviceptr*)&pDeviceFrame, &m_cudaPitch, + GetWidthInBytes(GetPixelFormat(), GetMaxEncodeWidth()), + GetMaxEncodeHeight() + chromaHeight, 16)); + inputFrames.push_back((void*)pDeviceFrame); + } + CUDA_DRVAPI_CALL(cuCtxPopCurrent(NULL)); + + RegisterInputResources( + inputFrames, NV_ENC_INPUT_RESOURCE_TYPE_CUDADEVICEPTR, + GetMaxEncodeWidth(), GetMaxEncodeHeight(), (int)m_cudaPitch, + GetPixelFormat(), (count == 1) ? true : false); + } +} + +void NvEncoderCuda::SetIOCudaStreams(NV_ENC_CUSTREAM_PTR inputStream, + NV_ENC_CUSTREAM_PTR outputStream) { + NVENC_API_CALL( + m_nvenc.nvEncSetIOCudaStreams(m_hEncoder, inputStream, outputStream)); +} + +void NvEncoderCuda::ReleaseInputBuffers() { + ReleaseCudaResources(); +} + +void NvEncoderCuda::ReleaseCudaResources() { + if (!m_hEncoder) { + return; + } + + if (!m_cuContext) { + return; + } + + UnregisterInputResources(); + + cuCtxPushCurrent(m_cuContext); + + for (uint32_t i = 0; i < m_vInputFrames.size(); ++i) { + if (m_vInputFrames[i].inputPtr) { + cuMemFree(reinterpret_cast(m_vInputFrames[i].inputPtr)); + } + } + m_vInputFrames.clear(); + + for (uint32_t i = 0; i < m_vReferenceFrames.size(); ++i) { + if (m_vReferenceFrames[i].inputPtr) { + cuMemFree(reinterpret_cast(m_vReferenceFrames[i].inputPtr)); + } + } + m_vReferenceFrames.clear(); + + cuCtxPopCurrent(NULL); + m_cuContext = nullptr; +} + +void NvEncoderCuda::CopyToDeviceFrame(CUcontext device, + void* pSrcFrame, + uint32_t nSrcPitch, + CUdeviceptr pDstFrame, + uint32_t dstPitch, + int width, + int height, + CUmemorytype srcMemoryType, + NV_ENC_BUFFER_FORMAT pixelFormat, + const uint32_t dstChromaOffsets[], + uint32_t numChromaPlanes, + bool bUnAlignedDeviceCopy, + CUstream stream) { + if (srcMemoryType != CU_MEMORYTYPE_HOST && + srcMemoryType != CU_MEMORYTYPE_DEVICE) { + NVENC_THROW_ERROR("Invalid source memory type for copy", + NV_ENC_ERR_INVALID_PARAM); + } + + CUDA_DRVAPI_CALL(cuCtxPushCurrent(device)); + + uint32_t srcPitch = + nSrcPitch ? nSrcPitch : NvEncoder::GetWidthInBytes(pixelFormat, width); + CUDA_MEMCPY2D m = {0}; + m.srcMemoryType = srcMemoryType; + if (srcMemoryType == CU_MEMORYTYPE_HOST) { + m.srcHost = pSrcFrame; + } else { + m.srcDevice = (CUdeviceptr)pSrcFrame; + } + m.srcPitch = srcPitch; + m.dstMemoryType = CU_MEMORYTYPE_DEVICE; + m.dstDevice = pDstFrame; + m.dstPitch = dstPitch; + m.WidthInBytes = NvEncoder::GetWidthInBytes(pixelFormat, width); + m.Height = height; + if (bUnAlignedDeviceCopy && srcMemoryType == CU_MEMORYTYPE_DEVICE) { + CUDA_DRVAPI_CALL(cuMemcpy2DUnaligned(&m)); + } else { + CUDA_DRVAPI_CALL(stream == NULL ? cuMemcpy2D(&m) + : cuMemcpy2DAsync(&m, stream)); + } + + std::vector srcChromaOffsets; + NvEncoder::GetChromaSubPlaneOffsets(pixelFormat, srcPitch, height, + srcChromaOffsets); + uint32_t chromaHeight = NvEncoder::GetChromaHeight(pixelFormat, height); + uint32_t destChromaPitch = NvEncoder::GetChromaPitch(pixelFormat, dstPitch); + uint32_t srcChromaPitch = NvEncoder::GetChromaPitch(pixelFormat, srcPitch); + uint32_t chromaWidthInBytes = + NvEncoder::GetChromaWidthInBytes(pixelFormat, width); + + for (uint32_t i = 0; i < numChromaPlanes; ++i) { + if (chromaHeight) { + if (srcMemoryType == CU_MEMORYTYPE_HOST) { + m.srcHost = ((uint8_t*)pSrcFrame + srcChromaOffsets[i]); + } else { + m.srcDevice = (CUdeviceptr)((uint8_t*)pSrcFrame + srcChromaOffsets[i]); + } + m.srcPitch = srcChromaPitch; + + m.dstDevice = (CUdeviceptr)((uint8_t*)pDstFrame + dstChromaOffsets[i]); + m.dstPitch = destChromaPitch; + m.WidthInBytes = chromaWidthInBytes; + m.Height = chromaHeight; + if (bUnAlignedDeviceCopy && srcMemoryType == CU_MEMORYTYPE_DEVICE) { + CUDA_DRVAPI_CALL(cuMemcpy2DUnaligned(&m)); + } else { + CUDA_DRVAPI_CALL(stream == NULL ? cuMemcpy2D(&m) + : cuMemcpy2DAsync(&m, stream)); + } + } + } + CUDA_DRVAPI_CALL(cuCtxPopCurrent(NULL)); +} + +void NvEncoderCuda::CopyToDeviceFrame(CUcontext device, + void* pSrcFrame, + uint32_t nSrcPitch, + CUdeviceptr pDstFrame, + uint32_t dstPitch, + int width, + int height, + CUmemorytype srcMemoryType, + NV_ENC_BUFFER_FORMAT pixelFormat, + CUdeviceptr dstChromaDevicePtrs[], + uint32_t dstChromaPitch, + uint32_t numChromaPlanes, + bool bUnAlignedDeviceCopy) { + if (srcMemoryType != CU_MEMORYTYPE_HOST && + srcMemoryType != CU_MEMORYTYPE_DEVICE) { + NVENC_THROW_ERROR("Invalid source memory type for copy", + NV_ENC_ERR_INVALID_PARAM); + } + + CUDA_DRVAPI_CALL(cuCtxPushCurrent(device)); + + uint32_t srcPitch = + nSrcPitch ? nSrcPitch : NvEncoder::GetWidthInBytes(pixelFormat, width); + CUDA_MEMCPY2D m = {0}; + m.srcMemoryType = srcMemoryType; + if (srcMemoryType == CU_MEMORYTYPE_HOST) { + m.srcHost = pSrcFrame; + } else { + m.srcDevice = (CUdeviceptr)pSrcFrame; + } + m.srcPitch = srcPitch; + m.dstMemoryType = CU_MEMORYTYPE_DEVICE; + m.dstDevice = pDstFrame; + m.dstPitch = dstPitch; + m.WidthInBytes = NvEncoder::GetWidthInBytes(pixelFormat, width); + m.Height = height; + if (bUnAlignedDeviceCopy && srcMemoryType == CU_MEMORYTYPE_DEVICE) { + CUDA_DRVAPI_CALL(cuMemcpy2DUnaligned(&m)); + } else { + CUDA_DRVAPI_CALL(cuMemcpy2D(&m)); + } + + std::vector srcChromaOffsets; + NvEncoder::GetChromaSubPlaneOffsets(pixelFormat, srcPitch, height, + srcChromaOffsets); + uint32_t chromaHeight = NvEncoder::GetChromaHeight(pixelFormat, height); + uint32_t srcChromaPitch = NvEncoder::GetChromaPitch(pixelFormat, srcPitch); + uint32_t chromaWidthInBytes = + NvEncoder::GetChromaWidthInBytes(pixelFormat, width); + + for (uint32_t i = 0; i < numChromaPlanes; ++i) { + if (chromaHeight) { + if (srcMemoryType == CU_MEMORYTYPE_HOST) { + m.srcHost = ((uint8_t*)pSrcFrame + srcChromaOffsets[i]); + } else { + m.srcDevice = (CUdeviceptr)((uint8_t*)pSrcFrame + srcChromaOffsets[i]); + } + m.srcPitch = srcChromaPitch; + + m.dstDevice = dstChromaDevicePtrs[i]; + m.dstPitch = dstChromaPitch; + m.WidthInBytes = chromaWidthInBytes; + m.Height = chromaHeight; + if (bUnAlignedDeviceCopy && srcMemoryType == CU_MEMORYTYPE_DEVICE) { + CUDA_DRVAPI_CALL(cuMemcpy2DUnaligned(&m)); + } else { + CUDA_DRVAPI_CALL(cuMemcpy2D(&m)); + } + } + } + CUDA_DRVAPI_CALL(cuCtxPopCurrent(NULL)); +} diff --git a/webrtc-sys/src/nvidia/NvCodec/NvCodec/NvEncoder/NvEncoderCuda.h b/webrtc-sys/src/nvidia/NvCodec/NvCodec/NvEncoder/NvEncoderCuda.h new file mode 100644 index 000000000..bd058667f --- /dev/null +++ b/webrtc-sys/src/nvidia/NvCodec/NvCodec/NvEncoder/NvEncoderCuda.h @@ -0,0 +1,123 @@ +/* + * Copyright 2017-2022 NVIDIA Corporation. All rights reserved. + * + * Please refer to the NVIDIA end user license agreement (EULA) associated + * with this source code for terms and conditions that govern your use of + * this software. Any use, reproduction, disclosure, or distribution of + * this software and related documentation outside the terms of the EULA + * is strictly prohibited. + * + */ + +#pragma once + +#include +#include + +#include +#include + +#include "NvEncoder.h" + +#define CUDA_DRVAPI_CALL(call) \ + do { \ + CUresult err__ = call; \ + if (err__ != CUDA_SUCCESS) { \ + const char* szErrName = NULL; \ + cuGetErrorName(err__, &szErrName); \ + std::ostringstream errorLog; \ + errorLog << "CUDA driver API error " << szErrName; \ + throw NVENCException::makeNVENCException( \ + errorLog.str(), NV_ENC_ERR_GENERIC, __FUNCTION__, __FILE__, \ + __LINE__); \ + } \ + } while (0) + +/** + * @brief Encoder for CUDA device memory. + */ +class NvEncoderCuda : public NvEncoder { + public: + NvEncoderCuda(CUcontext cuContext, + uint32_t nWidth, + uint32_t nHeight, + NV_ENC_BUFFER_FORMAT eBufferFormat, + uint32_t nExtraOutputDelay = 3, + bool bMotionEstimationOnly = false, + bool bOPInVideoMemory = false, + bool bUseIVFContainer = true); + virtual ~NvEncoderCuda(); + + /** + * @brief This is a static function to copy input data from host memory to + * device memory. This function assumes YUV plane is a single contiguous + * memory segment. + */ + static void CopyToDeviceFrame(CUcontext device, + void* pSrcFrame, + uint32_t nSrcPitch, + CUdeviceptr pDstFrame, + uint32_t dstPitch, + int width, + int height, + CUmemorytype srcMemoryType, + NV_ENC_BUFFER_FORMAT pixelFormat, + const uint32_t dstChromaOffsets[], + uint32_t numChromaPlanes, + bool bUnAlignedDeviceCopy = false, + CUstream stream = NULL); + + /** + * @brief This is a static function to copy input data from host memory to + * device memory. Application must pass a seperate device pointer for each YUV + * plane. + */ + static void CopyToDeviceFrame(CUcontext device, + void* pSrcFrame, + uint32_t nSrcPitch, + CUdeviceptr pDstFrame, + uint32_t dstPitch, + int width, + int height, + CUmemorytype srcMemoryType, + NV_ENC_BUFFER_FORMAT pixelFormat, + CUdeviceptr dstChromaPtr[], + uint32_t dstChromaPitch, + uint32_t numChromaPlanes, + bool bUnAlignedDeviceCopy = false); + + /** + * @brief This function sets input and output CUDA streams + */ + void SetIOCudaStreams(NV_ENC_CUSTREAM_PTR inputStream, + NV_ENC_CUSTREAM_PTR outputStream); + + protected: + /** + * @brief This function is used to release the input buffers allocated for + * encoding. This function is an override of virtual function + * NvEncoder::ReleaseInputBuffers(). + */ + virtual void ReleaseInputBuffers() override; + + private: + /** + * @brief This function is used to allocate input buffers for encoding. + * This function is an override of virtual function + * NvEncoder::AllocateInputBuffers(). + */ + virtual void AllocateInputBuffers(int32_t numInputBuffers) override; + + private: + /** + * @brief This is a private function to release CUDA device memory used for + * encoding. + */ + void ReleaseCudaResources(); + + protected: + CUcontext m_cuContext; + + private: + size_t m_cudaPitch = 0; +}; diff --git a/webrtc-sys/src/nvidia/NvCodec/README.txt b/webrtc-sys/src/nvidia/NvCodec/README.txt new file mode 100644 index 000000000..b0ea97ff0 --- /dev/null +++ b/webrtc-sys/src/nvidia/NvCodec/README.txt @@ -0,0 +1,2 @@ +Source code under the directory are copied from this repository. +https://github.com/NVIDIA/video-sdk-samples/tree/master/Samples \ No newline at end of file diff --git a/webrtc-sys/src/nvidia/NvCodec/include/Utils/Logger.h b/webrtc-sys/src/nvidia/NvCodec/include/Utils/Logger.h new file mode 100644 index 000000000..0ecda0647 --- /dev/null +++ b/webrtc-sys/src/nvidia/NvCodec/include/Utils/Logger.h @@ -0,0 +1,240 @@ +/* +* Copyright 2017-2022 NVIDIA Corporation. All rights reserved. +* +* Please refer to the NVIDIA end user license agreement (EULA) associated +* with this source code for terms and conditions that govern your use of +* this software. Any use, reproduction, disclosure, or distribution of +* this software and related documentation outside the terms of the EULA +* is strictly prohibited. +* +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#include + +#pragma comment(lib, "ws2_32.lib") +#undef ERROR +#else +#include +#include +#include +#include +#define SOCKET int +#define INVALID_SOCKET -1 +#endif + +enum LogLevel { + TRACE, + INFO, + WARNING, + ERROR, + FATAL +}; + +namespace simplelogger{ +class Logger { +public: + Logger(LogLevel level, bool bPrintTimeStamp) : level(level), bPrintTimeStamp(bPrintTimeStamp) {} + virtual ~Logger() {} + virtual std::ostream& GetStream() = 0; + virtual void FlushStream() {} + bool ShouldLogFor(LogLevel l) { + return l >= level; + } + char* GetLead(LogLevel l, const char *szFile, int nLine, const char *szFunc) { + if (l < TRACE || l > FATAL) { + sprintf(szLead, "[?????] "); + return szLead; + } + const char *szLevels[] = {"TRACE", "INFO", "WARN", "ERROR", "FATAL"}; + if (bPrintTimeStamp) { + time_t t = time(NULL); + struct tm *ptm = localtime(&t); + sprintf(szLead, "[%-5s][%02d:%02d:%02d] ", + szLevels[l], ptm->tm_hour, ptm->tm_min, ptm->tm_sec); + } else { + sprintf(szLead, "[%-5s] ", szLevels[l]); + } + return szLead; + } + void EnterCriticalSection() { + mtx.lock(); + } + void LeaveCriticalSection() { + mtx.unlock(); + } +private: + LogLevel level; + char szLead[80]; + bool bPrintTimeStamp; + std::mutex mtx; +}; + +class LoggerFactory { +public: + static Logger* CreateFileLogger(std::string strFilePath, + LogLevel level = INFO, bool bPrintTimeStamp = true) { + return new FileLogger(strFilePath, level, bPrintTimeStamp); + } + static Logger* CreateConsoleLogger(LogLevel level = INFO, + bool bPrintTimeStamp = true) { + return new ConsoleLogger(level, bPrintTimeStamp); + } + static Logger* CreateUdpLogger(char *szHost, unsigned uPort, LogLevel level = INFO, + bool bPrintTimeStamp = true) { + return new UdpLogger(szHost, uPort, level, bPrintTimeStamp); + } +private: + LoggerFactory() {} + + class FileLogger : public Logger { + public: + FileLogger(std::string strFilePath, LogLevel level, bool bPrintTimeStamp) + : Logger(level, bPrintTimeStamp) { + pFileOut = new std::ofstream(); + pFileOut->open(strFilePath.c_str()); + } + ~FileLogger() { + pFileOut->close(); + } + std::ostream& GetStream() { + return *pFileOut; + } + private: + std::ofstream *pFileOut; + }; + + class ConsoleLogger : public Logger { + public: + ConsoleLogger(LogLevel level, bool bPrintTimeStamp) + : Logger(level, bPrintTimeStamp) {} + std::ostream& GetStream() { + return std::cout; + } + }; + + class UdpLogger : public Logger { + private: + class UdpOstream : public std::ostream { + public: + UdpOstream(char *szHost, unsigned short uPort) : std::ostream(&sb), socket(INVALID_SOCKET){ +#ifdef _WIN32 + WSADATA w; + if (WSAStartup(0x0101, &w) != 0) { + fprintf(stderr, "WSAStartup() failed.\n"); + return; + } +#endif + socket = ::socket(AF_INET, SOCK_DGRAM, 0); + if (socket == INVALID_SOCKET) { +#ifdef _WIN32 + WSACleanup(); +#endif + fprintf(stderr, "socket() failed.\n"); + return; + } +#ifdef _WIN32 + unsigned int b1, b2, b3, b4; + sscanf(szHost, "%u.%u.%u.%u", &b1, &b2, &b3, &b4); + struct in_addr addr = {(unsigned char)b1, (unsigned char)b2, (unsigned char)b3, (unsigned char)b4}; +#else + struct in_addr addr = {inet_addr(szHost)}; +#endif + struct sockaddr_in s = {AF_INET, htons(uPort), addr}; + server = s; + } + ~UdpOstream() throw() { + if (socket == INVALID_SOCKET) { + return; + } +#ifdef _WIN32 + closesocket(socket); + WSACleanup(); +#else + close(socket); +#endif + } + void Flush() { + if (sendto(socket, sb.str().c_str(), (int)sb.str().length() + 1, + 0, (struct sockaddr *)&server, (int)sizeof(sockaddr_in)) == -1) { + fprintf(stderr, "sendto() failed.\n"); + } + sb.str(""); + } + + private: + std::stringbuf sb; + SOCKET socket; + struct sockaddr_in server; + }; + public: + UdpLogger(char *szHost, unsigned uPort, LogLevel level, bool bPrintTimeStamp) + : Logger(level, bPrintTimeStamp), udpOut(szHost, (unsigned short)uPort) {} + UdpOstream& GetStream() { + return udpOut; + } + virtual void FlushStream() { + udpOut.Flush(); + } + private: + UdpOstream udpOut; + }; +}; + +class LogTransaction { +public: + LogTransaction(Logger *pLogger, LogLevel level, const char *szFile, const int nLine, const char *szFunc) : pLogger(pLogger), level(level) { + if (!pLogger) { + std::cout << "[-----] "; + return; + } + if (!pLogger->ShouldLogFor(level)) { + return; + } + pLogger->EnterCriticalSection(); + pLogger->GetStream() << pLogger->GetLead(level, szFile, nLine, szFunc); + } + ~LogTransaction() { + if (!pLogger) { + std::cout << std::endl; + return; + } + if (!pLogger->ShouldLogFor(level)) { + return; + } + pLogger->GetStream() << std::endl; + pLogger->FlushStream(); + pLogger->LeaveCriticalSection(); + if (level == FATAL) { + exit(1); + } + } + std::ostream& GetStream() { + if (!pLogger) { + return std::cout; + } + if (!pLogger->ShouldLogFor(level)) { + return ossNull; + } + return pLogger->GetStream(); + } +private: + Logger *pLogger; + LogLevel level; + std::ostringstream ossNull; +}; + +} + +extern simplelogger::Logger *logger; +#define LOG(level) simplelogger::LogTransaction(logger, level, __FILE__, __LINE__, __FUNCTION__).GetStream() diff --git a/webrtc-sys/src/nvidia/NvCodec/include/Utils/NvCodecUtils.h b/webrtc-sys/src/nvidia/NvCodec/include/Utils/NvCodecUtils.h new file mode 100644 index 000000000..47bb940c5 --- /dev/null +++ b/webrtc-sys/src/nvidia/NvCodec/include/Utils/NvCodecUtils.h @@ -0,0 +1,537 @@ +/* +* Copyright 2017-2022 NVIDIA Corporation. All rights reserved. +* +* Please refer to the NVIDIA end user license agreement (EULA) associated +* with this source code for terms and conditions that govern your use of +* this software. Any use, reproduction, disclosure, or distribution of +* this software and related documentation outside the terms of the EULA +* is strictly prohibited. +* +*/ + +//--------------------------------------------------------------------------- +//! \file NvCodecUtils.h +//! \brief Miscellaneous classes and error checking functions. +//! +//! Used by Transcode/Encode samples apps for reading input files, mutithreading, performance measurement or colorspace conversion while decoding. +//--------------------------------------------------------------------------- + +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Logger.h" + +extern simplelogger::Logger *logger; + +#ifdef __cuda_cuda_h__ +inline bool check(CUresult e, int iLine, const char *szFile) { + if (e != CUDA_SUCCESS) { + const char *szErrName = NULL; + cuGetErrorName(e, &szErrName); + LOG(FATAL) << "CUDA driver API error " << szErrName << " at line " << iLine << " in file " << szFile; + return false; + } + return true; +} +#endif + +#ifdef __CUDA_RUNTIME_H__ +inline bool check(cudaError_t e, int iLine, const char *szFile) { + if (e != cudaSuccess) { + LOG(FATAL) << "CUDA runtime API error " << cudaGetErrorName(e) << " at line " << iLine << " in file " << szFile; + return false; + } + return true; +} +#endif + +#ifdef _NV_ENCODEAPI_H_ +inline bool check(NVENCSTATUS e, int iLine, const char *szFile) { + const char *aszErrName[] = { + "NV_ENC_SUCCESS", + "NV_ENC_ERR_NO_ENCODE_DEVICE", + "NV_ENC_ERR_UNSUPPORTED_DEVICE", + "NV_ENC_ERR_INVALID_ENCODERDEVICE", + "NV_ENC_ERR_INVALID_DEVICE", + "NV_ENC_ERR_DEVICE_NOT_EXIST", + "NV_ENC_ERR_INVALID_PTR", + "NV_ENC_ERR_INVALID_EVENT", + "NV_ENC_ERR_INVALID_PARAM", + "NV_ENC_ERR_INVALID_CALL", + "NV_ENC_ERR_OUT_OF_MEMORY", + "NV_ENC_ERR_ENCODER_NOT_INITIALIZED", + "NV_ENC_ERR_UNSUPPORTED_PARAM", + "NV_ENC_ERR_LOCK_BUSY", + "NV_ENC_ERR_NOT_ENOUGH_BUFFER", + "NV_ENC_ERR_INVALID_VERSION", + "NV_ENC_ERR_MAP_FAILED", + "NV_ENC_ERR_NEED_MORE_INPUT", + "NV_ENC_ERR_ENCODER_BUSY", + "NV_ENC_ERR_EVENT_NOT_REGISTERD", + "NV_ENC_ERR_GENERIC", + "NV_ENC_ERR_INCOMPATIBLE_CLIENT_KEY", + "NV_ENC_ERR_UNIMPLEMENTED", + "NV_ENC_ERR_RESOURCE_REGISTER_FAILED", + "NV_ENC_ERR_RESOURCE_NOT_REGISTERED", + "NV_ENC_ERR_RESOURCE_NOT_MAPPED", + }; + if (e != NV_ENC_SUCCESS) { + LOG(FATAL) << "NVENC error " << aszErrName[e] << " at line " << iLine << " in file " << szFile; + return false; + } + return true; +} +#endif + +#ifdef _WINERROR_ +inline bool check(HRESULT e, int iLine, const char *szFile) { + if (e != S_OK) { + std::stringstream stream; + stream << std::hex << std::uppercase << e; + LOG(FATAL) << "HRESULT error 0x" << stream.str() << " at line " << iLine << " in file " << szFile; + return false; + } + return true; +} +#endif + +#if defined(__gl_h_) || defined(__GL_H__) +inline bool check(GLenum e, int iLine, const char *szFile) { + if (e != 0) { + LOG(ERROR) << "GLenum error " << e << " at line " << iLine << " in file " << szFile; + return false; + } + return true; +} +#endif + +inline bool check(int e, int iLine, const char *szFile) { + if (e < 0) { + LOG(ERROR) << "General error " << e << " at line " << iLine << " in file " << szFile; + return false; + } + return true; +} + +#define ck(call) check(call, __LINE__, __FILE__) +#define MAKE_FOURCC( ch0, ch1, ch2, ch3 ) \ + ( (uint32_t)(uint8_t)(ch0) | ( (uint32_t)(uint8_t)(ch1) << 8 ) | \ + ( (uint32_t)(uint8_t)(ch2) << 16 ) | ( (uint32_t)(uint8_t)(ch3) << 24 ) ) + +/** +* @brief Wrapper class around std::thread +*/ +class NvThread +{ +public: + NvThread() = default; + NvThread(const NvThread&) = delete; + NvThread& operator=(const NvThread& other) = delete; + + NvThread(std::thread&& thread) : t(std::move(thread)) + { + + } + + NvThread(NvThread&& thread) : t(std::move(thread.t)) + { + + } + + NvThread& operator=(NvThread&& other) + { + t = std::move(other.t); + return *this; + } + + ~NvThread() + { + join(); + } + + void join() + { + if (t.joinable()) + { + t.join(); + } + } +private: + std::thread t; +}; + +#ifndef _WIN32 +#define _stricmp strcasecmp +#define _stat64 stat64 +#endif + +/** +* @brief Utility class to allocate buffer memory. Helps avoid I/O during the encode/decode loop in case of performance tests. +*/ +class BufferedFileReader { +public: + /** + * @brief Constructor function to allocate appropriate memory and copy file contents into it + */ + BufferedFileReader(const char *szFileName, bool bPartial = false) { + struct _stat64 st; + + if (_stat64(szFileName, &st) != 0) { + return; + } + + nSize = st.st_size; + while (nSize) { + try { + pBuf = new uint8_t[(size_t)nSize]; + if (nSize != st.st_size) { + LOG(WARNING) << "File is too large - only " << std::setprecision(4) << 100.0 * nSize / st.st_size << "% is loaded"; + } + break; + } catch(std::bad_alloc) { + if (!bPartial) { + LOG(ERROR) << "Failed to allocate memory in BufferedReader"; + return; + } + nSize = (uint32_t)(nSize * 0.9); + } + } + + std::ifstream fpIn(szFileName, std::ifstream::in | std::ifstream::binary); + if (!fpIn) + { + LOG(ERROR) << "Unable to open input file: " << szFileName; + return; + } + + std::streamsize nRead = fpIn.read(reinterpret_cast(pBuf), nSize).gcount(); + fpIn.close(); + + assert(nRead == nSize); + } + ~BufferedFileReader() { + if (pBuf) { + delete[] pBuf; + } + } + bool GetBuffer(uint8_t **ppBuf, uint64_t *pnSize) { + if (!pBuf) { + return false; + } + + *ppBuf = pBuf; + *pnSize = nSize; + return true; + } + +private: + uint8_t *pBuf = NULL; + uint64_t nSize = 0; +}; + +/** +* @brief Template class to facilitate color space conversion +*/ +template +class YuvConverter { +public: + YuvConverter(int nWidth, int nHeight) : nWidth(nWidth), nHeight(nHeight) { + pQuad = new T[((nWidth + 1) / 2) * ((nHeight + 1) / 2)]; + } + ~YuvConverter() { + delete[] pQuad; + } + void PlanarToUVInterleaved(T *pFrame, int nPitch = 0) { + if (nPitch == 0) { + nPitch = nWidth; + } + + // sizes of source surface plane + int nSizePlaneY = nPitch * nHeight; + int nSizePlaneU = ((nPitch + 1) / 2) * ((nHeight + 1) / 2); + int nSizePlaneV = nSizePlaneU; + + T *puv = pFrame + nSizePlaneY; + if (nPitch == nWidth) { + memcpy(pQuad, puv, nSizePlaneU * sizeof(T)); + } else { + for (int i = 0; i < (nHeight + 1) / 2; i++) { + memcpy(pQuad + ((nWidth + 1) / 2) * i, puv + ((nPitch + 1) / 2) * i, ((nWidth + 1) / 2) * sizeof(T)); + } + } + T *pv = puv + nSizePlaneU; + for (int y = 0; y < (nHeight + 1) / 2; y++) { + for (int x = 0; x < (nWidth + 1) / 2; x++) { + puv[y * nPitch + x * 2] = pQuad[y * ((nWidth + 1) / 2) + x]; + puv[y * nPitch + x * 2 + 1] = pv[y * ((nPitch + 1) / 2) + x]; + } + } + } + void UVInterleavedToPlanar(T *pFrame, int nPitch = 0) { + if (nPitch == 0) { + nPitch = nWidth; + } + + // sizes of source surface plane + int nSizePlaneY = nPitch * nHeight; + int nSizePlaneU = ((nPitch + 1) / 2) * ((nHeight + 1) / 2); + int nSizePlaneV = nSizePlaneU; + + T *puv = pFrame + nSizePlaneY, + *pu = puv, + *pv = puv + nSizePlaneU; + + // split chroma from interleave to planar + for (int y = 0; y < (nHeight + 1) / 2; y++) { + for (int x = 0; x < (nWidth + 1) / 2; x++) { + pu[y * ((nPitch + 1) / 2) + x] = puv[y * nPitch + x * 2]; + pQuad[y * ((nWidth + 1) / 2) + x] = puv[y * nPitch + x * 2 + 1]; + } + } + if (nPitch == nWidth) { + memcpy(pv, pQuad, nSizePlaneV * sizeof(T)); + } else { + for (int i = 0; i < (nHeight + 1) / 2; i++) { + memcpy(pv + ((nPitch + 1) / 2) * i, pQuad + ((nWidth + 1) / 2) * i, ((nWidth + 1) / 2) * sizeof(T)); + } + } + } + +private: + T *pQuad; + int nWidth, nHeight; +}; + +/** +* @brief Class for writing IVF format header for AV1 codec +*/ +class IVFUtils { +public: + void WriteFileHeader(std::vector &vPacket, uint32_t nFourCC, uint32_t nWidth, uint32_t nHeight, uint32_t nFrameRateNum, uint32_t nFrameRateDen, uint32_t nFrameCnt) + { + char header[32]; + + header[0] = 'D'; + header[1] = 'K'; + header[2] = 'I'; + header[3] = 'F'; + mem_put_le16(header + 4, 0); // version + mem_put_le16(header + 6, 32); // header size + mem_put_le32(header + 8, nFourCC); // fourcc + mem_put_le16(header + 12, nWidth); // width + mem_put_le16(header + 14, nHeight); // height + mem_put_le32(header + 16, nFrameRateNum); // rate + mem_put_le32(header + 20, nFrameRateDen); // scale + mem_put_le32(header + 24, nFrameCnt); // length + mem_put_le32(header + 28, 0); // unused + + vPacket.insert(vPacket.end(), &header[0], &header[32]); + } + + void WriteFrameHeader(std::vector &vPacket, size_t nFrameSize, int64_t pts) + { + char header[12]; + mem_put_le32(header, (int)nFrameSize); + mem_put_le32(header + 4, (int)(pts & 0xFFFFFFFF)); + mem_put_le32(header + 8, (int)(pts >> 32)); + + vPacket.insert(vPacket.end(), &header[0], &header[12]); + } + +private: + static inline void mem_put_le32(void *vmem, int val) + { + unsigned char *mem = (unsigned char *)vmem; + mem[0] = (unsigned char)((val >> 0) & 0xff); + mem[1] = (unsigned char)((val >> 8) & 0xff); + mem[2] = (unsigned char)((val >> 16) & 0xff); + mem[3] = (unsigned char)((val >> 24) & 0xff); + } + + static inline void mem_put_le16(void *vmem, int val) + { + unsigned char *mem = (unsigned char *)vmem; + mem[0] = (unsigned char)((val >> 0) & 0xff); + mem[1] = (unsigned char)((val >> 8) & 0xff); + } + +}; + +/** +* @brief Utility class to measure elapsed time in seconds between the block of executed code +*/ +class StopWatch { +public: + void Start() { + t0 = std::chrono::high_resolution_clock::now(); + } + double Stop() { + return std::chrono::duration_cast(std::chrono::high_resolution_clock::now().time_since_epoch() - t0.time_since_epoch()).count() / 1.0e9; + } + +private: + std::chrono::high_resolution_clock::time_point t0; +}; + +template +class ConcurrentQueue +{ + public: + + ConcurrentQueue() {} + ConcurrentQueue(size_t size) : maxSize(size) {} + ConcurrentQueue(const ConcurrentQueue&) = delete; + ConcurrentQueue& operator=(const ConcurrentQueue&) = delete; + + void setSize(size_t s) { + maxSize = s; + } + + void push_back(const T& value) { + // Do not use a std::lock_guard here. We will need to explicitly + // unlock before notify_one as the other waiting thread will + // automatically try to acquire mutex once it wakes up + // (which will happen on notify_one) + std::unique_lock lock(m_mutex); + auto wasEmpty = m_List.empty(); + + while (full()) { + m_cond.wait(lock); + } + + m_List.push_back(value); + if (wasEmpty && !m_List.empty()) { + lock.unlock(); + m_cond.notify_one(); + } + } + + T pop_front() { + std::unique_lock lock(m_mutex); + + while (m_List.empty()) { + m_cond.wait(lock); + } + auto wasFull = full(); + T data = std::move(m_List.front()); + m_List.pop_front(); + + if (wasFull && !full()) { + lock.unlock(); + m_cond.notify_one(); + } + + return data; + } + + T front() { + std::unique_lock lock(m_mutex); + + while (m_List.empty()) { + m_cond.wait(lock); + } + + return m_List.front(); + } + + size_t size() { + std::unique_lock lock(m_mutex); + return m_List.size(); + } + + bool empty() { + std::unique_lock lock(m_mutex); + return m_List.empty(); + } + void clear() { + std::unique_lock lock(m_mutex); + m_List.clear(); + } + +private: + bool full() { + if (m_List.size() == maxSize) + return true; + return false; + } + +private: + std::list m_List; + std::mutex m_mutex; + std::condition_variable m_cond; + size_t maxSize; +}; + +inline void CheckInputFile(const char *szInFilePath) { + std::ifstream fpIn(szInFilePath, std::ios::in | std::ios::binary); + if (fpIn.fail()) { + std::ostringstream err; + err << "Unable to open input file: " << szInFilePath << std::endl; + throw std::invalid_argument(err.str()); + } +} + +inline void ValidateResolution(int nWidth, int nHeight) { + + if (nWidth <= 0 || nHeight <= 0) { + std::ostringstream err; + err << "Please specify positive non zero resolution as -s WxH. Current resolution is " << nWidth << "x" << nHeight << std::endl; + throw std::invalid_argument(err.str()); + } +} + +template +void Nv12ToColor32(uint8_t *dpNv12, int nNv12Pitch, uint8_t *dpBgra, int nBgraPitch, int nWidth, int nHeight, int iMatrix = 0); +template +void Nv12ToColor64(uint8_t *dpNv12, int nNv12Pitch, uint8_t *dpBgra, int nBgraPitch, int nWidth, int nHeight, int iMatrix = 0); + +template +void P016ToColor32(uint8_t *dpP016, int nP016Pitch, uint8_t *dpBgra, int nBgraPitch, int nWidth, int nHeight, int iMatrix = 4); +template +void P016ToColor64(uint8_t *dpP016, int nP016Pitch, uint8_t *dpBgra, int nBgraPitch, int nWidth, int nHeight, int iMatrix = 4); + +template +void YUV444ToColor32(uint8_t *dpYUV444, int nPitch, uint8_t *dpBgra, int nBgraPitch, int nWidth, int nHeight, int iMatrix = 0); +template +void YUV444ToColor64(uint8_t *dpYUV444, int nPitch, uint8_t *dpBgra, int nBgraPitch, int nWidth, int nHeight, int iMatrix = 0); + +template +void YUV444P16ToColor32(uint8_t *dpYUV444, int nPitch, uint8_t *dpBgra, int nBgraPitch, int nWidth, int nHeight, int iMatrix = 4); +template +void YUV444P16ToColor64(uint8_t *dpYUV444, int nPitch, uint8_t *dpBgra, int nBgraPitch, int nWidth, int nHeight, int iMatrix = 4); + +template +void Nv12ToColorPlanar(uint8_t *dpNv12, int nNv12Pitch, uint8_t *dpBgrp, int nBgrpPitch, int nWidth, int nHeight, int iMatrix = 0); +template +void P016ToColorPlanar(uint8_t *dpP016, int nP016Pitch, uint8_t *dpBgrp, int nBgrpPitch, int nWidth, int nHeight, int iMatrix = 4); + +template +void YUV444ToColorPlanar(uint8_t *dpYUV444, int nPitch, uint8_t *dpBgrp, int nBgrpPitch, int nWidth, int nHeight, int iMatrix = 0); +template +void YUV444P16ToColorPlanar(uint8_t *dpYUV444, int nPitch, uint8_t *dpBgrp, int nBgrpPitch, int nWidth, int nHeight, int iMatrix = 4); + +void Bgra64ToP016(uint8_t *dpBgra, int nBgraPitch, uint8_t *dpP016, int nP016Pitch, int nWidth, int nHeight, int iMatrix = 4); + +void ConvertUInt8ToUInt16(uint8_t *dpUInt8, uint16_t *dpUInt16, int nSrcPitch, int nDestPitch, int nWidth, int nHeight); +void ConvertUInt16ToUInt8(uint16_t *dpUInt16, uint8_t *dpUInt8, int nSrcPitch, int nDestPitch, int nWidth, int nHeight); + +void ResizeNv12(unsigned char *dpDstNv12, int nDstPitch, int nDstWidth, int nDstHeight, unsigned char *dpSrcNv12, int nSrcPitch, int nSrcWidth, int nSrcHeight, unsigned char *dpDstNv12UV = nullptr); +void ResizeP016(unsigned char *dpDstP016, int nDstPitch, int nDstWidth, int nDstHeight, unsigned char *dpSrcP016, int nSrcPitch, int nSrcWidth, int nSrcHeight, unsigned char *dpDstP016UV = nullptr); + +void ScaleYUV420(unsigned char *dpDstY, unsigned char* dpDstU, unsigned char* dpDstV, int nDstPitch, int nDstChromaPitch, int nDstWidth, int nDstHeight, + unsigned char *dpSrcY, unsigned char* dpSrcU, unsigned char* dpSrcV, int nSrcPitch, int nSrcChromaPitch, int nSrcWidth, int nSrcHeight, bool bSemiplanar); + +#ifdef __cuda_cuda_h__ +void ComputeCRC(uint8_t *pBuffer, uint32_t *crcValue, CUstream_st *outputCUStream); +#endif diff --git a/webrtc-sys/src/nvidia/NvCodec/include/cuviddec.h b/webrtc-sys/src/nvidia/NvCodec/include/cuviddec.h new file mode 100644 index 000000000..d1bbe0afc --- /dev/null +++ b/webrtc-sys/src/nvidia/NvCodec/include/cuviddec.h @@ -0,0 +1,1188 @@ +/* + * This copyright notice applies to this header file only: + * + * Copyright (c) 2010-2022 NVIDIA Corporation + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the software, and to permit persons to whom the + * software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +/*****************************************************************************************************/ +//! \file cuviddec.h +//! NVDECODE API provides video decoding interface to NVIDIA GPU devices. +//! This file contains constants, structure definitions and function prototypes used for decoding. +/*****************************************************************************************************/ + +#if !defined(__CUDA_VIDEO_H__) +#define __CUDA_VIDEO_H__ + +#ifndef __cuda_cuda_h__ +#include +#endif // __cuda_cuda_h__ + +#if defined(_WIN64) || defined(__LP64__) || defined(__x86_64) || defined(AMD64) || defined(_M_AMD64) +#if (CUDA_VERSION >= 3020) && (!defined(CUDA_FORCE_API_VERSION) || (CUDA_FORCE_API_VERSION >= 3020)) +#define __CUVID_DEVPTR64 +#endif +#endif + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +typedef void *CUvideodecoder; +typedef struct _CUcontextlock_st *CUvideoctxlock; + +/*********************************************************************************/ +//! \enum cudaVideoCodec +//! Video codec enums +//! These enums are used in CUVIDDECODECREATEINFO and CUVIDDECODECAPS structures +/*********************************************************************************/ +typedef enum cudaVideoCodec_enum { + cudaVideoCodec_MPEG1=0, /**< MPEG1 */ + cudaVideoCodec_MPEG2, /**< MPEG2 */ + cudaVideoCodec_MPEG4, /**< MPEG4 */ + cudaVideoCodec_VC1, /**< VC1 */ + cudaVideoCodec_H264, /**< H264 */ + cudaVideoCodec_JPEG, /**< JPEG */ + cudaVideoCodec_H264_SVC, /**< H264-SVC */ + cudaVideoCodec_H264_MVC, /**< H264-MVC */ + cudaVideoCodec_HEVC, /**< HEVC */ + cudaVideoCodec_VP8, /**< VP8 */ + cudaVideoCodec_VP9, /**< VP9 */ + cudaVideoCodec_AV1, /**< AV1 */ + cudaVideoCodec_NumCodecs, /**< Max codecs */ + // Uncompressed YUV + cudaVideoCodec_YUV420 = (('I'<<24)|('Y'<<16)|('U'<<8)|('V')), /**< Y,U,V (4:2:0) */ + cudaVideoCodec_YV12 = (('Y'<<24)|('V'<<16)|('1'<<8)|('2')), /**< Y,V,U (4:2:0) */ + cudaVideoCodec_NV12 = (('N'<<24)|('V'<<16)|('1'<<8)|('2')), /**< Y,UV (4:2:0) */ + cudaVideoCodec_YUYV = (('Y'<<24)|('U'<<16)|('Y'<<8)|('V')), /**< YUYV/YUY2 (4:2:2) */ + cudaVideoCodec_UYVY = (('U'<<24)|('Y'<<16)|('V'<<8)|('Y')) /**< UYVY (4:2:2) */ +} cudaVideoCodec; + +/*********************************************************************************/ +//! \enum cudaVideoSurfaceFormat +//! Video surface format enums used for output format of decoded output +//! These enums are used in CUVIDDECODECREATEINFO structure +/*********************************************************************************/ +typedef enum cudaVideoSurfaceFormat_enum { + cudaVideoSurfaceFormat_NV12=0, /**< Semi-Planar YUV [Y plane followed by interleaved UV plane] */ + cudaVideoSurfaceFormat_P016=1, /**< 16 bit Semi-Planar YUV [Y plane followed by interleaved UV plane]. + Can be used for 10 bit(6LSB bits 0), 12 bit (4LSB bits 0) */ + cudaVideoSurfaceFormat_YUV444=2, /**< Planar YUV [Y plane followed by U and V planes] */ + cudaVideoSurfaceFormat_YUV444_16Bit=3, /**< 16 bit Planar YUV [Y plane followed by U and V planes]. + Can be used for 10 bit(6LSB bits 0), 12 bit (4LSB bits 0) */ +} cudaVideoSurfaceFormat; + +/******************************************************************************************************************/ +//! \enum cudaVideoDeinterlaceMode +//! Deinterlacing mode enums +//! These enums are used in CUVIDDECODECREATEINFO structure +//! Use cudaVideoDeinterlaceMode_Weave for progressive content and for content that doesn't need deinterlacing +//! cudaVideoDeinterlaceMode_Adaptive needs more video memory than other DImodes +/******************************************************************************************************************/ +typedef enum cudaVideoDeinterlaceMode_enum { + cudaVideoDeinterlaceMode_Weave=0, /**< Weave both fields (no deinterlacing) */ + cudaVideoDeinterlaceMode_Bob, /**< Drop one field */ + cudaVideoDeinterlaceMode_Adaptive /**< Adaptive deinterlacing */ +} cudaVideoDeinterlaceMode; + +/**************************************************************************************************************/ +//! \enum cudaVideoChromaFormat +//! Chroma format enums +//! These enums are used in CUVIDDECODECREATEINFO and CUVIDDECODECAPS structures +/**************************************************************************************************************/ +typedef enum cudaVideoChromaFormat_enum { + cudaVideoChromaFormat_Monochrome=0, /**< MonoChrome */ + cudaVideoChromaFormat_420, /**< YUV 4:2:0 */ + cudaVideoChromaFormat_422, /**< YUV 4:2:2 */ + cudaVideoChromaFormat_444 /**< YUV 4:4:4 */ +} cudaVideoChromaFormat; + +/*************************************************************************************************************/ +//! \enum cudaVideoCreateFlags +//! Decoder flag enums to select preferred decode path +//! cudaVideoCreate_Default and cudaVideoCreate_PreferCUVID are most optimized, use these whenever possible +/*************************************************************************************************************/ +typedef enum cudaVideoCreateFlags_enum { + cudaVideoCreate_Default = 0x00, /**< Default operation mode: use dedicated video engines */ + cudaVideoCreate_PreferCUDA = 0x01, /**< Use CUDA-based decoder (requires valid vidLock object for multi-threading) */ + cudaVideoCreate_PreferDXVA = 0x02, /**< Go through DXVA internally if possible (requires D3D9 interop) */ + cudaVideoCreate_PreferCUVID = 0x04 /**< Use dedicated video engines directly */ +} cudaVideoCreateFlags; + + +/*************************************************************************/ +//! \enum cuvidDecodeStatus +//! Decode status enums +//! These enums are used in CUVIDGETDECODESTATUS structure +/*************************************************************************/ +typedef enum cuvidDecodeStatus_enum +{ + cuvidDecodeStatus_Invalid = 0, // Decode status is not valid + cuvidDecodeStatus_InProgress = 1, // Decode is in progress + cuvidDecodeStatus_Success = 2, // Decode is completed without any errors + // 3 to 7 enums are reserved for future use + cuvidDecodeStatus_Error = 8, // Decode is completed with an error (error is not concealed) + cuvidDecodeStatus_Error_Concealed = 9, // Decode is completed with an error and error is concealed +} cuvidDecodeStatus; + +/**************************************************************************************************************/ +//! \struct CUVIDDECODECAPS; +//! This structure is used in cuvidGetDecoderCaps API +/**************************************************************************************************************/ +typedef struct _CUVIDDECODECAPS +{ + cudaVideoCodec eCodecType; /**< IN: cudaVideoCodec_XXX */ + cudaVideoChromaFormat eChromaFormat; /**< IN: cudaVideoChromaFormat_XXX */ + unsigned int nBitDepthMinus8; /**< IN: The Value "BitDepth minus 8" */ + unsigned int reserved1[3]; /**< Reserved for future use - set to zero */ + + unsigned char bIsSupported; /**< OUT: 1 if codec supported, 0 if not supported */ + unsigned char nNumNVDECs; /**< OUT: Number of NVDECs that can support IN params */ + unsigned short nOutputFormatMask; /**< OUT: each bit represents corresponding cudaVideoSurfaceFormat enum */ + unsigned int nMaxWidth; /**< OUT: Max supported coded width in pixels */ + unsigned int nMaxHeight; /**< OUT: Max supported coded height in pixels */ + unsigned int nMaxMBCount; /**< OUT: Max supported macroblock count + CodedWidth*CodedHeight/256 must be <= nMaxMBCount */ + unsigned short nMinWidth; /**< OUT: Min supported coded width in pixels */ + unsigned short nMinHeight; /**< OUT: Min supported coded height in pixels */ + unsigned char bIsHistogramSupported; /**< OUT: 1 if Y component histogram output is supported, 0 if not + Note: histogram is computed on original picture data before + any post-processing like scaling, cropping, etc. is applied */ + unsigned char nCounterBitDepth; /**< OUT: histogram counter bit depth */ + unsigned short nMaxHistogramBins; /**< OUT: Max number of histogram bins */ + unsigned int reserved3[10]; /**< Reserved for future use - set to zero */ +} CUVIDDECODECAPS; + +/**************************************************************************************************************/ +//! \struct CUVIDDECODECREATEINFO +//! This structure is used in cuvidCreateDecoder API +/**************************************************************************************************************/ +typedef struct _CUVIDDECODECREATEINFO +{ + unsigned long ulWidth; /**< IN: Coded sequence width in pixels */ + unsigned long ulHeight; /**< IN: Coded sequence height in pixels */ + unsigned long ulNumDecodeSurfaces; /**< IN: Maximum number of internal decode surfaces */ + cudaVideoCodec CodecType; /**< IN: cudaVideoCodec_XXX */ + cudaVideoChromaFormat ChromaFormat; /**< IN: cudaVideoChromaFormat_XXX */ + unsigned long ulCreationFlags; /**< IN: Decoder creation flags (cudaVideoCreateFlags_XXX) */ + unsigned long bitDepthMinus8; /**< IN: The value "BitDepth minus 8" */ + unsigned long ulIntraDecodeOnly; /**< IN: Set 1 only if video has all intra frames (default value is 0). This will + optimize video memory for Intra frames only decoding. The support is limited + to specific codecs - H264, HEVC, VP9, the flag will be ignored for codecs which + are not supported. However decoding might fail if the flag is enabled in case + of supported codecs for regular bit streams having P and/or B frames. */ + unsigned long ulMaxWidth; /**< IN: Coded sequence max width in pixels used with reconfigure Decoder */ + unsigned long ulMaxHeight; /**< IN: Coded sequence max height in pixels used with reconfigure Decoder */ + unsigned long Reserved1; /**< Reserved for future use - set to zero */ + /** + * IN: area of the frame that should be displayed + */ + struct { + short left; + short top; + short right; + short bottom; + } display_area; + + cudaVideoSurfaceFormat OutputFormat; /**< IN: cudaVideoSurfaceFormat_XXX */ + cudaVideoDeinterlaceMode DeinterlaceMode; /**< IN: cudaVideoDeinterlaceMode_XXX */ + unsigned long ulTargetWidth; /**< IN: Post-processed output width (Should be aligned to 2) */ + unsigned long ulTargetHeight; /**< IN: Post-processed output height (Should be aligned to 2) */ + unsigned long ulNumOutputSurfaces; /**< IN: Maximum number of output surfaces simultaneously mapped */ + CUvideoctxlock vidLock; /**< IN: If non-NULL, context lock used for synchronizing ownership of + the cuda context. Needed for cudaVideoCreate_PreferCUDA decode */ + /** + * IN: target rectangle in the output frame (for aspect ratio conversion) + * if a null rectangle is specified, {0,0,ulTargetWidth,ulTargetHeight} will be used + */ + struct { + short left; + short top; + short right; + short bottom; + } target_rect; + + unsigned long enableHistogram; /**< IN: enable histogram output, if supported */ + unsigned long Reserved2[4]; /**< Reserved for future use - set to zero */ +} CUVIDDECODECREATEINFO; + +/*********************************************************/ +//! \struct CUVIDH264DPBENTRY +//! H.264 DPB entry +//! This structure is used in CUVIDH264PICPARAMS structure +/*********************************************************/ +typedef struct _CUVIDH264DPBENTRY +{ + int PicIdx; /**< picture index of reference frame */ + int FrameIdx; /**< frame_num(short-term) or LongTermFrameIdx(long-term) */ + int is_long_term; /**< 0=short term reference, 1=long term reference */ + int not_existing; /**< non-existing reference frame (corresponding PicIdx should be set to -1) */ + int used_for_reference; /**< 0=unused, 1=top_field, 2=bottom_field, 3=both_fields */ + int FieldOrderCnt[2]; /**< field order count of top and bottom fields */ +} CUVIDH264DPBENTRY; + +/************************************************************/ +//! \struct CUVIDH264MVCEXT +//! H.264 MVC picture parameters ext +//! This structure is used in CUVIDH264PICPARAMS structure +/************************************************************/ +typedef struct _CUVIDH264MVCEXT +{ + int num_views_minus1; /**< Max number of coded views minus 1 in video : Range - 0 to 1023 */ + int view_id; /**< view identifier */ + unsigned char inter_view_flag; /**< 1 if used for inter-view prediction, 0 if not */ + unsigned char num_inter_view_refs_l0; /**< number of inter-view ref pics in RefPicList0 */ + unsigned char num_inter_view_refs_l1; /**< number of inter-view ref pics in RefPicList1 */ + unsigned char MVCReserved8Bits; /**< Reserved bits */ + int InterViewRefsL0[16]; /**< view id of the i-th view component for inter-view prediction in RefPicList0 */ + int InterViewRefsL1[16]; /**< view id of the i-th view component for inter-view prediction in RefPicList1 */ +} CUVIDH264MVCEXT; + +/*********************************************************/ +//! \struct CUVIDH264SVCEXT +//! H.264 SVC picture parameters ext +//! This structure is used in CUVIDH264PICPARAMS structure +/*********************************************************/ +typedef struct _CUVIDH264SVCEXT +{ + unsigned char profile_idc; + unsigned char level_idc; + unsigned char DQId; + unsigned char DQIdMax; + unsigned char disable_inter_layer_deblocking_filter_idc; + unsigned char ref_layer_chroma_phase_y_plus1; + signed char inter_layer_slice_alpha_c0_offset_div2; + signed char inter_layer_slice_beta_offset_div2; + + unsigned short DPBEntryValidFlag; + unsigned char inter_layer_deblocking_filter_control_present_flag; + unsigned char extended_spatial_scalability_idc; + unsigned char adaptive_tcoeff_level_prediction_flag; + unsigned char slice_header_restriction_flag; + unsigned char chroma_phase_x_plus1_flag; + unsigned char chroma_phase_y_plus1; + + unsigned char tcoeff_level_prediction_flag; + unsigned char constrained_intra_resampling_flag; + unsigned char ref_layer_chroma_phase_x_plus1_flag; + unsigned char store_ref_base_pic_flag; + unsigned char Reserved8BitsA; + unsigned char Reserved8BitsB; + + short scaled_ref_layer_left_offset; + short scaled_ref_layer_top_offset; + short scaled_ref_layer_right_offset; + short scaled_ref_layer_bottom_offset; + unsigned short Reserved16Bits; + struct _CUVIDPICPARAMS *pNextLayer; /**< Points to the picparams for the next layer to be decoded. + Linked list ends at the target layer. */ + int bRefBaseLayer; /**< whether to store ref base pic */ +} CUVIDH264SVCEXT; + +/******************************************************/ +//! \struct CUVIDH264PICPARAMS +//! H.264 picture parameters +//! This structure is used in CUVIDPICPARAMS structure +/******************************************************/ +typedef struct _CUVIDH264PICPARAMS +{ + // SPS + int log2_max_frame_num_minus4; + int pic_order_cnt_type; + int log2_max_pic_order_cnt_lsb_minus4; + int delta_pic_order_always_zero_flag; + int frame_mbs_only_flag; + int direct_8x8_inference_flag; + int num_ref_frames; // NOTE: shall meet level 4.1 restrictions + unsigned char residual_colour_transform_flag; + unsigned char bit_depth_luma_minus8; // Must be 0 (only 8-bit supported) + unsigned char bit_depth_chroma_minus8; // Must be 0 (only 8-bit supported) + unsigned char qpprime_y_zero_transform_bypass_flag; + // PPS + int entropy_coding_mode_flag; + int pic_order_present_flag; + int num_ref_idx_l0_active_minus1; + int num_ref_idx_l1_active_minus1; + int weighted_pred_flag; + int weighted_bipred_idc; + int pic_init_qp_minus26; + int deblocking_filter_control_present_flag; + int redundant_pic_cnt_present_flag; + int transform_8x8_mode_flag; + int MbaffFrameFlag; + int constrained_intra_pred_flag; + int chroma_qp_index_offset; + int second_chroma_qp_index_offset; + int ref_pic_flag; + int frame_num; + int CurrFieldOrderCnt[2]; + // DPB + CUVIDH264DPBENTRY dpb[16]; // List of reference frames within the DPB + // Quantization Matrices (raster-order) + unsigned char WeightScale4x4[6][16]; + unsigned char WeightScale8x8[2][64]; + // FMO/ASO + unsigned char fmo_aso_enable; + unsigned char num_slice_groups_minus1; + unsigned char slice_group_map_type; + signed char pic_init_qs_minus26; + unsigned int slice_group_change_rate_minus1; + union + { + unsigned long long slice_group_map_addr; + const unsigned char *pMb2SliceGroupMap; + } fmo; + unsigned int Reserved[12]; + // SVC/MVC + union + { + CUVIDH264MVCEXT mvcext; + CUVIDH264SVCEXT svcext; + }; +} CUVIDH264PICPARAMS; + + +/********************************************************/ +//! \struct CUVIDMPEG2PICPARAMS +//! MPEG-2 picture parameters +//! This structure is used in CUVIDPICPARAMS structure +/********************************************************/ +typedef struct _CUVIDMPEG2PICPARAMS +{ + int ForwardRefIdx; // Picture index of forward reference (P/B-frames) + int BackwardRefIdx; // Picture index of backward reference (B-frames) + int picture_coding_type; + int full_pel_forward_vector; + int full_pel_backward_vector; + int f_code[2][2]; + int intra_dc_precision; + int frame_pred_frame_dct; + int concealment_motion_vectors; + int q_scale_type; + int intra_vlc_format; + int alternate_scan; + int top_field_first; + // Quantization matrices (raster order) + unsigned char QuantMatrixIntra[64]; + unsigned char QuantMatrixInter[64]; +} CUVIDMPEG2PICPARAMS; + +// MPEG-4 has VOP types instead of Picture types +#define I_VOP 0 +#define P_VOP 1 +#define B_VOP 2 +#define S_VOP 3 + +/*******************************************************/ +//! \struct CUVIDMPEG4PICPARAMS +//! MPEG-4 picture parameters +//! This structure is used in CUVIDPICPARAMS structure +/*******************************************************/ +typedef struct _CUVIDMPEG4PICPARAMS +{ + int ForwardRefIdx; // Picture index of forward reference (P/B-frames) + int BackwardRefIdx; // Picture index of backward reference (B-frames) + // VOL + int video_object_layer_width; + int video_object_layer_height; + int vop_time_increment_bitcount; + int top_field_first; + int resync_marker_disable; + int quant_type; + int quarter_sample; + int short_video_header; + int divx_flags; + // VOP + int vop_coding_type; + int vop_coded; + int vop_rounding_type; + int alternate_vertical_scan_flag; + int interlaced; + int vop_fcode_forward; + int vop_fcode_backward; + int trd[2]; + int trb[2]; + // Quantization matrices (raster order) + unsigned char QuantMatrixIntra[64]; + unsigned char QuantMatrixInter[64]; + int gmc_enabled; +} CUVIDMPEG4PICPARAMS; + +/********************************************************/ +//! \struct CUVIDVC1PICPARAMS +//! VC1 picture parameters +//! This structure is used in CUVIDPICPARAMS structure +/********************************************************/ +typedef struct _CUVIDVC1PICPARAMS +{ + int ForwardRefIdx; /**< Picture index of forward reference (P/B-frames) */ + int BackwardRefIdx; /**< Picture index of backward reference (B-frames) */ + int FrameWidth; /**< Actual frame width */ + int FrameHeight; /**< Actual frame height */ + // PICTURE + int intra_pic_flag; /**< Set to 1 for I,BI frames */ + int ref_pic_flag; /**< Set to 1 for I,P frames */ + int progressive_fcm; /**< Progressive frame */ + // SEQUENCE + int profile; + int postprocflag; + int pulldown; + int interlace; + int tfcntrflag; + int finterpflag; + int psf; + int multires; + int syncmarker; + int rangered; + int maxbframes; + // ENTRYPOINT + int panscan_flag; + int refdist_flag; + int extended_mv; + int dquant; + int vstransform; + int loopfilter; + int fastuvmc; + int overlap; + int quantizer; + int extended_dmv; + int range_mapy_flag; + int range_mapy; + int range_mapuv_flag; + int range_mapuv; + int rangeredfrm; // range reduction state +} CUVIDVC1PICPARAMS; + +/***********************************************************/ +//! \struct CUVIDJPEGPICPARAMS +//! JPEG picture parameters +//! This structure is used in CUVIDPICPARAMS structure +/***********************************************************/ +typedef struct _CUVIDJPEGPICPARAMS +{ + int Reserved; +} CUVIDJPEGPICPARAMS; + + +/*******************************************************/ +//! \struct CUVIDHEVCPICPARAMS +//! HEVC picture parameters +//! This structure is used in CUVIDPICPARAMS structure +/*******************************************************/ +typedef struct _CUVIDHEVCPICPARAMS +{ + // sps + int pic_width_in_luma_samples; + int pic_height_in_luma_samples; + unsigned char log2_min_luma_coding_block_size_minus3; + unsigned char log2_diff_max_min_luma_coding_block_size; + unsigned char log2_min_transform_block_size_minus2; + unsigned char log2_diff_max_min_transform_block_size; + unsigned char pcm_enabled_flag; + unsigned char log2_min_pcm_luma_coding_block_size_minus3; + unsigned char log2_diff_max_min_pcm_luma_coding_block_size; + unsigned char pcm_sample_bit_depth_luma_minus1; + + unsigned char pcm_sample_bit_depth_chroma_minus1; + unsigned char pcm_loop_filter_disabled_flag; + unsigned char strong_intra_smoothing_enabled_flag; + unsigned char max_transform_hierarchy_depth_intra; + unsigned char max_transform_hierarchy_depth_inter; + unsigned char amp_enabled_flag; + unsigned char separate_colour_plane_flag; + unsigned char log2_max_pic_order_cnt_lsb_minus4; + + unsigned char num_short_term_ref_pic_sets; + unsigned char long_term_ref_pics_present_flag; + unsigned char num_long_term_ref_pics_sps; + unsigned char sps_temporal_mvp_enabled_flag; + unsigned char sample_adaptive_offset_enabled_flag; + unsigned char scaling_list_enable_flag; + unsigned char IrapPicFlag; + unsigned char IdrPicFlag; + + unsigned char bit_depth_luma_minus8; + unsigned char bit_depth_chroma_minus8; + //sps/pps extension fields + unsigned char log2_max_transform_skip_block_size_minus2; + unsigned char log2_sao_offset_scale_luma; + unsigned char log2_sao_offset_scale_chroma; + unsigned char high_precision_offsets_enabled_flag; + unsigned char reserved1[10]; + + // pps + unsigned char dependent_slice_segments_enabled_flag; + unsigned char slice_segment_header_extension_present_flag; + unsigned char sign_data_hiding_enabled_flag; + unsigned char cu_qp_delta_enabled_flag; + unsigned char diff_cu_qp_delta_depth; + signed char init_qp_minus26; + signed char pps_cb_qp_offset; + signed char pps_cr_qp_offset; + + unsigned char constrained_intra_pred_flag; + unsigned char weighted_pred_flag; + unsigned char weighted_bipred_flag; + unsigned char transform_skip_enabled_flag; + unsigned char transquant_bypass_enabled_flag; + unsigned char entropy_coding_sync_enabled_flag; + unsigned char log2_parallel_merge_level_minus2; + unsigned char num_extra_slice_header_bits; + + unsigned char loop_filter_across_tiles_enabled_flag; + unsigned char loop_filter_across_slices_enabled_flag; + unsigned char output_flag_present_flag; + unsigned char num_ref_idx_l0_default_active_minus1; + unsigned char num_ref_idx_l1_default_active_minus1; + unsigned char lists_modification_present_flag; + unsigned char cabac_init_present_flag; + unsigned char pps_slice_chroma_qp_offsets_present_flag; + + unsigned char deblocking_filter_override_enabled_flag; + unsigned char pps_deblocking_filter_disabled_flag; + signed char pps_beta_offset_div2; + signed char pps_tc_offset_div2; + unsigned char tiles_enabled_flag; + unsigned char uniform_spacing_flag; + unsigned char num_tile_columns_minus1; + unsigned char num_tile_rows_minus1; + + unsigned short column_width_minus1[21]; + unsigned short row_height_minus1[21]; + + // sps and pps extension HEVC-main 444 + unsigned char sps_range_extension_flag; + unsigned char transform_skip_rotation_enabled_flag; + unsigned char transform_skip_context_enabled_flag; + unsigned char implicit_rdpcm_enabled_flag; + + unsigned char explicit_rdpcm_enabled_flag; + unsigned char extended_precision_processing_flag; + unsigned char intra_smoothing_disabled_flag; + unsigned char persistent_rice_adaptation_enabled_flag; + + unsigned char cabac_bypass_alignment_enabled_flag; + unsigned char pps_range_extension_flag; + unsigned char cross_component_prediction_enabled_flag; + unsigned char chroma_qp_offset_list_enabled_flag; + + unsigned char diff_cu_chroma_qp_offset_depth; + unsigned char chroma_qp_offset_list_len_minus1; + signed char cb_qp_offset_list[6]; + + signed char cr_qp_offset_list[6]; + unsigned char reserved2[2]; + + unsigned int reserved3[8]; + + // RefPicSets + int NumBitsForShortTermRPSInSlice; + int NumDeltaPocsOfRefRpsIdx; + int NumPocTotalCurr; + int NumPocStCurrBefore; + int NumPocStCurrAfter; + int NumPocLtCurr; + int CurrPicOrderCntVal; + int RefPicIdx[16]; // [refpic] Indices of valid reference pictures (-1 if unused for reference) + int PicOrderCntVal[16]; // [refpic] + unsigned char IsLongTerm[16]; // [refpic] 0=not a long-term reference, 1=long-term reference + unsigned char RefPicSetStCurrBefore[8]; // [0..NumPocStCurrBefore-1] -> refpic (0..15) + unsigned char RefPicSetStCurrAfter[8]; // [0..NumPocStCurrAfter-1] -> refpic (0..15) + unsigned char RefPicSetLtCurr[8]; // [0..NumPocLtCurr-1] -> refpic (0..15) + unsigned char RefPicSetInterLayer0[8]; + unsigned char RefPicSetInterLayer1[8]; + unsigned int reserved4[12]; + + // scaling lists (diag order) + unsigned char ScalingList4x4[6][16]; // [matrixId][i] + unsigned char ScalingList8x8[6][64]; // [matrixId][i] + unsigned char ScalingList16x16[6][64]; // [matrixId][i] + unsigned char ScalingList32x32[2][64]; // [matrixId][i] + unsigned char ScalingListDCCoeff16x16[6]; // [matrixId] + unsigned char ScalingListDCCoeff32x32[2]; // [matrixId] +} CUVIDHEVCPICPARAMS; + + +/***********************************************************/ +//! \struct CUVIDVP8PICPARAMS +//! VP8 picture parameters +//! This structure is used in CUVIDPICPARAMS structure +/***********************************************************/ +typedef struct _CUVIDVP8PICPARAMS +{ + int width; + int height; + unsigned int first_partition_size; + //Frame Indexes + unsigned char LastRefIdx; + unsigned char GoldenRefIdx; + unsigned char AltRefIdx; + union { + struct { + unsigned char frame_type : 1; /**< 0 = KEYFRAME, 1 = INTERFRAME */ + unsigned char version : 3; + unsigned char show_frame : 1; + unsigned char update_mb_segmentation_data : 1; /**< Must be 0 if segmentation is not enabled */ + unsigned char Reserved2Bits : 2; + }vp8_frame_tag; + unsigned char wFrameTagFlags; + }; + unsigned char Reserved1[4]; + unsigned int Reserved2[3]; +} CUVIDVP8PICPARAMS; + +/***********************************************************/ +//! \struct CUVIDVP9PICPARAMS +//! VP9 picture parameters +//! This structure is used in CUVIDPICPARAMS structure +/***********************************************************/ +typedef struct _CUVIDVP9PICPARAMS +{ + unsigned int width; + unsigned int height; + + //Frame Indices + unsigned char LastRefIdx; + unsigned char GoldenRefIdx; + unsigned char AltRefIdx; + unsigned char colorSpace; + + unsigned short profile : 3; + unsigned short frameContextIdx : 2; + unsigned short frameType : 1; + unsigned short showFrame : 1; + unsigned short errorResilient : 1; + unsigned short frameParallelDecoding : 1; + unsigned short subSamplingX : 1; + unsigned short subSamplingY : 1; + unsigned short intraOnly : 1; + unsigned short allow_high_precision_mv : 1; + unsigned short refreshEntropyProbs : 1; + unsigned short reserved2Bits : 2; + + unsigned short reserved16Bits; + + unsigned char refFrameSignBias[4]; + + unsigned char bitDepthMinus8Luma; + unsigned char bitDepthMinus8Chroma; + unsigned char loopFilterLevel; + unsigned char loopFilterSharpness; + + unsigned char modeRefLfEnabled; + unsigned char log2_tile_columns; + unsigned char log2_tile_rows; + + unsigned char segmentEnabled : 1; + unsigned char segmentMapUpdate : 1; + unsigned char segmentMapTemporalUpdate : 1; + unsigned char segmentFeatureMode : 1; + unsigned char reserved4Bits : 4; + + + unsigned char segmentFeatureEnable[8][4]; + short segmentFeatureData[8][4]; + unsigned char mb_segment_tree_probs[7]; + unsigned char segment_pred_probs[3]; + unsigned char reservedSegment16Bits[2]; + + int qpYAc; + int qpYDc; + int qpChDc; + int qpChAc; + + unsigned int activeRefIdx[3]; + unsigned int resetFrameContext; + unsigned int mcomp_filter_type; + unsigned int mbRefLfDelta[4]; + unsigned int mbModeLfDelta[2]; + unsigned int frameTagSize; + unsigned int offsetToDctParts; + unsigned int reserved128Bits[4]; + +} CUVIDVP9PICPARAMS; + +/***********************************************************/ +//! \struct CUVIDAV1PICPARAMS +//! AV1 picture parameters +//! This structure is used in CUVIDPICPARAMS structure +/***********************************************************/ +typedef struct _CUVIDAV1PICPARAMS +{ + unsigned int width; // coded width, if superres enabled then it is upscaled width + unsigned int height; // coded height + unsigned int frame_offset; // defined as order_hint in AV1 specification + int decodePicIdx; // decoded output pic index, if film grain enabled, it will keep decoded (without film grain) output + // It can be used as reference frame for future frames + + // sequence header + unsigned int profile : 3; // 0 = profile0, 1 = profile1, 2 = profile2 + unsigned int use_128x128_superblock : 1; // superblock size 0:64x64, 1: 128x128 + unsigned int subsampling_x : 1; // (subsampling_x, _y) 1,1 = 420, 1,0 = 422, 0,0 = 444 + unsigned int subsampling_y : 1; + unsigned int mono_chrome : 1; // for monochrome content, mono_chrome = 1 and (subsampling_x, _y) should be 1,1 + unsigned int bit_depth_minus8 : 4; // bit depth minus 8 + unsigned int enable_filter_intra : 1; // tool enable in seq level, 0 : disable 1: frame header control + unsigned int enable_intra_edge_filter : 1; // intra edge filtering process, 0 : disable 1: enabled + unsigned int enable_interintra_compound : 1; // interintra, 0 : not present 1: present + unsigned int enable_masked_compound : 1; // 1: mode info for inter blocks may contain the syntax element compound_type. + // 0: syntax element compound_type will not be present + unsigned int enable_dual_filter : 1; // vertical and horiz filter selection, 1: enable and 0: disable + unsigned int enable_order_hint : 1; // order hint, and related tools, 1: enable and 0: disable + unsigned int order_hint_bits_minus1 : 3; // is used to compute OrderHintBits + unsigned int enable_jnt_comp : 1; // joint compound modes, 1: enable and 0: disable + unsigned int enable_superres : 1; // superres in seq level, 0 : disable 1: frame level control + unsigned int enable_cdef : 1; // cdef filtering in seq level, 0 : disable 1: frame level control + unsigned int enable_restoration : 1; // loop restoration filtering in seq level, 0 : disable 1: frame level control + unsigned int enable_fgs : 1; // defined as film_grain_params_present in AV1 specification + unsigned int reserved0_7bits : 7; // reserved bits; must be set to 0 + + // frame header + unsigned int frame_type : 2 ; // 0:Key frame, 1:Inter frame, 2:intra only, 3:s-frame + unsigned int show_frame : 1 ; // show_frame = 1 implies that frame should be immediately output once decoded + unsigned int disable_cdf_update : 1; // CDF update during symbol decoding, 1: disabled, 0: enabled + unsigned int allow_screen_content_tools : 1; // 1: intra blocks may use palette encoding, 0: palette encoding is never used + unsigned int force_integer_mv : 1; // 1: motion vectors will always be integers, 0: can contain fractional bits + unsigned int coded_denom : 3; // coded_denom of the superres scale as specified in AV1 specification + unsigned int allow_intrabc : 1; // 1: intra block copy may be used, 0: intra block copy is not allowed + unsigned int allow_high_precision_mv : 1; // 1/8 precision mv enable + unsigned int interp_filter : 3; // interpolation filter. Refer to section 6.8.9 of the AV1 specification Version 1.0.0 with Errata 1 + unsigned int switchable_motion_mode : 1; // defined as is_motion_mode_switchable in AV1 specification + unsigned int use_ref_frame_mvs : 1; // 1: current frame can use the previous frame mv information, 0: will not use. + unsigned int disable_frame_end_update_cdf : 1; // 1: indicates that the end of frame CDF update is disabled + unsigned int delta_q_present : 1; // quantizer index delta values are present in the block level + unsigned int delta_q_res : 2; // left shift which should be applied to decoded quantizer index delta values + unsigned int using_qmatrix : 1; // 1: quantizer matrix will be used to compute quantizers + unsigned int coded_lossless : 1; // 1: all segments use lossless coding + unsigned int use_superres : 1; // 1: superres enabled for frame + unsigned int tx_mode : 2; // 0: ONLY4x4,1:LARGEST,2:SELECT + unsigned int reference_mode : 1; // 0: SINGLE, 1: SELECT + unsigned int allow_warped_motion : 1; // 1: allow_warped_motion may be present, 0: allow_warped_motion will not be present + unsigned int reduced_tx_set : 1; // 1: frame is restricted to subset of the full set of transform types, 0: no such restriction + unsigned int skip_mode : 1; // 1: most of the mode info is skipped, 0: mode info is not skipped + unsigned int reserved1_3bits : 3; // reserved bits; must be set to 0 + + // tiling info + unsigned int num_tile_cols : 8; // number of tiles across the frame., max is 64 + unsigned int num_tile_rows : 8; // number of tiles down the frame., max is 64 + unsigned int context_update_tile_id : 16; // specifies which tile to use for the CDF update + unsigned short tile_widths[64]; // Width of each column in superblocks + unsigned short tile_heights[64]; // height of each row in superblocks + + // CDEF - refer to section 6.10.14 of the AV1 specification Version 1.0.0 with Errata 1 + unsigned char cdef_damping_minus_3 : 2; // controls the amount of damping in the deringing filter + unsigned char cdef_bits : 2; // the number of bits needed to specify which CDEF filter to apply + unsigned char reserved2_4bits : 4; // reserved bits; must be set to 0 + unsigned char cdef_y_strength[8]; // 0-3 bits: y_pri_strength, 4-7 bits y_sec_strength + unsigned char cdef_uv_strength[8]; // 0-3 bits: uv_pri_strength, 4-7 bits uv_sec_strength + + // SkipModeFrames + unsigned char SkipModeFrame0 : 4; // specifies the frames to use for compound prediction when skip_mode is equal to 1. + unsigned char SkipModeFrame1 : 4; + + // qp information - refer to section 6.8.11 of the AV1 specification Version 1.0.0 with Errata 1 + unsigned char base_qindex; // indicates the base frame qindex. Defined as base_q_idx in AV1 specification + char qp_y_dc_delta_q; // indicates the Y DC quantizer relative to base_q_idx. Defined as DeltaQYDc in AV1 specification + char qp_u_dc_delta_q; // indicates the U DC quantizer relative to base_q_idx. Defined as DeltaQUDc in AV1 specification + char qp_v_dc_delta_q; // indicates the V DC quantizer relative to base_q_idx. Defined as DeltaQVDc in AV1 specification + char qp_u_ac_delta_q; // indicates the U AC quantizer relative to base_q_idx. Defined as DeltaQUAc in AV1 specification + char qp_v_ac_delta_q; // indicates the V AC quantizer relative to base_q_idx. Defined as DeltaQVAc in AV1 specification + unsigned char qm_y; // specifies the level in the quantizer matrix that should be used for luma plane decoding + unsigned char qm_u; // specifies the level in the quantizer matrix that should be used for chroma U plane decoding + unsigned char qm_v; // specifies the level in the quantizer matrix that should be used for chroma V plane decoding + + // segmentation - refer to section 6.8.13 of the AV1 specification Version 1.0.0 with Errata 1 + unsigned char segmentation_enabled : 1; // 1 indicates that this frame makes use of the segmentation tool + unsigned char segmentation_update_map : 1; // 1 indicates that the segmentation map are updated during the decoding of this frame + unsigned char segmentation_update_data : 1; // 1 indicates that new parameters are about to be specified for each segment + unsigned char segmentation_temporal_update : 1; // 1 indicates that the updates to the segmentation map are coded relative to the existing segmentation map + unsigned char reserved3_4bits : 4; // reserved bits; must be set to 0 + short segmentation_feature_data[8][8]; // specifies the feature data for a segment feature + unsigned char segmentation_feature_mask[8]; // indicates that the corresponding feature is unused or feature value is coded + + // loopfilter - refer to section 6.8.10 of the AV1 specification Version 1.0.0 with Errata 1 + unsigned char loop_filter_level[2]; // contains loop filter strength values + unsigned char loop_filter_level_u; // loop filter strength value of U plane + unsigned char loop_filter_level_v; // loop filter strength value of V plane + unsigned char loop_filter_sharpness; // indicates the sharpness level + char loop_filter_ref_deltas[8]; // contains the adjustment needed for the filter level based on the chosen reference frame + char loop_filter_mode_deltas[2]; // contains the adjustment needed for the filter level based on the chosen mode + unsigned char loop_filter_delta_enabled : 1; // indicates that the filter level depends on the mode and reference frame used to predict a block + unsigned char loop_filter_delta_update : 1; // indicates that additional syntax elements are present that specify which mode and + // reference frame deltas are to be updated + unsigned char delta_lf_present : 1; // specifies whether loop filter delta values are present in the block level + unsigned char delta_lf_res : 2; // specifies the left shift to apply to the decoded loop filter values + unsigned char delta_lf_multi : 1; // separate loop filter deltas for Hy,Vy,U,V edges + unsigned char reserved4_2bits : 2; // reserved bits; must be set to 0 + + // restoration - refer to section 6.10.15 of the AV1 specification Version 1.0.0 with Errata 1 + unsigned char lr_unit_size[3]; // specifies the size of loop restoration units: 0: 32, 1: 64, 2: 128, 3: 256 + unsigned char lr_type[3] ; // used to compute FrameRestorationType + + // reference frames + unsigned char primary_ref_frame; // specifies which reference frame contains the CDF values and other state that should be + // loaded at the start of the frame + unsigned char ref_frame_map[8]; // frames in dpb that can be used as reference for current or future frames + + unsigned char temporal_layer_id : 4; // temporal layer id + unsigned char spatial_layer_id : 4; // spatial layer id + + unsigned char reserved5_32bits[4]; // reserved bits; must be set to 0 + + // ref frame list + struct + { + unsigned int width; + unsigned int height; + unsigned char index; + unsigned char reserved24Bits[3]; // reserved bits; must be set to 0 + } ref_frame[7]; // frames used as reference frame for current frame. + + // global motion + struct { + unsigned char invalid : 1; + unsigned char wmtype : 2; // defined as GmType in AV1 specification + unsigned char reserved5Bits : 5; // reserved bits; must be set to 0 + char reserved24Bits[3]; // reserved bits; must be set to 0 + int wmmat[6]; // defined as gm_params[] in AV1 specification + } global_motion[7]; // global motion params for reference frames + + // film grain params - refer to section 6.8.20 of the AV1 specification Version 1.0.0 with Errata 1 + unsigned short apply_grain : 1; + unsigned short overlap_flag : 1; + unsigned short scaling_shift_minus8 : 2; + unsigned short chroma_scaling_from_luma : 1; + unsigned short ar_coeff_lag : 2; + unsigned short ar_coeff_shift_minus6 : 2; + unsigned short grain_scale_shift : 2; + unsigned short clip_to_restricted_range : 1; + unsigned short reserved6_4bits : 4; // reserved bits; must be set to 0 + unsigned char num_y_points; + unsigned char scaling_points_y[14][2]; + unsigned char num_cb_points; + unsigned char scaling_points_cb[10][2]; + unsigned char num_cr_points; + unsigned char scaling_points_cr[10][2]; + unsigned char reserved7_8bits; // reserved bits; must be set to 0 + unsigned short random_seed; + short ar_coeffs_y[24]; + short ar_coeffs_cb[25]; + short ar_coeffs_cr[25]; + unsigned char cb_mult; + unsigned char cb_luma_mult; + short cb_offset; + unsigned char cr_mult; + unsigned char cr_luma_mult; + short cr_offset; + + int reserved[7]; // reserved bits; must be set to 0 +} CUVIDAV1PICPARAMS; + +/******************************************************************************************/ +//! \struct CUVIDPICPARAMS +//! Picture parameters for decoding +//! This structure is used in cuvidDecodePicture API +//! IN for cuvidDecodePicture +/******************************************************************************************/ +typedef struct _CUVIDPICPARAMS +{ + int PicWidthInMbs; /**< IN: Coded frame size in macroblocks */ + int FrameHeightInMbs; /**< IN: Coded frame height in macroblocks */ + int CurrPicIdx; /**< IN: Output index of the current picture */ + int field_pic_flag; /**< IN: 0=frame picture, 1=field picture */ + int bottom_field_flag; /**< IN: 0=top field, 1=bottom field (ignored if field_pic_flag=0) */ + int second_field; /**< IN: Second field of a complementary field pair */ + // Bitstream data + unsigned int nBitstreamDataLen; /**< IN: Number of bytes in bitstream data buffer */ + const unsigned char *pBitstreamData; /**< IN: Ptr to bitstream data for this picture (slice-layer) */ + unsigned int nNumSlices; /**< IN: Number of slices in this picture */ + const unsigned int *pSliceDataOffsets; /**< IN: nNumSlices entries, contains offset of each slice within + the bitstream data buffer */ + int ref_pic_flag; /**< IN: This picture is a reference picture */ + int intra_pic_flag; /**< IN: This picture is entirely intra coded */ + unsigned int Reserved[30]; /**< Reserved for future use */ + // IN: Codec-specific data + union { + CUVIDMPEG2PICPARAMS mpeg2; /**< Also used for MPEG-1 */ + CUVIDH264PICPARAMS h264; + CUVIDVC1PICPARAMS vc1; + CUVIDMPEG4PICPARAMS mpeg4; + CUVIDJPEGPICPARAMS jpeg; + CUVIDHEVCPICPARAMS hevc; + CUVIDVP8PICPARAMS vp8; + CUVIDVP9PICPARAMS vp9; + CUVIDAV1PICPARAMS av1; + unsigned int CodecReserved[1024]; + } CodecSpecific; +} CUVIDPICPARAMS; + + +/******************************************************/ +//! \struct CUVIDPROCPARAMS +//! Picture parameters for postprocessing +//! This structure is used in cuvidMapVideoFrame API +/******************************************************/ +typedef struct _CUVIDPROCPARAMS +{ + int progressive_frame; /**< IN: Input is progressive (deinterlace_mode will be ignored) */ + int second_field; /**< IN: Output the second field (ignored if deinterlace mode is Weave) */ + int top_field_first; /**< IN: Input frame is top field first (1st field is top, 2nd field is bottom) */ + int unpaired_field; /**< IN: Input only contains one field (2nd field is invalid) */ + // The fields below are used for raw YUV input + unsigned int reserved_flags; /**< Reserved for future use (set to zero) */ + unsigned int reserved_zero; /**< Reserved (set to zero) */ + unsigned long long raw_input_dptr; /**< IN: Input CUdeviceptr for raw YUV extensions */ + unsigned int raw_input_pitch; /**< IN: pitch in bytes of raw YUV input (should be aligned appropriately) */ + unsigned int raw_input_format; /**< IN: Input YUV format (cudaVideoCodec_enum) */ + unsigned long long raw_output_dptr; /**< IN: Output CUdeviceptr for raw YUV extensions */ + unsigned int raw_output_pitch; /**< IN: pitch in bytes of raw YUV output (should be aligned appropriately) */ + unsigned int Reserved1; /**< Reserved for future use (set to zero) */ + CUstream output_stream; /**< IN: stream object used by cuvidMapVideoFrame */ + unsigned int Reserved[46]; /**< Reserved for future use (set to zero) */ + unsigned long long *histogram_dptr; /**< OUT: Output CUdeviceptr for histogram extensions */ + void *Reserved2[1]; /**< Reserved for future use (set to zero) */ +} CUVIDPROCPARAMS; + +/*********************************************************************************************************/ +//! \struct CUVIDGETDECODESTATUS +//! Struct for reporting decode status. +//! This structure is used in cuvidGetDecodeStatus API. +/*********************************************************************************************************/ +typedef struct _CUVIDGETDECODESTATUS +{ + cuvidDecodeStatus decodeStatus; + unsigned int reserved[31]; + void *pReserved[8]; +} CUVIDGETDECODESTATUS; + +/****************************************************/ +//! \struct CUVIDRECONFIGUREDECODERINFO +//! Struct for decoder reset +//! This structure is used in cuvidReconfigureDecoder() API +/****************************************************/ +typedef struct _CUVIDRECONFIGUREDECODERINFO +{ + unsigned int ulWidth; /**< IN: Coded sequence width in pixels, MUST be < = ulMaxWidth defined at CUVIDDECODECREATEINFO */ + unsigned int ulHeight; /**< IN: Coded sequence height in pixels, MUST be < = ulMaxHeight defined at CUVIDDECODECREATEINFO */ + unsigned int ulTargetWidth; /**< IN: Post processed output width */ + unsigned int ulTargetHeight; /**< IN: Post Processed output height */ + unsigned int ulNumDecodeSurfaces; /**< IN: Maximum number of internal decode surfaces */ + unsigned int reserved1[12]; /**< Reserved for future use. Set to Zero */ + /** + * IN: Area of frame to be displayed. Use-case : Source Cropping + */ + struct { + short left; + short top; + short right; + short bottom; + } display_area; + /** + * IN: Target Rectangle in the OutputFrame. Use-case : Aspect ratio Conversion + */ + struct { + short left; + short top; + short right; + short bottom; + } target_rect; + unsigned int reserved2[11]; /**< Reserved for future use. Set to Zero */ +} CUVIDRECONFIGUREDECODERINFO; + + +/***********************************************************************************************************/ +//! VIDEO_DECODER +//! +//! In order to minimize decode latencies, there should be always at least 2 pictures in the decode +//! queue at any time, in order to make sure that all decode engines are always busy. +//! +//! Overall data flow: +//! - cuvidGetDecoderCaps(...) +//! - cuvidCreateDecoder(...) +//! - For each picture: +//! + cuvidDecodePicture(N) +//! + cuvidMapVideoFrame(N-4) +//! + do some processing in cuda +//! + cuvidUnmapVideoFrame(N-4) +//! + cuvidDecodePicture(N+1) +//! + cuvidMapVideoFrame(N-3) +//! + ... +//! - cuvidDestroyDecoder(...) +//! +//! NOTE: +//! - When the cuda context is created from a D3D device, the D3D device must also be created +//! with the D3DCREATE_MULTITHREADED flag. +//! - There is a limit to how many pictures can be mapped simultaneously (ulNumOutputSurfaces) +//! - cuvidDecodePicture may block the calling thread if there are too many pictures pending +//! in the decode queue +/***********************************************************************************************************/ + + +/**********************************************************************************************************************/ +//! \fn CUresult CUDAAPI cuvidGetDecoderCaps(CUVIDDECODECAPS *pdc) +//! Queries decode capabilities of NVDEC-HW based on CodecType, ChromaFormat and BitDepthMinus8 parameters. +//! 1. Application fills IN parameters CodecType, ChromaFormat and BitDepthMinus8 of CUVIDDECODECAPS structure +//! 2. On calling cuvidGetDecoderCaps, driver fills OUT parameters if the IN parameters are supported +//! If IN parameters passed to the driver are not supported by NVDEC-HW, then all OUT params are set to 0. +//! E.g. on Geforce GTX 960: +//! App fills - eCodecType = cudaVideoCodec_H264; eChromaFormat = cudaVideoChromaFormat_420; nBitDepthMinus8 = 0; +//! Given IN parameters are supported, hence driver fills: bIsSupported = 1; nMinWidth = 48; nMinHeight = 16; +//! nMaxWidth = 4096; nMaxHeight = 4096; nMaxMBCount = 65536; +//! CodedWidth*CodedHeight/256 must be less than or equal to nMaxMBCount +/**********************************************************************************************************************/ +extern CUresult CUDAAPI cuvidGetDecoderCaps(CUVIDDECODECAPS *pdc); + +/*****************************************************************************************************/ +//! \fn CUresult CUDAAPI cuvidCreateDecoder(CUvideodecoder *phDecoder, CUVIDDECODECREATEINFO *pdci) +//! Create the decoder object based on pdci. A handle to the created decoder is returned +/*****************************************************************************************************/ +extern CUresult CUDAAPI cuvidCreateDecoder(CUvideodecoder *phDecoder, CUVIDDECODECREATEINFO *pdci); + +/*****************************************************************************************************/ +//! \fn CUresult CUDAAPI cuvidDestroyDecoder(CUvideodecoder hDecoder) +//! Destroy the decoder object +/*****************************************************************************************************/ +extern CUresult CUDAAPI cuvidDestroyDecoder(CUvideodecoder hDecoder); + +/*****************************************************************************************************/ +//! \fn CUresult CUDAAPI cuvidDecodePicture(CUvideodecoder hDecoder, CUVIDPICPARAMS *pPicParams) +//! Decode a single picture (field or frame) +//! Kicks off HW decoding +/*****************************************************************************************************/ +extern CUresult CUDAAPI cuvidDecodePicture(CUvideodecoder hDecoder, CUVIDPICPARAMS *pPicParams); + +/************************************************************************************************************/ +//! \fn CUresult CUDAAPI cuvidGetDecodeStatus(CUvideodecoder hDecoder, int nPicIdx); +//! Get the decode status for frame corresponding to nPicIdx +//! API is supported for Maxwell and above generation GPUs. +//! API is currently supported for HEVC, H264 and JPEG codecs. +//! API returns CUDA_ERROR_NOT_SUPPORTED error code for unsupported GPU or codec. +/************************************************************************************************************/ +extern CUresult CUDAAPI cuvidGetDecodeStatus(CUvideodecoder hDecoder, int nPicIdx, CUVIDGETDECODESTATUS* pDecodeStatus); + +/*********************************************************************************************************/ +//! \fn CUresult CUDAAPI cuvidReconfigureDecoder(CUvideodecoder hDecoder, CUVIDRECONFIGUREDECODERINFO *pDecReconfigParams) +//! Used to reuse single decoder for multiple clips. Currently supports resolution change, resize params, display area +//! params, target area params change for same codec. Must be called during CUVIDPARSERPARAMS::pfnSequenceCallback +/*********************************************************************************************************/ +extern CUresult CUDAAPI cuvidReconfigureDecoder(CUvideodecoder hDecoder, CUVIDRECONFIGUREDECODERINFO *pDecReconfigParams); + + +#if !defined(__CUVID_DEVPTR64) || defined(__CUVID_INTERNAL) +/************************************************************************************************************************/ +//! \fn CUresult CUDAAPI cuvidMapVideoFrame(CUvideodecoder hDecoder, int nPicIdx, unsigned int *pDevPtr, +//! unsigned int *pPitch, CUVIDPROCPARAMS *pVPP); +//! Post-process and map video frame corresponding to nPicIdx for use in cuda. Returns cuda device pointer and associated +//! pitch of the video frame +/************************************************************************************************************************/ +extern CUresult CUDAAPI cuvidMapVideoFrame(CUvideodecoder hDecoder, int nPicIdx, + unsigned int *pDevPtr, unsigned int *pPitch, + CUVIDPROCPARAMS *pVPP); + +/*****************************************************************************************************/ +//! \fn CUresult CUDAAPI cuvidUnmapVideoFrame(CUvideodecoder hDecoder, unsigned int DevPtr) +//! Unmap a previously mapped video frame +/*****************************************************************************************************/ +extern CUresult CUDAAPI cuvidUnmapVideoFrame(CUvideodecoder hDecoder, unsigned int DevPtr); +#endif + +/****************************************************************************************************************************/ +//! \fn CUresult CUDAAPI cuvidMapVideoFrame64(CUvideodecoder hDecoder, int nPicIdx, unsigned long long *pDevPtr, +//! unsigned int * pPitch, CUVIDPROCPARAMS *pVPP); +//! Post-process and map video frame corresponding to nPicIdx for use in cuda. Returns cuda device pointer and associated +//! pitch of the video frame +/****************************************************************************************************************************/ +extern CUresult CUDAAPI cuvidMapVideoFrame64(CUvideodecoder hDecoder, int nPicIdx, unsigned long long *pDevPtr, + unsigned int *pPitch, CUVIDPROCPARAMS *pVPP); + +/**************************************************************************************************/ +//! \fn CUresult CUDAAPI cuvidUnmapVideoFrame64(CUvideodecoder hDecoder, unsigned long long DevPtr); +//! Unmap a previously mapped video frame +/**************************************************************************************************/ +extern CUresult CUDAAPI cuvidUnmapVideoFrame64(CUvideodecoder hDecoder, unsigned long long DevPtr); + +#if defined(__CUVID_DEVPTR64) && !defined(__CUVID_INTERNAL) +#define cuvidMapVideoFrame cuvidMapVideoFrame64 +#define cuvidUnmapVideoFrame cuvidUnmapVideoFrame64 +#endif + + + +/********************************************************************************************************************/ +//! +//! Context-locking: to facilitate multi-threaded implementations, the following 4 functions +//! provide a simple mutex-style host synchronization. If a non-NULL context is specified +//! in CUVIDDECODECREATEINFO, the codec library will acquire the mutex associated with the given +//! context before making any cuda calls. +//! A multi-threaded application could create a lock associated with a context handle so that +//! multiple threads can safely share the same cuda context: +//! - use cuCtxPopCurrent immediately after context creation in order to create a 'floating' context +//! that can be passed to cuvidCtxLockCreate. +//! - When using a floating context, all cuda calls should only be made within a cuvidCtxLock/cuvidCtxUnlock section. +//! +//! NOTE: This is a safer alternative to cuCtxPushCurrent and cuCtxPopCurrent, and is not related to video +//! decoder in any way (implemented as a critical section associated with cuCtx{Push|Pop}Current calls). +/********************************************************************************************************************/ + +/********************************************************************************************************************/ +//! \fn CUresult CUDAAPI cuvidCtxLockCreate(CUvideoctxlock *pLock, CUcontext ctx) +//! This API is used to create CtxLock object +/********************************************************************************************************************/ +extern CUresult CUDAAPI cuvidCtxLockCreate(CUvideoctxlock *pLock, CUcontext ctx); + +/********************************************************************************************************************/ +//! \fn CUresult CUDAAPI cuvidCtxLockDestroy(CUvideoctxlock lck) +//! This API is used to free CtxLock object +/********************************************************************************************************************/ +extern CUresult CUDAAPI cuvidCtxLockDestroy(CUvideoctxlock lck); + +/********************************************************************************************************************/ +//! \fn CUresult CUDAAPI cuvidCtxLock(CUvideoctxlock lck, unsigned int reserved_flags) +//! This API is used to acquire ctxlock +/********************************************************************************************************************/ +extern CUresult CUDAAPI cuvidCtxLock(CUvideoctxlock lck, unsigned int reserved_flags); + +/********************************************************************************************************************/ +//! \fn CUresult CUDAAPI cuvidCtxUnlock(CUvideoctxlock lck, unsigned int reserved_flags) +//! This API is used to release ctxlock +/********************************************************************************************************************/ +extern CUresult CUDAAPI cuvidCtxUnlock(CUvideoctxlock lck, unsigned int reserved_flags); + +/**********************************************************************************************/ + + +#if defined(__cplusplus) +} +// Auto-lock helper for C++ applications +class CCtxAutoLock +{ +private: + CUvideoctxlock m_ctx; +public: + CCtxAutoLock(CUvideoctxlock ctx):m_ctx(ctx) { cuvidCtxLock(m_ctx,0); } + ~CCtxAutoLock() { cuvidCtxUnlock(m_ctx,0); } +}; +#endif /* __cplusplus */ + +#endif // __CUDA_VIDEO_H__ + diff --git a/webrtc-sys/src/nvidia/NvCodec/include/nvEncodeAPI.h b/webrtc-sys/src/nvidia/NvCodec/include/nvEncodeAPI.h new file mode 100644 index 000000000..8a56e840d --- /dev/null +++ b/webrtc-sys/src/nvidia/NvCodec/include/nvEncodeAPI.h @@ -0,0 +1,4280 @@ +/* + * This copyright notice applies to this header file only: + * + * Copyright (c) 2010-2022 NVIDIA Corporation + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the software, and to permit persons to whom the + * software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * \file nvEncodeAPI.h + * NVIDIA GPUs - beginning with the Kepler generation - contain a hardware-based encoder + * (referred to as NVENC) which provides fully-accelerated hardware-based video encoding. + * NvEncodeAPI provides the interface for NVIDIA video encoder (NVENC). + * \date 2011-2022 + * This file contains the interface constants, structure definitions and function prototypes. + */ + +#ifndef _NV_ENCODEAPI_H_ +#define _NV_ENCODEAPI_H_ + +#include + +#ifdef _WIN32 +#include +#endif + +#ifdef _MSC_VER +#ifndef _STDINT +typedef __int32 int32_t; +typedef unsigned __int32 uint32_t; +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; +typedef signed char int8_t; +typedef unsigned char uint8_t; +typedef short int16_t; +typedef unsigned short uint16_t; +#endif +#else +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \addtogroup ENCODER_STRUCTURE NvEncodeAPI Data structures + * @{ + */ + +#ifdef _WIN32 +#define NVENCAPI __stdcall +typedef RECT NVENC_RECT; +#else +#define NVENCAPI +// ========================================================================================= +#ifndef GUID_DEFINED +#define GUID_DEFINED +/*! + * \struct GUID + * Abstracts the GUID structure for non-windows platforms. + */ +// ========================================================================================= +typedef struct _GUID +{ + uint32_t Data1; /**< [in]: Specifies the first 8 hexadecimal digits of the GUID. */ + uint16_t Data2; /**< [in]: Specifies the first group of 4 hexadecimal digits. */ + uint16_t Data3; /**< [in]: Specifies the second group of 4 hexadecimal digits. */ + uint8_t Data4[8]; /**< [in]: Array of 8 bytes. The first 2 bytes contain the third group of 4 hexadecimal digits. + The remaining 6 bytes contain the final 12 hexadecimal digits. */ +} GUID, *LPGUID; +#endif // GUID + +/** + * \struct _NVENC_RECT + * Defines a Rectangle. Used in ::NV_ENC_PREPROCESS_FRAME. + */ +typedef struct _NVENC_RECT +{ + uint32_t left; /**< [in]: X coordinate of the upper left corner of rectangular area to be specified. */ + uint32_t top; /**< [in]: Y coordinate of the upper left corner of the rectangular area to be specified. */ + uint32_t right; /**< [in]: X coordinate of the bottom right corner of the rectangular area to be specified. */ + uint32_t bottom; /**< [in]: Y coordinate of the bottom right corner of the rectangular area to be specified. */ +} NVENC_RECT; + +#endif // _WIN32 + +/** @} */ /* End of GUID and NVENC_RECT structure grouping*/ + +typedef void* NV_ENC_INPUT_PTR; /**< NVENCODE API input buffer */ +typedef void* NV_ENC_OUTPUT_PTR; /**< NVENCODE API output buffer*/ +typedef void* NV_ENC_REGISTERED_PTR; /**< A Resource that has been registered with NVENCODE API*/ +typedef void* NV_ENC_CUSTREAM_PTR; /**< Pointer to CUstream*/ + +#define NVENCAPI_MAJOR_VERSION 12 +#define NVENCAPI_MINOR_VERSION 0 + +#define NVENCAPI_VERSION (NVENCAPI_MAJOR_VERSION | (NVENCAPI_MINOR_VERSION << 24)) + +/** + * Macro to generate per-structure version for use with API. + */ +#define NVENCAPI_STRUCT_VERSION(ver) ((uint32_t)NVENCAPI_VERSION | ((ver)<<16) | (0x7 << 28)) + + +#define NVENC_INFINITE_GOPLENGTH 0xffffffff + +#define NV_MAX_SEQ_HDR_LEN (512) + +#ifdef __GNUC__ +#define NV_ENC_DEPRECATED __attribute__ ((deprecated("WILL BE REMOVED IN A FUTURE VIDEO CODEC SDK VERSION"))) +#elif defined(_MSC_VER) +#define NV_ENC_DEPRECATED __declspec(deprecated("WILL BE REMOVED IN A FUTURE VIDEO CODEC SDK VERSION")) +#endif + +// ========================================================================================= +// Encode Codec GUIDS supported by the NvEncodeAPI interface. +// ========================================================================================= + +// {6BC82762-4E63-4ca4-AA85-1E50F321F6BF} +static const GUID NV_ENC_CODEC_H264_GUID = +{ 0x6bc82762, 0x4e63, 0x4ca4, { 0xaa, 0x85, 0x1e, 0x50, 0xf3, 0x21, 0xf6, 0xbf } }; + +// {790CDC88-4522-4d7b-9425-BDA9975F7603} +static const GUID NV_ENC_CODEC_HEVC_GUID = +{ 0x790cdc88, 0x4522, 0x4d7b, { 0x94, 0x25, 0xbd, 0xa9, 0x97, 0x5f, 0x76, 0x3 } }; + +// {0A352289-0AA7-4759-862D-5D15CD16D254} +static const GUID NV_ENC_CODEC_AV1_GUID = +{ 0x0a352289, 0x0aa7, 0x4759, { 0x86, 0x2d, 0x5d, 0x15, 0xcd, 0x16, 0xd2, 0x54 } }; + + + +// ========================================================================================= +// * Encode Profile GUIDS supported by the NvEncodeAPI interface. +// ========================================================================================= + +// {BFD6F8E7-233C-4341-8B3E-4818523803F4} +static const GUID NV_ENC_CODEC_PROFILE_AUTOSELECT_GUID = +{ 0xbfd6f8e7, 0x233c, 0x4341, { 0x8b, 0x3e, 0x48, 0x18, 0x52, 0x38, 0x3, 0xf4 } }; + +// {0727BCAA-78C4-4c83-8C2F-EF3DFF267C6A} +static const GUID NV_ENC_H264_PROFILE_BASELINE_GUID = +{ 0x727bcaa, 0x78c4, 0x4c83, { 0x8c, 0x2f, 0xef, 0x3d, 0xff, 0x26, 0x7c, 0x6a } }; + +// {60B5C1D4-67FE-4790-94D5-C4726D7B6E6D} +static const GUID NV_ENC_H264_PROFILE_MAIN_GUID = +{ 0x60b5c1d4, 0x67fe, 0x4790, { 0x94, 0xd5, 0xc4, 0x72, 0x6d, 0x7b, 0x6e, 0x6d } }; + +// {E7CBC309-4F7A-4b89-AF2A-D537C92BE310} +static const GUID NV_ENC_H264_PROFILE_HIGH_GUID = +{ 0xe7cbc309, 0x4f7a, 0x4b89, { 0xaf, 0x2a, 0xd5, 0x37, 0xc9, 0x2b, 0xe3, 0x10 } }; + +// {7AC663CB-A598-4960-B844-339B261A7D52} +static const GUID NV_ENC_H264_PROFILE_HIGH_444_GUID = +{ 0x7ac663cb, 0xa598, 0x4960, { 0xb8, 0x44, 0x33, 0x9b, 0x26, 0x1a, 0x7d, 0x52 } }; + +// {40847BF5-33F7-4601-9084-E8FE3C1DB8B7} +static const GUID NV_ENC_H264_PROFILE_STEREO_GUID = +{ 0x40847bf5, 0x33f7, 0x4601, { 0x90, 0x84, 0xe8, 0xfe, 0x3c, 0x1d, 0xb8, 0xb7 } }; + +// {B405AFAC-F32B-417B-89C4-9ABEED3E5978} +static const GUID NV_ENC_H264_PROFILE_PROGRESSIVE_HIGH_GUID = +{ 0xb405afac, 0xf32b, 0x417b, { 0x89, 0xc4, 0x9a, 0xbe, 0xed, 0x3e, 0x59, 0x78 } }; + +// {AEC1BD87-E85B-48f2-84C3-98BCA6285072} +static const GUID NV_ENC_H264_PROFILE_CONSTRAINED_HIGH_GUID = +{ 0xaec1bd87, 0xe85b, 0x48f2, { 0x84, 0xc3, 0x98, 0xbc, 0xa6, 0x28, 0x50, 0x72 } }; + +// {B514C39A-B55B-40fa-878F-F1253B4DFDEC} +static const GUID NV_ENC_HEVC_PROFILE_MAIN_GUID = +{ 0xb514c39a, 0xb55b, 0x40fa, { 0x87, 0x8f, 0xf1, 0x25, 0x3b, 0x4d, 0xfd, 0xec } }; + +// {fa4d2b6c-3a5b-411a-8018-0a3f5e3c9be5} +static const GUID NV_ENC_HEVC_PROFILE_MAIN10_GUID = +{ 0xfa4d2b6c, 0x3a5b, 0x411a, { 0x80, 0x18, 0x0a, 0x3f, 0x5e, 0x3c, 0x9b, 0xe5 } }; + +// For HEVC Main 444 8 bit and HEVC Main 444 10 bit profiles only +// {51ec32b5-1b4c-453c-9cbd-b616bd621341} +static const GUID NV_ENC_HEVC_PROFILE_FREXT_GUID = +{ 0x51ec32b5, 0x1b4c, 0x453c, { 0x9c, 0xbd, 0xb6, 0x16, 0xbd, 0x62, 0x13, 0x41 } }; + +// {5f2a39f5-f14e-4f95-9a9e-b76d568fcf97} +static const GUID NV_ENC_AV1_PROFILE_MAIN_GUID = +{ 0x5f2a39f5, 0xf14e, 0x4f95, { 0x9a, 0x9e, 0xb7, 0x6d, 0x56, 0x8f, 0xcf, 0x97 } }; + +// ========================================================================================= +// * Preset GUIDS supported by the NvEncodeAPI interface. +// ========================================================================================= +// {B2DFB705-4EBD-4C49-9B5F-24A777D3E587} +NV_ENC_DEPRECATED static const GUID NV_ENC_PRESET_DEFAULT_GUID = +{ 0xb2dfb705, 0x4ebd, 0x4c49, { 0x9b, 0x5f, 0x24, 0xa7, 0x77, 0xd3, 0xe5, 0x87 } }; + +// {60E4C59F-E846-4484-A56D-CD45BE9FDDF6} +NV_ENC_DEPRECATED static const GUID NV_ENC_PRESET_HP_GUID = +{ 0x60e4c59f, 0xe846, 0x4484, { 0xa5, 0x6d, 0xcd, 0x45, 0xbe, 0x9f, 0xdd, 0xf6 } }; + +// {34DBA71D-A77B-4B8F-9C3E-B6D5DA24C012} +NV_ENC_DEPRECATED static const GUID NV_ENC_PRESET_HQ_GUID = +{ 0x34dba71d, 0xa77b, 0x4b8f, { 0x9c, 0x3e, 0xb6, 0xd5, 0xda, 0x24, 0xc0, 0x12 } }; + +// {82E3E450-BDBB-4e40-989C-82A90DF9EF32} +NV_ENC_DEPRECATED static const GUID NV_ENC_PRESET_BD_GUID = +{ 0x82e3e450, 0xbdbb, 0x4e40, { 0x98, 0x9c, 0x82, 0xa9, 0xd, 0xf9, 0xef, 0x32 } }; + +// {49DF21C5-6DFA-4feb-9787-6ACC9EFFB726} +NV_ENC_DEPRECATED static const GUID NV_ENC_PRESET_LOW_LATENCY_DEFAULT_GUID = +{ 0x49df21c5, 0x6dfa, 0x4feb, { 0x97, 0x87, 0x6a, 0xcc, 0x9e, 0xff, 0xb7, 0x26 } }; + +// {C5F733B9-EA97-4cf9-BEC2-BF78A74FD105} +NV_ENC_DEPRECATED static const GUID NV_ENC_PRESET_LOW_LATENCY_HQ_GUID = +{ 0xc5f733b9, 0xea97, 0x4cf9, { 0xbe, 0xc2, 0xbf, 0x78, 0xa7, 0x4f, 0xd1, 0x5 } }; + +// {67082A44-4BAD-48FA-98EA-93056D150A58} +NV_ENC_DEPRECATED static const GUID NV_ENC_PRESET_LOW_LATENCY_HP_GUID = +{ 0x67082a44, 0x4bad, 0x48fa, { 0x98, 0xea, 0x93, 0x5, 0x6d, 0x15, 0xa, 0x58 } }; + +// {D5BFB716-C604-44e7-9BB8-DEA5510FC3AC} +NV_ENC_DEPRECATED static const GUID NV_ENC_PRESET_LOSSLESS_DEFAULT_GUID = +{ 0xd5bfb716, 0xc604, 0x44e7, { 0x9b, 0xb8, 0xde, 0xa5, 0x51, 0xf, 0xc3, 0xac } }; + +// {149998E7-2364-411d-82EF-179888093409} +NV_ENC_DEPRECATED static const GUID NV_ENC_PRESET_LOSSLESS_HP_GUID = +{ 0x149998e7, 0x2364, 0x411d, { 0x82, 0xef, 0x17, 0x98, 0x88, 0x9, 0x34, 0x9 } }; + +// Performance degrades and quality improves as we move from P1 to P7. Presets P3 to P7 for H264 and Presets P2 to P7 for HEVC have B frames enabled by default +// for HIGH_QUALITY and LOSSLESS tuning info, and will not work with Weighted Prediction enabled. In case Weighted Prediction is required, disable B frames by +// setting frameIntervalP = 1 +// {FC0A8D3E-45F8-4CF8-80C7-298871590EBF} +static const GUID NV_ENC_PRESET_P1_GUID = +{ 0xfc0a8d3e, 0x45f8, 0x4cf8, { 0x80, 0xc7, 0x29, 0x88, 0x71, 0x59, 0xe, 0xbf } }; + +// {F581CFB8-88D6-4381-93F0-DF13F9C27DAB} +static const GUID NV_ENC_PRESET_P2_GUID = +{ 0xf581cfb8, 0x88d6, 0x4381, { 0x93, 0xf0, 0xdf, 0x13, 0xf9, 0xc2, 0x7d, 0xab } }; + +// {36850110-3A07-441F-94D5-3670631F91F6} +static const GUID NV_ENC_PRESET_P3_GUID = +{ 0x36850110, 0x3a07, 0x441f, { 0x94, 0xd5, 0x36, 0x70, 0x63, 0x1f, 0x91, 0xf6 } }; + +// {90A7B826-DF06-4862-B9D2-CD6D73A08681} +static const GUID NV_ENC_PRESET_P4_GUID = +{ 0x90a7b826, 0xdf06, 0x4862, { 0xb9, 0xd2, 0xcd, 0x6d, 0x73, 0xa0, 0x86, 0x81 } }; + +// {21C6E6B4-297A-4CBA-998F-B6CBDE72ADE3} +static const GUID NV_ENC_PRESET_P5_GUID = +{ 0x21c6e6b4, 0x297a, 0x4cba, { 0x99, 0x8f, 0xb6, 0xcb, 0xde, 0x72, 0xad, 0xe3 } }; + +// {8E75C279-6299-4AB6-8302-0B215A335CF5} +static const GUID NV_ENC_PRESET_P6_GUID = +{ 0x8e75c279, 0x6299, 0x4ab6, { 0x83, 0x2, 0xb, 0x21, 0x5a, 0x33, 0x5c, 0xf5 } }; + +// {84848C12-6F71-4C13-931B-53E283F57974} +static const GUID NV_ENC_PRESET_P7_GUID = +{ 0x84848c12, 0x6f71, 0x4c13, { 0x93, 0x1b, 0x53, 0xe2, 0x83, 0xf5, 0x79, 0x74 } }; + +/** + * \addtogroup ENCODER_STRUCTURE NvEncodeAPI Data structures + * @{ + */ + +/** + * Input frame encode modes + */ +typedef enum _NV_ENC_PARAMS_FRAME_FIELD_MODE +{ + NV_ENC_PARAMS_FRAME_FIELD_MODE_FRAME = 0x01, /**< Frame mode */ + NV_ENC_PARAMS_FRAME_FIELD_MODE_FIELD = 0x02, /**< Field mode */ + NV_ENC_PARAMS_FRAME_FIELD_MODE_MBAFF = 0x03 /**< MB adaptive frame/field */ +} NV_ENC_PARAMS_FRAME_FIELD_MODE; + +/** + * Rate Control Modes + */ +typedef enum _NV_ENC_PARAMS_RC_MODE +{ + NV_ENC_PARAMS_RC_CONSTQP = 0x0, /**< Constant QP mode */ + NV_ENC_PARAMS_RC_VBR = 0x1, /**< Variable bitrate mode */ + NV_ENC_PARAMS_RC_CBR = 0x2, /**< Constant bitrate mode */ + NV_ENC_PARAMS_RC_CBR_LOWDELAY_HQ = 0x8, /**< Deprecated, use NV_ENC_PARAMS_RC_CBR + NV_ENC_TWO_PASS_QUARTER_RESOLUTION / NV_ENC_TWO_PASS_FULL_RESOLUTION + + lowDelayKeyFrameScale=1 */ + NV_ENC_PARAMS_RC_CBR_HQ = 0x10, /**< Deprecated, use NV_ENC_PARAMS_RC_CBR + NV_ENC_TWO_PASS_QUARTER_RESOLUTION / NV_ENC_TWO_PASS_FULL_RESOLUTION */ + NV_ENC_PARAMS_RC_VBR_HQ = 0x20 /**< Deprecated, use NV_ENC_PARAMS_RC_VBR + NV_ENC_TWO_PASS_QUARTER_RESOLUTION / NV_ENC_TWO_PASS_FULL_RESOLUTION */ +} NV_ENC_PARAMS_RC_MODE; + +/** + * Multi Pass encoding + */ +typedef enum _NV_ENC_MULTI_PASS +{ + NV_ENC_MULTI_PASS_DISABLED = 0x0, /**< Single Pass */ + NV_ENC_TWO_PASS_QUARTER_RESOLUTION = 0x1, /**< Two Pass encoding is enabled where first Pass is quarter resolution */ + NV_ENC_TWO_PASS_FULL_RESOLUTION = 0x2, /**< Two Pass encoding is enabled where first Pass is full resolution */ +} NV_ENC_MULTI_PASS; + +/** + * Emphasis Levels + */ +typedef enum _NV_ENC_EMPHASIS_MAP_LEVEL +{ + NV_ENC_EMPHASIS_MAP_LEVEL_0 = 0x0, /**< Emphasis Map Level 0, for zero Delta QP value */ + NV_ENC_EMPHASIS_MAP_LEVEL_1 = 0x1, /**< Emphasis Map Level 1, for very low Delta QP value */ + NV_ENC_EMPHASIS_MAP_LEVEL_2 = 0x2, /**< Emphasis Map Level 2, for low Delta QP value */ + NV_ENC_EMPHASIS_MAP_LEVEL_3 = 0x3, /**< Emphasis Map Level 3, for medium Delta QP value */ + NV_ENC_EMPHASIS_MAP_LEVEL_4 = 0x4, /**< Emphasis Map Level 4, for high Delta QP value */ + NV_ENC_EMPHASIS_MAP_LEVEL_5 = 0x5 /**< Emphasis Map Level 5, for very high Delta QP value */ +} NV_ENC_EMPHASIS_MAP_LEVEL; + +/** + * QP MAP MODE + */ +typedef enum _NV_ENC_QP_MAP_MODE +{ + NV_ENC_QP_MAP_DISABLED = 0x0, /**< Value in NV_ENC_PIC_PARAMS::qpDeltaMap have no effect. */ + NV_ENC_QP_MAP_EMPHASIS = 0x1, /**< Value in NV_ENC_PIC_PARAMS::qpDeltaMap will be treated as Emphasis level. Currently this is only supported for H264 */ + NV_ENC_QP_MAP_DELTA = 0x2, /**< Value in NV_ENC_PIC_PARAMS::qpDeltaMap will be treated as QP delta map. */ + NV_ENC_QP_MAP = 0x3, /**< Currently This is not supported. Value in NV_ENC_PIC_PARAMS::qpDeltaMap will be treated as QP value. */ +} NV_ENC_QP_MAP_MODE; + +#define NV_ENC_PARAMS_RC_VBR_MINQP (NV_ENC_PARAMS_RC_MODE)0x4 /**< Deprecated */ +#define NV_ENC_PARAMS_RC_2_PASS_QUALITY NV_ENC_PARAMS_RC_CBR_LOWDELAY_HQ /**< Deprecated */ +#define NV_ENC_PARAMS_RC_2_PASS_FRAMESIZE_CAP NV_ENC_PARAMS_RC_CBR_HQ /**< Deprecated */ +#define NV_ENC_PARAMS_RC_2_PASS_VBR NV_ENC_PARAMS_RC_VBR_HQ /**< Deprecated */ +#define NV_ENC_PARAMS_RC_CBR2 NV_ENC_PARAMS_RC_CBR /**< Deprecated */ + +/** + * Input picture structure + */ +typedef enum _NV_ENC_PIC_STRUCT +{ + NV_ENC_PIC_STRUCT_FRAME = 0x01, /**< Progressive frame */ + NV_ENC_PIC_STRUCT_FIELD_TOP_BOTTOM = 0x02, /**< Field encoding top field first */ + NV_ENC_PIC_STRUCT_FIELD_BOTTOM_TOP = 0x03 /**< Field encoding bottom field first */ +} NV_ENC_PIC_STRUCT; + +/** + * Display picture structure + * Currently, this enum is only used for deciding the number of clock timestamp sets in Picture Timing SEI / Time Code SEI + * Otherwise, this has no impact on encoder behavior + */ +typedef enum _NV_ENC_DISPLAY_PIC_STRUCT +{ + NV_ENC_PIC_STRUCT_DISPLAY_FRAME = 0x00, /**< Field encoding top field first */ + NV_ENC_PIC_STRUCT_DISPLAY_FIELD_TOP_BOTTOM = 0x01, /**< Field encoding top field first */ + NV_ENC_PIC_STRUCT_DISPLAY_FIELD_BOTTOM_TOP = 0x02, /**< Field encoding bottom field first */ + NV_ENC_PIC_STRUCT_DISPLAY_FRAME_DOUBLING = 0x03, /**< Frame doubling */ + NV_ENC_PIC_STRUCT_DISPLAY_FRAME_TRIPLING = 0x04 /**< Field tripling */ +} NV_ENC_DISPLAY_PIC_STRUCT; + +/** + * Input picture type + */ +typedef enum _NV_ENC_PIC_TYPE +{ + NV_ENC_PIC_TYPE_P = 0x0, /**< Forward predicted */ + NV_ENC_PIC_TYPE_B = 0x01, /**< Bi-directionally predicted picture */ + NV_ENC_PIC_TYPE_I = 0x02, /**< Intra predicted picture */ + NV_ENC_PIC_TYPE_IDR = 0x03, /**< IDR picture */ + NV_ENC_PIC_TYPE_BI = 0x04, /**< Bi-directionally predicted with only Intra MBs */ + NV_ENC_PIC_TYPE_SKIPPED = 0x05, /**< Picture is skipped */ + NV_ENC_PIC_TYPE_INTRA_REFRESH = 0x06, /**< First picture in intra refresh cycle */ + NV_ENC_PIC_TYPE_NONREF_P = 0x07, /**< Non reference P picture */ + NV_ENC_PIC_TYPE_UNKNOWN = 0xFF /**< Picture type unknown */ +} NV_ENC_PIC_TYPE; + +/** + * Motion vector precisions + */ +typedef enum _NV_ENC_MV_PRECISION +{ + NV_ENC_MV_PRECISION_DEFAULT = 0x0, /**< Driver selects Quarter-Pel motion vector precision by default */ + NV_ENC_MV_PRECISION_FULL_PEL = 0x01, /**< Full-Pel motion vector precision */ + NV_ENC_MV_PRECISION_HALF_PEL = 0x02, /**< Half-Pel motion vector precision */ + NV_ENC_MV_PRECISION_QUARTER_PEL = 0x03 /**< Quarter-Pel motion vector precision */ +} NV_ENC_MV_PRECISION; + + +/** + * Input buffer formats + */ +typedef enum _NV_ENC_BUFFER_FORMAT +{ + NV_ENC_BUFFER_FORMAT_UNDEFINED = 0x00000000, /**< Undefined buffer format */ + + NV_ENC_BUFFER_FORMAT_NV12 = 0x00000001, /**< Semi-Planar YUV [Y plane followed by interleaved UV plane] */ + NV_ENC_BUFFER_FORMAT_YV12 = 0x00000010, /**< Planar YUV [Y plane followed by V and U planes] */ + NV_ENC_BUFFER_FORMAT_IYUV = 0x00000100, /**< Planar YUV [Y plane followed by U and V planes] */ + NV_ENC_BUFFER_FORMAT_YUV444 = 0x00001000, /**< Planar YUV [Y plane followed by U and V planes] */ + NV_ENC_BUFFER_FORMAT_YUV420_10BIT = 0x00010000, /**< 10 bit Semi-Planar YUV [Y plane followed by interleaved UV plane]. Each pixel of size 2 bytes. Most Significant 10 bits contain pixel data. */ + NV_ENC_BUFFER_FORMAT_YUV444_10BIT = 0x00100000, /**< 10 bit Planar YUV444 [Y plane followed by U and V planes]. Each pixel of size 2 bytes. Most Significant 10 bits contain pixel data. */ + NV_ENC_BUFFER_FORMAT_ARGB = 0x01000000, /**< 8 bit Packed A8R8G8B8. This is a word-ordered format + where a pixel is represented by a 32-bit word with B + in the lowest 8 bits, G in the next 8 bits, R in the + 8 bits after that and A in the highest 8 bits. */ + NV_ENC_BUFFER_FORMAT_ARGB10 = 0x02000000, /**< 10 bit Packed A2R10G10B10. This is a word-ordered format + where a pixel is represented by a 32-bit word with B + in the lowest 10 bits, G in the next 10 bits, R in the + 10 bits after that and A in the highest 2 bits. */ + NV_ENC_BUFFER_FORMAT_AYUV = 0x04000000, /**< 8 bit Packed A8Y8U8V8. This is a word-ordered format + where a pixel is represented by a 32-bit word with V + in the lowest 8 bits, U in the next 8 bits, Y in the + 8 bits after that and A in the highest 8 bits. */ + NV_ENC_BUFFER_FORMAT_ABGR = 0x10000000, /**< 8 bit Packed A8B8G8R8. This is a word-ordered format + where a pixel is represented by a 32-bit word with R + in the lowest 8 bits, G in the next 8 bits, B in the + 8 bits after that and A in the highest 8 bits. */ + NV_ENC_BUFFER_FORMAT_ABGR10 = 0x20000000, /**< 10 bit Packed A2B10G10R10. This is a word-ordered format + where a pixel is represented by a 32-bit word with R + in the lowest 10 bits, G in the next 10 bits, B in the + 10 bits after that and A in the highest 2 bits. */ + NV_ENC_BUFFER_FORMAT_U8 = 0x40000000, /**< Buffer format representing one-dimensional buffer. + This format should be used only when registering the + resource as output buffer, which will be used to write + the encoded bit stream or H.264 ME only mode output. */ +} NV_ENC_BUFFER_FORMAT; + +#define NV_ENC_BUFFER_FORMAT_NV12_PL NV_ENC_BUFFER_FORMAT_NV12 +#define NV_ENC_BUFFER_FORMAT_YV12_PL NV_ENC_BUFFER_FORMAT_YV12 +#define NV_ENC_BUFFER_FORMAT_IYUV_PL NV_ENC_BUFFER_FORMAT_IYUV +#define NV_ENC_BUFFER_FORMAT_YUV444_PL NV_ENC_BUFFER_FORMAT_YUV444 + +/** + * Encoding levels + */ +typedef enum _NV_ENC_LEVEL +{ + NV_ENC_LEVEL_AUTOSELECT = 0, + + NV_ENC_LEVEL_H264_1 = 10, + NV_ENC_LEVEL_H264_1b = 9, + NV_ENC_LEVEL_H264_11 = 11, + NV_ENC_LEVEL_H264_12 = 12, + NV_ENC_LEVEL_H264_13 = 13, + NV_ENC_LEVEL_H264_2 = 20, + NV_ENC_LEVEL_H264_21 = 21, + NV_ENC_LEVEL_H264_22 = 22, + NV_ENC_LEVEL_H264_3 = 30, + NV_ENC_LEVEL_H264_31 = 31, + NV_ENC_LEVEL_H264_32 = 32, + NV_ENC_LEVEL_H264_4 = 40, + NV_ENC_LEVEL_H264_41 = 41, + NV_ENC_LEVEL_H264_42 = 42, + NV_ENC_LEVEL_H264_5 = 50, + NV_ENC_LEVEL_H264_51 = 51, + NV_ENC_LEVEL_H264_52 = 52, + NV_ENC_LEVEL_H264_60 = 60, + NV_ENC_LEVEL_H264_61 = 61, + NV_ENC_LEVEL_H264_62 = 62, + + NV_ENC_LEVEL_HEVC_1 = 30, + NV_ENC_LEVEL_HEVC_2 = 60, + NV_ENC_LEVEL_HEVC_21 = 63, + NV_ENC_LEVEL_HEVC_3 = 90, + NV_ENC_LEVEL_HEVC_31 = 93, + NV_ENC_LEVEL_HEVC_4 = 120, + NV_ENC_LEVEL_HEVC_41 = 123, + NV_ENC_LEVEL_HEVC_5 = 150, + NV_ENC_LEVEL_HEVC_51 = 153, + NV_ENC_LEVEL_HEVC_52 = 156, + NV_ENC_LEVEL_HEVC_6 = 180, + NV_ENC_LEVEL_HEVC_61 = 183, + NV_ENC_LEVEL_HEVC_62 = 186, + + NV_ENC_TIER_HEVC_MAIN = 0, + NV_ENC_TIER_HEVC_HIGH = 1, + + NV_ENC_LEVEL_AV1_2 = 0, + NV_ENC_LEVEL_AV1_21 = 1, + NV_ENC_LEVEL_AV1_22 = 2, + NV_ENC_LEVEL_AV1_23 = 3, + NV_ENC_LEVEL_AV1_3 = 4, + NV_ENC_LEVEL_AV1_31 = 5, + NV_ENC_LEVEL_AV1_32 = 6, + NV_ENC_LEVEL_AV1_33 = 7, + NV_ENC_LEVEL_AV1_4 = 8, + NV_ENC_LEVEL_AV1_41 = 9, + NV_ENC_LEVEL_AV1_42 = 10, + NV_ENC_LEVEL_AV1_43 = 11, + NV_ENC_LEVEL_AV1_5 = 12, + NV_ENC_LEVEL_AV1_51 = 13, + NV_ENC_LEVEL_AV1_52 = 14, + NV_ENC_LEVEL_AV1_53 = 15, + NV_ENC_LEVEL_AV1_6 = 16, + NV_ENC_LEVEL_AV1_61 = 17, + NV_ENC_LEVEL_AV1_62 = 18, + NV_ENC_LEVEL_AV1_63 = 19, + NV_ENC_LEVEL_AV1_7 = 20, + NV_ENC_LEVEL_AV1_71 = 21, + NV_ENC_LEVEL_AV1_72 = 22, + NV_ENC_LEVEL_AV1_73 = 23, + NV_ENC_LEVEL_AV1_AUTOSELECT , + + NV_ENC_TIER_AV1_0 = 0, + NV_ENC_TIER_AV1_1 = 1 +} NV_ENC_LEVEL; + +/** + * Error Codes + */ +typedef enum _NVENCSTATUS +{ + /** + * This indicates that API call returned with no errors. + */ + NV_ENC_SUCCESS, + + /** + * This indicates that no encode capable devices were detected. + */ + NV_ENC_ERR_NO_ENCODE_DEVICE, + + /** + * This indicates that devices pass by the client is not supported. + */ + NV_ENC_ERR_UNSUPPORTED_DEVICE, + + /** + * This indicates that the encoder device supplied by the client is not + * valid. + */ + NV_ENC_ERR_INVALID_ENCODERDEVICE, + + /** + * This indicates that device passed to the API call is invalid. + */ + NV_ENC_ERR_INVALID_DEVICE, + + /** + * This indicates that device passed to the API call is no longer available and + * needs to be reinitialized. The clients need to destroy the current encoder + * session by freeing the allocated input output buffers and destroying the device + * and create a new encoding session. + */ + NV_ENC_ERR_DEVICE_NOT_EXIST, + + /** + * This indicates that one or more of the pointers passed to the API call + * is invalid. + */ + NV_ENC_ERR_INVALID_PTR, + + /** + * This indicates that completion event passed in ::NvEncEncodePicture() call + * is invalid. + */ + NV_ENC_ERR_INVALID_EVENT, + + /** + * This indicates that one or more of the parameter passed to the API call + * is invalid. + */ + NV_ENC_ERR_INVALID_PARAM, + + /** + * This indicates that an API call was made in wrong sequence/order. + */ + NV_ENC_ERR_INVALID_CALL, + + /** + * This indicates that the API call failed because it was unable to allocate + * enough memory to perform the requested operation. + */ + NV_ENC_ERR_OUT_OF_MEMORY, + + /** + * This indicates that the encoder has not been initialized with + * ::NvEncInitializeEncoder() or that initialization has failed. + * The client cannot allocate input or output buffers or do any encoding + * related operation before successfully initializing the encoder. + */ + NV_ENC_ERR_ENCODER_NOT_INITIALIZED, + + /** + * This indicates that an unsupported parameter was passed by the client. + */ + NV_ENC_ERR_UNSUPPORTED_PARAM, + + /** + * This indicates that the ::NvEncLockBitstream() failed to lock the output + * buffer. This happens when the client makes a non blocking lock call to + * access the output bitstream by passing NV_ENC_LOCK_BITSTREAM::doNotWait flag. + * This is not a fatal error and client should retry the same operation after + * few milliseconds. + */ + NV_ENC_ERR_LOCK_BUSY, + + /** + * This indicates that the size of the user buffer passed by the client is + * insufficient for the requested operation. + */ + NV_ENC_ERR_NOT_ENOUGH_BUFFER, + + /** + * This indicates that an invalid struct version was used by the client. + */ + NV_ENC_ERR_INVALID_VERSION, + + /** + * This indicates that ::NvEncMapInputResource() API failed to map the client + * provided input resource. + */ + NV_ENC_ERR_MAP_FAILED, + + /** + * This indicates encode driver requires more input buffers to produce an output + * bitstream. If this error is returned from ::NvEncEncodePicture() API, this + * is not a fatal error. If the client is encoding with B frames then, + * ::NvEncEncodePicture() API might be buffering the input frame for re-ordering. + * + * A client operating in synchronous mode cannot call ::NvEncLockBitstream() + * API on the output bitstream buffer if ::NvEncEncodePicture() returned the + * ::NV_ENC_ERR_NEED_MORE_INPUT error code. + * The client must continue providing input frames until encode driver returns + * ::NV_ENC_SUCCESS. After receiving ::NV_ENC_SUCCESS status the client can call + * ::NvEncLockBitstream() API on the output buffers in the same order in which + * it has called ::NvEncEncodePicture(). + */ + NV_ENC_ERR_NEED_MORE_INPUT, + + /** + * This indicates that the HW encoder is busy encoding and is unable to encode + * the input. The client should call ::NvEncEncodePicture() again after few + * milliseconds. + */ + NV_ENC_ERR_ENCODER_BUSY, + + /** + * This indicates that the completion event passed in ::NvEncEncodePicture() + * API has not been registered with encoder driver using ::NvEncRegisterAsyncEvent(). + */ + NV_ENC_ERR_EVENT_NOT_REGISTERD, + + /** + * This indicates that an unknown internal error has occurred. + */ + NV_ENC_ERR_GENERIC, + + /** + * This indicates that the client is attempting to use a feature + * that is not available for the license type for the current system. + */ + NV_ENC_ERR_INCOMPATIBLE_CLIENT_KEY, + + /** + * This indicates that the client is attempting to use a feature + * that is not implemented for the current version. + */ + NV_ENC_ERR_UNIMPLEMENTED, + + /** + * This indicates that the ::NvEncRegisterResource API failed to register the resource. + */ + NV_ENC_ERR_RESOURCE_REGISTER_FAILED, + + /** + * This indicates that the client is attempting to unregister a resource + * that has not been successfully registered. + */ + NV_ENC_ERR_RESOURCE_NOT_REGISTERED, + + /** + * This indicates that the client is attempting to unmap a resource + * that has not been successfully mapped. + */ + NV_ENC_ERR_RESOURCE_NOT_MAPPED, + +} NVENCSTATUS; + +/** + * Encode Picture encode flags. + */ +typedef enum _NV_ENC_PIC_FLAGS +{ + NV_ENC_PIC_FLAG_FORCEINTRA = 0x1, /**< Encode the current picture as an Intra picture */ + NV_ENC_PIC_FLAG_FORCEIDR = 0x2, /**< Encode the current picture as an IDR picture. + This flag is only valid when Picture type decision is taken by the Encoder + [_NV_ENC_INITIALIZE_PARAMS::enablePTD == 1]. */ + NV_ENC_PIC_FLAG_OUTPUT_SPSPPS = 0x4, /**< Write the sequence and picture header in encoded bitstream of the current picture */ + NV_ENC_PIC_FLAG_EOS = 0x8, /**< Indicates end of the input stream */ +} NV_ENC_PIC_FLAGS; + +/** + * Memory heap to allocate input and output buffers. + */ +typedef enum _NV_ENC_MEMORY_HEAP +{ + NV_ENC_MEMORY_HEAP_AUTOSELECT = 0, /**< Memory heap to be decided by the encoder driver based on the usage */ + NV_ENC_MEMORY_HEAP_VID = 1, /**< Memory heap is in local video memory */ + NV_ENC_MEMORY_HEAP_SYSMEM_CACHED = 2, /**< Memory heap is in cached system memory */ + NV_ENC_MEMORY_HEAP_SYSMEM_UNCACHED = 3 /**< Memory heap is in uncached system memory */ +} NV_ENC_MEMORY_HEAP; + +/** + * B-frame used as reference modes + */ +typedef enum _NV_ENC_BFRAME_REF_MODE +{ + NV_ENC_BFRAME_REF_MODE_DISABLED = 0x0, /**< B frame is not used for reference */ + NV_ENC_BFRAME_REF_MODE_EACH = 0x1, /**< Each B-frame will be used for reference */ + NV_ENC_BFRAME_REF_MODE_MIDDLE = 0x2, /**< Only(Number of B-frame)/2 th B-frame will be used for reference */ +} NV_ENC_BFRAME_REF_MODE; + +/** + * H.264 entropy coding modes. + */ +typedef enum _NV_ENC_H264_ENTROPY_CODING_MODE +{ + NV_ENC_H264_ENTROPY_CODING_MODE_AUTOSELECT = 0x0, /**< Entropy coding mode is auto selected by the encoder driver */ + NV_ENC_H264_ENTROPY_CODING_MODE_CABAC = 0x1, /**< Entropy coding mode is CABAC */ + NV_ENC_H264_ENTROPY_CODING_MODE_CAVLC = 0x2 /**< Entropy coding mode is CAVLC */ +} NV_ENC_H264_ENTROPY_CODING_MODE; + +/** + * H.264 specific BDirect modes + */ +typedef enum _NV_ENC_H264_BDIRECT_MODE +{ + NV_ENC_H264_BDIRECT_MODE_AUTOSELECT = 0x0, /**< BDirect mode is auto selected by the encoder driver */ + NV_ENC_H264_BDIRECT_MODE_DISABLE = 0x1, /**< Disable BDirect mode */ + NV_ENC_H264_BDIRECT_MODE_TEMPORAL = 0x2, /**< Temporal BDirect mode */ + NV_ENC_H264_BDIRECT_MODE_SPATIAL = 0x3 /**< Spatial BDirect mode */ +} NV_ENC_H264_BDIRECT_MODE; + +/** + * H.264 specific FMO usage + */ +typedef enum _NV_ENC_H264_FMO_MODE +{ + NV_ENC_H264_FMO_AUTOSELECT = 0x0, /**< FMO usage is auto selected by the encoder driver */ + NV_ENC_H264_FMO_ENABLE = 0x1, /**< Enable FMO */ + NV_ENC_H264_FMO_DISABLE = 0x2, /**< Disable FMO */ +} NV_ENC_H264_FMO_MODE; + +/** + * H.264 specific Adaptive Transform modes + */ +typedef enum _NV_ENC_H264_ADAPTIVE_TRANSFORM_MODE +{ + NV_ENC_H264_ADAPTIVE_TRANSFORM_AUTOSELECT = 0x0, /**< Adaptive Transform 8x8 mode is auto selected by the encoder driver*/ + NV_ENC_H264_ADAPTIVE_TRANSFORM_DISABLE = 0x1, /**< Adaptive Transform 8x8 mode disabled */ + NV_ENC_H264_ADAPTIVE_TRANSFORM_ENABLE = 0x2, /**< Adaptive Transform 8x8 mode should be used */ +} NV_ENC_H264_ADAPTIVE_TRANSFORM_MODE; + +/** + * Stereo frame packing modes. + */ +typedef enum _NV_ENC_STEREO_PACKING_MODE +{ + NV_ENC_STEREO_PACKING_MODE_NONE = 0x0, /**< No Stereo packing required */ + NV_ENC_STEREO_PACKING_MODE_CHECKERBOARD = 0x1, /**< Checkerboard mode for packing stereo frames */ + NV_ENC_STEREO_PACKING_MODE_COLINTERLEAVE = 0x2, /**< Column Interleave mode for packing stereo frames */ + NV_ENC_STEREO_PACKING_MODE_ROWINTERLEAVE = 0x3, /**< Row Interleave mode for packing stereo frames */ + NV_ENC_STEREO_PACKING_MODE_SIDEBYSIDE = 0x4, /**< Side-by-side mode for packing stereo frames */ + NV_ENC_STEREO_PACKING_MODE_TOPBOTTOM = 0x5, /**< Top-Bottom mode for packing stereo frames */ + NV_ENC_STEREO_PACKING_MODE_FRAMESEQ = 0x6 /**< Frame Sequential mode for packing stereo frames */ +} NV_ENC_STEREO_PACKING_MODE; + +/** + * Input Resource type + */ +typedef enum _NV_ENC_INPUT_RESOURCE_TYPE +{ + NV_ENC_INPUT_RESOURCE_TYPE_DIRECTX = 0x0, /**< input resource type is a directx9 surface*/ + NV_ENC_INPUT_RESOURCE_TYPE_CUDADEVICEPTR = 0x1, /**< input resource type is a cuda device pointer surface*/ + NV_ENC_INPUT_RESOURCE_TYPE_CUDAARRAY = 0x2, /**< input resource type is a cuda array surface. + This array must be a 2D array and the CUDA_ARRAY3D_SURFACE_LDST + flag must have been specified when creating it. */ + NV_ENC_INPUT_RESOURCE_TYPE_OPENGL_TEX = 0x3 /**< input resource type is an OpenGL texture */ +} NV_ENC_INPUT_RESOURCE_TYPE; + +/** + * Buffer usage + */ +typedef enum _NV_ENC_BUFFER_USAGE +{ + NV_ENC_INPUT_IMAGE = 0x0, /**< Registered surface will be used for input image */ + NV_ENC_OUTPUT_MOTION_VECTOR = 0x1, /**< Registered surface will be used for output of H.264 ME only mode. + This buffer usage type is not supported for HEVC ME only mode. */ + NV_ENC_OUTPUT_BITSTREAM = 0x2, /**< Registered surface will be used for output bitstream in encoding */ +} NV_ENC_BUFFER_USAGE; + +/** + * Encoder Device type + */ +typedef enum _NV_ENC_DEVICE_TYPE +{ + NV_ENC_DEVICE_TYPE_DIRECTX = 0x0, /**< encode device type is a directx9 device */ + NV_ENC_DEVICE_TYPE_CUDA = 0x1, /**< encode device type is a cuda device */ + NV_ENC_DEVICE_TYPE_OPENGL = 0x2 /**< encode device type is an OpenGL device. + Use of this device type is supported only on Linux */ +} NV_ENC_DEVICE_TYPE; + +/** + * Number of reference frames + */ +typedef enum _NV_ENC_NUM_REF_FRAMES +{ + NV_ENC_NUM_REF_FRAMES_AUTOSELECT = 0x0, /**< Number of reference frames is auto selected by the encoder driver */ + NV_ENC_NUM_REF_FRAMES_1 = 0x1, /**< Number of reference frames equal to 1 */ + NV_ENC_NUM_REF_FRAMES_2 = 0x2, /**< Number of reference frames equal to 2 */ + NV_ENC_NUM_REF_FRAMES_3 = 0x3, /**< Number of reference frames equal to 3 */ + NV_ENC_NUM_REF_FRAMES_4 = 0x4, /**< Number of reference frames equal to 4 */ + NV_ENC_NUM_REF_FRAMES_5 = 0x5, /**< Number of reference frames equal to 5 */ + NV_ENC_NUM_REF_FRAMES_6 = 0x6, /**< Number of reference frames equal to 6 */ + NV_ENC_NUM_REF_FRAMES_7 = 0x7 /**< Number of reference frames equal to 7 */ +} NV_ENC_NUM_REF_FRAMES; + +/** + * Encoder capabilities enumeration. + */ +typedef enum _NV_ENC_CAPS +{ + /** + * Maximum number of B-Frames supported. + */ + NV_ENC_CAPS_NUM_MAX_BFRAMES, + + /** + * Rate control modes supported. + * \n The API return value is a bitmask of the values in NV_ENC_PARAMS_RC_MODE. + */ + NV_ENC_CAPS_SUPPORTED_RATECONTROL_MODES, + + /** + * Indicates HW support for field mode encoding. + * \n 0 : Interlaced mode encoding is not supported. + * \n 1 : Interlaced field mode encoding is supported. + * \n 2 : Interlaced frame encoding and field mode encoding are both supported. + */ + NV_ENC_CAPS_SUPPORT_FIELD_ENCODING, + + /** + * Indicates HW support for monochrome mode encoding. + * \n 0 : Monochrome mode not supported. + * \n 1 : Monochrome mode supported. + */ + NV_ENC_CAPS_SUPPORT_MONOCHROME, + + /** + * Indicates HW support for FMO. + * \n 0 : FMO not supported. + * \n 1 : FMO supported. + */ + NV_ENC_CAPS_SUPPORT_FMO, + + /** + * Indicates HW capability for Quarter pel motion estimation. + * \n 0 : Quarter-Pel Motion Estimation not supported. + * \n 1 : Quarter-Pel Motion Estimation supported. + */ + NV_ENC_CAPS_SUPPORT_QPELMV, + + /** + * H.264 specific. Indicates HW support for BDirect modes. + * \n 0 : BDirect mode encoding not supported. + * \n 1 : BDirect mode encoding supported. + */ + NV_ENC_CAPS_SUPPORT_BDIRECT_MODE, + + /** + * H264 specific. Indicates HW support for CABAC entropy coding mode. + * \n 0 : CABAC entropy coding not supported. + * \n 1 : CABAC entropy coding supported. + */ + NV_ENC_CAPS_SUPPORT_CABAC, + + /** + * Indicates HW support for Adaptive Transform. + * \n 0 : Adaptive Transform not supported. + * \n 1 : Adaptive Transform supported. + */ + NV_ENC_CAPS_SUPPORT_ADAPTIVE_TRANSFORM, + + /** + * Indicates HW support for Multi View Coding. + * \n 0 : Multi View Coding not supported. + * \n 1 : Multi View Coding supported. + */ + NV_ENC_CAPS_SUPPORT_STEREO_MVC, + + /** + * Indicates HW support for encoding Temporal layers. + * \n 0 : Encoding Temporal layers not supported. + * \n 1 : Encoding Temporal layers supported. + */ + NV_ENC_CAPS_NUM_MAX_TEMPORAL_LAYERS, + + /** + * Indicates HW support for Hierarchical P frames. + * \n 0 : Hierarchical P frames not supported. + * \n 1 : Hierarchical P frames supported. + */ + NV_ENC_CAPS_SUPPORT_HIERARCHICAL_PFRAMES, + + /** + * Indicates HW support for Hierarchical B frames. + * \n 0 : Hierarchical B frames not supported. + * \n 1 : Hierarchical B frames supported. + */ + NV_ENC_CAPS_SUPPORT_HIERARCHICAL_BFRAMES, + + /** + * Maximum Encoding level supported (See ::NV_ENC_LEVEL for details). + */ + NV_ENC_CAPS_LEVEL_MAX, + + /** + * Minimum Encoding level supported (See ::NV_ENC_LEVEL for details). + */ + NV_ENC_CAPS_LEVEL_MIN, + + /** + * Indicates HW support for separate colour plane encoding. + * \n 0 : Separate colour plane encoding not supported. + * \n 1 : Separate colour plane encoding supported. + */ + NV_ENC_CAPS_SEPARATE_COLOUR_PLANE, + + /** + * Maximum output width supported. + */ + NV_ENC_CAPS_WIDTH_MAX, + + /** + * Maximum output height supported. + */ + NV_ENC_CAPS_HEIGHT_MAX, + + /** + * Indicates Temporal Scalability Support. + * \n 0 : Temporal SVC encoding not supported. + * \n 1 : Temporal SVC encoding supported. + */ + NV_ENC_CAPS_SUPPORT_TEMPORAL_SVC, + + /** + * Indicates Dynamic Encode Resolution Change Support. + * Support added from NvEncodeAPI version 2.0. + * \n 0 : Dynamic Encode Resolution Change not supported. + * \n 1 : Dynamic Encode Resolution Change supported. + */ + NV_ENC_CAPS_SUPPORT_DYN_RES_CHANGE, + + /** + * Indicates Dynamic Encode Bitrate Change Support. + * Support added from NvEncodeAPI version 2.0. + * \n 0 : Dynamic Encode bitrate change not supported. + * \n 1 : Dynamic Encode bitrate change supported. + */ + NV_ENC_CAPS_SUPPORT_DYN_BITRATE_CHANGE, + + /** + * Indicates Forcing Constant QP On The Fly Support. + * Support added from NvEncodeAPI version 2.0. + * \n 0 : Forcing constant QP on the fly not supported. + * \n 1 : Forcing constant QP on the fly supported. + */ + NV_ENC_CAPS_SUPPORT_DYN_FORCE_CONSTQP, + + /** + * Indicates Dynamic rate control mode Change Support. + * \n 0 : Dynamic rate control mode change not supported. + * \n 1 : Dynamic rate control mode change supported. + */ + NV_ENC_CAPS_SUPPORT_DYN_RCMODE_CHANGE, + + /** + * Indicates Subframe readback support for slice-based encoding. If this feature is supported, it can be enabled by setting enableSubFrameWrite = 1. + * \n 0 : Subframe readback not supported. + * \n 1 : Subframe readback supported. + */ + NV_ENC_CAPS_SUPPORT_SUBFRAME_READBACK, + + /** + * Indicates Constrained Encoding mode support. + * Support added from NvEncodeAPI version 2.0. + * \n 0 : Constrained encoding mode not supported. + * \n 1 : Constrained encoding mode supported. + * If this mode is supported client can enable this during initialization. + * Client can then force a picture to be coded as constrained picture where + * in-loop filtering is disabled across slice boundaries and prediction vectors for inter + * macroblocks in each slice will be restricted to the slice region. + */ + NV_ENC_CAPS_SUPPORT_CONSTRAINED_ENCODING, + + /** + * Indicates Intra Refresh Mode Support. + * Support added from NvEncodeAPI version 2.0. + * \n 0 : Intra Refresh Mode not supported. + * \n 1 : Intra Refresh Mode supported. + */ + NV_ENC_CAPS_SUPPORT_INTRA_REFRESH, + + /** + * Indicates Custom VBV Buffer Size support. It can be used for capping frame size. + * Support added from NvEncodeAPI version 2.0. + * \n 0 : Custom VBV buffer size specification from client, not supported. + * \n 1 : Custom VBV buffer size specification from client, supported. + */ + NV_ENC_CAPS_SUPPORT_CUSTOM_VBV_BUF_SIZE, + + /** + * Indicates Dynamic Slice Mode Support. + * Support added from NvEncodeAPI version 2.0. + * \n 0 : Dynamic Slice Mode not supported. + * \n 1 : Dynamic Slice Mode supported. + */ + NV_ENC_CAPS_SUPPORT_DYNAMIC_SLICE_MODE, + + /** + * Indicates Reference Picture Invalidation Support. + * Support added from NvEncodeAPI version 2.0. + * \n 0 : Reference Picture Invalidation not supported. + * \n 1 : Reference Picture Invalidation supported. + */ + NV_ENC_CAPS_SUPPORT_REF_PIC_INVALIDATION, + + /** + * Indicates support for Pre-Processing. + * The API return value is a bitmask of the values defined in ::NV_ENC_PREPROC_FLAGS + */ + NV_ENC_CAPS_PREPROC_SUPPORT, + + /** + * Indicates support Async mode. + * \n 0 : Async Encode mode not supported. + * \n 1 : Async Encode mode supported. + */ + NV_ENC_CAPS_ASYNC_ENCODE_SUPPORT, + + /** + * Maximum MBs per frame supported. + */ + NV_ENC_CAPS_MB_NUM_MAX, + + /** + * Maximum aggregate throughput in MBs per sec. + */ + NV_ENC_CAPS_MB_PER_SEC_MAX, + + /** + * Indicates HW support for YUV444 mode encoding. + * \n 0 : YUV444 mode encoding not supported. + * \n 1 : YUV444 mode encoding supported. + */ + NV_ENC_CAPS_SUPPORT_YUV444_ENCODE, + + /** + * Indicates HW support for lossless encoding. + * \n 0 : lossless encoding not supported. + * \n 1 : lossless encoding supported. + */ + NV_ENC_CAPS_SUPPORT_LOSSLESS_ENCODE, + + /** + * Indicates HW support for Sample Adaptive Offset. + * \n 0 : SAO not supported. + * \n 1 : SAO encoding supported. + */ + NV_ENC_CAPS_SUPPORT_SAO, + + /** + * Indicates HW support for Motion Estimation Only Mode. + * \n 0 : MEOnly Mode not supported. + * \n 1 : MEOnly Mode supported for I and P frames. + * \n 2 : MEOnly Mode supported for I, P and B frames. + */ + NV_ENC_CAPS_SUPPORT_MEONLY_MODE, + + /** + * Indicates HW support for lookahead encoding (enableLookahead=1). + * \n 0 : Lookahead not supported. + * \n 1 : Lookahead supported. + */ + NV_ENC_CAPS_SUPPORT_LOOKAHEAD, + + /** + * Indicates HW support for temporal AQ encoding (enableTemporalAQ=1). + * \n 0 : Temporal AQ not supported. + * \n 1 : Temporal AQ supported. + */ + NV_ENC_CAPS_SUPPORT_TEMPORAL_AQ, + /** + * Indicates HW support for 10 bit encoding. + * \n 0 : 10 bit encoding not supported. + * \n 1 : 10 bit encoding supported. + */ + NV_ENC_CAPS_SUPPORT_10BIT_ENCODE, + /** + * Maximum number of Long Term Reference frames supported + */ + NV_ENC_CAPS_NUM_MAX_LTR_FRAMES, + + /** + * Indicates HW support for Weighted Prediction. + * \n 0 : Weighted Prediction not supported. + * \n 1 : Weighted Prediction supported. + */ + NV_ENC_CAPS_SUPPORT_WEIGHTED_PREDICTION, + + + /** + * On managed (vGPU) platforms (Windows only), this API, in conjunction with other GRID Management APIs, can be used + * to estimate the residual capacity of the hardware encoder on the GPU as a percentage of the total available encoder capacity. + * This API can be called at any time; i.e. during the encode session or before opening the encode session. + * If the available encoder capacity is returned as zero, applications may choose to switch to software encoding + * and continue to call this API (e.g. polling once per second) until capacity becomes available. + * + * On bare metal (non-virtualized GPU) and linux platforms, this API always returns 100. + */ + NV_ENC_CAPS_DYNAMIC_QUERY_ENCODER_CAPACITY, + + /** + * Indicates B as reference support. + * \n 0 : B as reference is not supported. + * \n 1 : each B-Frame as reference is supported. + * \n 2 : only Middle B-frame as reference is supported. + */ + NV_ENC_CAPS_SUPPORT_BFRAME_REF_MODE, + + /** + * Indicates HW support for Emphasis Level Map based delta QP computation. + * \n 0 : Emphasis Level Map based delta QP not supported. + * \n 1 : Emphasis Level Map based delta QP is supported. + */ + NV_ENC_CAPS_SUPPORT_EMPHASIS_LEVEL_MAP, + + /** + * Minimum input width supported. + */ + NV_ENC_CAPS_WIDTH_MIN, + + /** + * Minimum input height supported. + */ + NV_ENC_CAPS_HEIGHT_MIN, + + /** + * Indicates HW support for multiple reference frames. + */ + NV_ENC_CAPS_SUPPORT_MULTIPLE_REF_FRAMES, + + /** + * Indicates HW support for HEVC with alpha encoding. + * \n 0 : HEVC with alpha encoding not supported. + * \n 1 : HEVC with alpha encoding is supported. + */ + NV_ENC_CAPS_SUPPORT_ALPHA_LAYER_ENCODING, + + /** + * Indicates number of Encoding engines present on GPU. + */ + NV_ENC_CAPS_NUM_ENCODER_ENGINES, + + /** + * Indicates single slice intra refresh support. + */ + NV_ENC_CAPS_SINGLE_SLICE_INTRA_REFRESH, + + /** + * Reserved - Not to be used by clients. + */ + NV_ENC_CAPS_EXPOSED_COUNT + +} NV_ENC_CAPS; + +/** + * HEVC CU SIZE + */ +typedef enum _NV_ENC_HEVC_CUSIZE +{ + NV_ENC_HEVC_CUSIZE_AUTOSELECT = 0, + NV_ENC_HEVC_CUSIZE_8x8 = 1, + NV_ENC_HEVC_CUSIZE_16x16 = 2, + NV_ENC_HEVC_CUSIZE_32x32 = 3, + NV_ENC_HEVC_CUSIZE_64x64 = 4, +}NV_ENC_HEVC_CUSIZE; + +/** +* AV1 PART SIZE +*/ +typedef enum _NV_ENC_AV1_PART_SIZE +{ + NV_ENC_AV1_PART_SIZE_AUTOSELECT = 0, + NV_ENC_AV1_PART_SIZE_4x4 = 1, + NV_ENC_AV1_PART_SIZE_8x8 = 2, + NV_ENC_AV1_PART_SIZE_16x16 = 3, + NV_ENC_AV1_PART_SIZE_32x32 = 4, + NV_ENC_AV1_PART_SIZE_64x64 = 5, +}NV_ENC_AV1_PART_SIZE; + +/** +* Enums related to fields in VUI parameters. +*/ +typedef enum _NV_ENC_VUI_VIDEO_FORMAT +{ + NV_ENC_VUI_VIDEO_FORMAT_COMPONENT = 0, + NV_ENC_VUI_VIDEO_FORMAT_PAL = 1, + NV_ENC_VUI_VIDEO_FORMAT_NTSC = 2, + NV_ENC_VUI_VIDEO_FORMAT_SECAM = 3, + NV_ENC_VUI_VIDEO_FORMAT_MAC = 4, + NV_ENC_VUI_VIDEO_FORMAT_UNSPECIFIED = 5, +}NV_ENC_VUI_VIDEO_FORMAT; + +typedef enum _NV_ENC_VUI_COLOR_PRIMARIES +{ + NV_ENC_VUI_COLOR_PRIMARIES_UNDEFINED = 0, + NV_ENC_VUI_COLOR_PRIMARIES_BT709 = 1, + NV_ENC_VUI_COLOR_PRIMARIES_UNSPECIFIED = 2, + NV_ENC_VUI_COLOR_PRIMARIES_RESERVED = 3, + NV_ENC_VUI_COLOR_PRIMARIES_BT470M = 4, + NV_ENC_VUI_COLOR_PRIMARIES_BT470BG = 5, + NV_ENC_VUI_COLOR_PRIMARIES_SMPTE170M = 6, + NV_ENC_VUI_COLOR_PRIMARIES_SMPTE240M = 7, + NV_ENC_VUI_COLOR_PRIMARIES_FILM = 8, + NV_ENC_VUI_COLOR_PRIMARIES_BT2020 = 9, + NV_ENC_VUI_COLOR_PRIMARIES_SMPTE428 = 10, + NV_ENC_VUI_COLOR_PRIMARIES_SMPTE431 = 11, + NV_ENC_VUI_COLOR_PRIMARIES_SMPTE432 = 12, + NV_ENC_VUI_COLOR_PRIMARIES_JEDEC_P22 = 22, +}NV_ENC_VUI_COLOR_PRIMARIES; + +typedef enum _NV_ENC_VUI_TRANSFER_CHARACTERISTIC +{ + NV_ENC_VUI_TRANSFER_CHARACTERISTIC_UNDEFINED = 0, + NV_ENC_VUI_TRANSFER_CHARACTERISTIC_BT709 = 1, + NV_ENC_VUI_TRANSFER_CHARACTERISTIC_UNSPECIFIED = 2, + NV_ENC_VUI_TRANSFER_CHARACTERISTIC_RESERVED = 3, + NV_ENC_VUI_TRANSFER_CHARACTERISTIC_BT470M = 4, + NV_ENC_VUI_TRANSFER_CHARACTERISTIC_BT470BG = 5, + NV_ENC_VUI_TRANSFER_CHARACTERISTIC_SMPTE170M = 6, + NV_ENC_VUI_TRANSFER_CHARACTERISTIC_SMPTE240M = 7, + NV_ENC_VUI_TRANSFER_CHARACTERISTIC_LINEAR = 8, + NV_ENC_VUI_TRANSFER_CHARACTERISTIC_LOG = 9, + NV_ENC_VUI_TRANSFER_CHARACTERISTIC_LOG_SQRT = 10, + NV_ENC_VUI_TRANSFER_CHARACTERISTIC_IEC61966_2_4 = 11, + NV_ENC_VUI_TRANSFER_CHARACTERISTIC_BT1361_ECG = 12, + NV_ENC_VUI_TRANSFER_CHARACTERISTIC_SRGB = 13, + NV_ENC_VUI_TRANSFER_CHARACTERISTIC_BT2020_10 = 14, + NV_ENC_VUI_TRANSFER_CHARACTERISTIC_BT2020_12 = 15, + NV_ENC_VUI_TRANSFER_CHARACTERISTIC_SMPTE2084 = 16, + NV_ENC_VUI_TRANSFER_CHARACTERISTIC_SMPTE428 = 17, + NV_ENC_VUI_TRANSFER_CHARACTERISTIC_ARIB_STD_B67 = 18, +}NV_ENC_VUI_TRANSFER_CHARACTERISTIC; + +typedef enum _NV_ENC_VUI_MATRIX_COEFFS +{ + NV_ENC_VUI_MATRIX_COEFFS_RGB = 0, + NV_ENC_VUI_MATRIX_COEFFS_BT709 = 1, + NV_ENC_VUI_MATRIX_COEFFS_UNSPECIFIED = 2, + NV_ENC_VUI_MATRIX_COEFFS_RESERVED = 3, + NV_ENC_VUI_MATRIX_COEFFS_FCC = 4, + NV_ENC_VUI_MATRIX_COEFFS_BT470BG = 5, + NV_ENC_VUI_MATRIX_COEFFS_SMPTE170M = 6, + NV_ENC_VUI_MATRIX_COEFFS_SMPTE240M = 7, + NV_ENC_VUI_MATRIX_COEFFS_YCGCO = 8, + NV_ENC_VUI_MATRIX_COEFFS_BT2020_NCL = 9, + NV_ENC_VUI_MATRIX_COEFFS_BT2020_CL = 10, + NV_ENC_VUI_MATRIX_COEFFS_SMPTE2085 = 11, +}NV_ENC_VUI_MATRIX_COEFFS; + +/** + * Input struct for querying Encoding capabilities. + */ +typedef struct _NV_ENC_CAPS_PARAM +{ + uint32_t version; /**< [in]: Struct version. Must be set to ::NV_ENC_CAPS_PARAM_VER */ + NV_ENC_CAPS capsToQuery; /**< [in]: Specifies the encode capability to be queried. Client should pass a member for ::NV_ENC_CAPS enum. */ + uint32_t reserved[62]; /**< [in]: Reserved and must be set to 0 */ +} NV_ENC_CAPS_PARAM; + +/** NV_ENC_CAPS_PARAM struct version. */ +#define NV_ENC_CAPS_PARAM_VER NVENCAPI_STRUCT_VERSION(1) + + +/** + * Encoder Output parameters + */ +typedef struct _NV_ENC_ENCODE_OUT_PARAMS +{ + uint32_t version; /**< [out]: Struct version. */ + uint32_t bitstreamSizeInBytes; /**< [out]: Encoded bitstream size in bytes */ + uint32_t reserved[62]; /**< [out]: Reserved and must be set to 0 */ +} NV_ENC_ENCODE_OUT_PARAMS; + +/** NV_ENC_ENCODE_OUT_PARAMS struct version. */ +#define NV_ENC_ENCODE_OUT_PARAMS_VER NVENCAPI_STRUCT_VERSION(1) + +/** + * Creation parameters for input buffer. + */ +typedef struct _NV_ENC_CREATE_INPUT_BUFFER +{ + uint32_t version; /**< [in]: Struct version. Must be set to ::NV_ENC_CREATE_INPUT_BUFFER_VER */ + uint32_t width; /**< [in]: Input frame width */ + uint32_t height; /**< [in]: Input frame height */ + NV_ENC_MEMORY_HEAP memoryHeap; /**< [in]: Deprecated. Do not use */ + NV_ENC_BUFFER_FORMAT bufferFmt; /**< [in]: Input buffer format */ + uint32_t reserved; /**< [in]: Reserved and must be set to 0 */ + NV_ENC_INPUT_PTR inputBuffer; /**< [out]: Pointer to input buffer */ + void* pSysMemBuffer; /**< [in]: Pointer to existing system memory buffer */ + uint32_t reserved1[57]; /**< [in]: Reserved and must be set to 0 */ + void* reserved2[63]; /**< [in]: Reserved and must be set to NULL */ +} NV_ENC_CREATE_INPUT_BUFFER; + +/** NV_ENC_CREATE_INPUT_BUFFER struct version. */ +#define NV_ENC_CREATE_INPUT_BUFFER_VER NVENCAPI_STRUCT_VERSION(1) + +/** + * Creation parameters for output bitstream buffer. + */ +typedef struct _NV_ENC_CREATE_BITSTREAM_BUFFER +{ + uint32_t version; /**< [in]: Struct version. Must be set to ::NV_ENC_CREATE_BITSTREAM_BUFFER_VER */ + uint32_t size; /**< [in]: Deprecated. Do not use */ + NV_ENC_MEMORY_HEAP memoryHeap; /**< [in]: Deprecated. Do not use */ + uint32_t reserved; /**< [in]: Reserved and must be set to 0 */ + NV_ENC_OUTPUT_PTR bitstreamBuffer; /**< [out]: Pointer to the output bitstream buffer */ + void* bitstreamBufferPtr; /**< [out]: Reserved and should not be used */ + uint32_t reserved1[58]; /**< [in]: Reserved and should be set to 0 */ + void* reserved2[64]; /**< [in]: Reserved and should be set to NULL */ +} NV_ENC_CREATE_BITSTREAM_BUFFER; + +/** NV_ENC_CREATE_BITSTREAM_BUFFER struct version. */ +#define NV_ENC_CREATE_BITSTREAM_BUFFER_VER NVENCAPI_STRUCT_VERSION(1) + +/** + * Structs needed for ME only mode. + */ +typedef struct _NV_ENC_MVECTOR +{ + int16_t mvx; /**< the x component of MV in quarter-pel units */ + int16_t mvy; /**< the y component of MV in quarter-pel units */ +} NV_ENC_MVECTOR; + +/** + * Motion vector structure per macroblock for H264 motion estimation. + */ +typedef struct _NV_ENC_H264_MV_DATA +{ + NV_ENC_MVECTOR mv[4]; /**< up to 4 vectors for 8x8 partition */ + uint8_t mbType; /**< 0 (I), 1 (P), 2 (IPCM), 3 (B) */ + uint8_t partitionType; /**< Specifies the block partition type. 0:16x16, 1:8x8, 2:16x8, 3:8x16 */ + uint16_t reserved; /**< reserved padding for alignment */ + uint32_t mbCost; +} NV_ENC_H264_MV_DATA; + +/** + * Motion vector structure per CU for HEVC motion estimation. + */ +typedef struct _NV_ENC_HEVC_MV_DATA +{ + NV_ENC_MVECTOR mv[4]; /**< up to 4 vectors within a CU */ + uint8_t cuType; /**< 0 (I), 1(P) */ + uint8_t cuSize; /**< 0: 8x8, 1: 16x16, 2: 32x32, 3: 64x64 */ + uint8_t partitionMode; /**< The CU partition mode + 0 (2Nx2N), 1 (2NxN), 2(Nx2N), 3 (NxN), + 4 (2NxnU), 5 (2NxnD), 6(nLx2N), 7 (nRx2N) */ + uint8_t lastCUInCTB; /**< Marker to separate CUs in the current CTB from CUs in the next CTB */ +} NV_ENC_HEVC_MV_DATA; + +/** + * Creation parameters for output motion vector buffer for ME only mode. + */ +typedef struct _NV_ENC_CREATE_MV_BUFFER +{ + uint32_t version; /**< [in]: Struct version. Must be set to NV_ENC_CREATE_MV_BUFFER_VER */ + NV_ENC_OUTPUT_PTR mvBuffer; /**< [out]: Pointer to the output motion vector buffer */ + uint32_t reserved1[255]; /**< [in]: Reserved and should be set to 0 */ + void* reserved2[63]; /**< [in]: Reserved and should be set to NULL */ +} NV_ENC_CREATE_MV_BUFFER; + +/** NV_ENC_CREATE_MV_BUFFER struct version*/ +#define NV_ENC_CREATE_MV_BUFFER_VER NVENCAPI_STRUCT_VERSION(1) + +/** + * QP value for frames + */ +typedef struct _NV_ENC_QP +{ + uint32_t qpInterP; /**< [in]: Specifies QP value for P-frame. Even though this field is uint32_t for legacy reasons, the client should treat this as a signed parameter(int32_t) for cases in which negative QP values are to be specified. */ + uint32_t qpInterB; /**< [in]: Specifies QP value for B-frame. Even though this field is uint32_t for legacy reasons, the client should treat this as a signed parameter(int32_t) for cases in which negative QP values are to be specified. */ + uint32_t qpIntra; /**< [in]: Specifies QP value for Intra Frame. Even though this field is uint32_t for legacy reasons, the client should treat this as a signed parameter(int32_t) for cases in which negative QP values are to be specified. */ +} NV_ENC_QP; + +/** + * Rate Control Configuration Parameters + */ + typedef struct _NV_ENC_RC_PARAMS + { + uint32_t version; + NV_ENC_PARAMS_RC_MODE rateControlMode; /**< [in]: Specifies the rate control mode. Check support for various rate control modes using ::NV_ENC_CAPS_SUPPORTED_RATECONTROL_MODES caps. */ + NV_ENC_QP constQP; /**< [in]: Specifies the initial QP to be used for encoding, these values would be used for all frames if in Constant QP mode. */ + uint32_t averageBitRate; /**< [in]: Specifies the average bitrate(in bits/sec) used for encoding. */ + uint32_t maxBitRate; /**< [in]: Specifies the maximum bitrate for the encoded output. This is used for VBR and ignored for CBR mode. */ + uint32_t vbvBufferSize; /**< [in]: Specifies the VBV(HRD) buffer size. in bits. Set 0 to use the default VBV buffer size. */ + uint32_t vbvInitialDelay; /**< [in]: Specifies the VBV(HRD) initial delay in bits. Set 0 to use the default VBV initial delay .*/ + uint32_t enableMinQP :1; /**< [in]: Set this to 1 if minimum QP used for rate control. */ + uint32_t enableMaxQP :1; /**< [in]: Set this to 1 if maximum QP used for rate control. */ + uint32_t enableInitialRCQP :1; /**< [in]: Set this to 1 if user supplied initial QP is used for rate control. */ + uint32_t enableAQ :1; /**< [in]: Set this to 1 to enable adaptive quantization (Spatial). */ + uint32_t reservedBitField1 :1; /**< [in]: Reserved bitfields and must be set to 0. */ + uint32_t enableLookahead :1; /**< [in]: Set this to 1 to enable lookahead with depth (if lookahead is enabled, input frames must remain available to the encoder until encode completion) */ + uint32_t disableIadapt :1; /**< [in]: Set this to 1 to disable adaptive I-frame insertion at scene cuts (only has an effect when lookahead is enabled) */ + uint32_t disableBadapt :1; /**< [in]: Set this to 1 to disable adaptive B-frame decision (only has an effect when lookahead is enabled) */ + uint32_t enableTemporalAQ :1; /**< [in]: Set this to 1 to enable temporal AQ */ + uint32_t zeroReorderDelay :1; /**< [in]: Set this to 1 to indicate zero latency operation (no reordering delay, num_reorder_frames=0) */ + uint32_t enableNonRefP :1; /**< [in]: Set this to 1 to enable automatic insertion of non-reference P-frames (no effect if enablePTD=0) */ + uint32_t strictGOPTarget :1; /**< [in]: Set this to 1 to minimize GOP-to-GOP rate fluctuations */ + uint32_t aqStrength :4; /**< [in]: When AQ (Spatial) is enabled (i.e. NV_ENC_RC_PARAMS::enableAQ is set), this field is used to specify AQ strength. AQ strength scale is from 1 (low) - 15 (aggressive). + If not set, strength is auto selected by driver. */ + uint32_t reservedBitFields :16; /**< [in]: Reserved bitfields and must be set to 0 */ + NV_ENC_QP minQP; /**< [in]: Specifies the minimum QP used for rate control. Client must set NV_ENC_CONFIG::enableMinQP to 1. */ + NV_ENC_QP maxQP; /**< [in]: Specifies the maximum QP used for rate control. Client must set NV_ENC_CONFIG::enableMaxQP to 1. */ + NV_ENC_QP initialRCQP; /**< [in]: Specifies the initial QP used for rate control. Client must set NV_ENC_CONFIG::enableInitialRCQP to 1. */ + uint32_t temporallayerIdxMask; /**< [in]: Specifies the temporal layers (as a bitmask) whose QPs have changed. Valid max bitmask is [2^NV_ENC_CAPS_NUM_MAX_TEMPORAL_LAYERS - 1]. + Applicable only for constant QP mode (NV_ENC_RC_PARAMS::rateControlMode = NV_ENC_PARAMS_RC_CONSTQP). */ + uint8_t temporalLayerQP[8]; /**< [in]: Specifies the temporal layer QPs used for rate control. Temporal layer index is used as the array index. + Applicable only for constant QP mode (NV_ENC_RC_PARAMS::rateControlMode = NV_ENC_PARAMS_RC_CONSTQP). */ + uint8_t targetQuality; /**< [in]: Target CQ (Constant Quality) level for VBR mode (range 0-51 with 0-automatic) */ + uint8_t targetQualityLSB; /**< [in]: Fractional part of target quality (as 8.8 fixed point format) */ + uint16_t lookaheadDepth; /**< [in]: Maximum depth of lookahead with range 0-(31 - number of B frames). + lookaheadDepth is only used if enableLookahead=1.*/ + uint8_t lowDelayKeyFrameScale; /**< [in]: Specifies the ratio of I frame bits to P frame bits in case of single frame VBV and CBR rate control mode, + is set to 2 by default for low latency tuning info and 1 by default for ultra low latency tuning info */ + int8_t yDcQPIndexOffset; /**< [in]: Specifies the value of 'deltaQ_y_dc' in AV1.*/ + int8_t uDcQPIndexOffset; /**< [in]: Specifies the value of 'deltaQ_u_dc' in AV1.*/ + int8_t vDcQPIndexOffset; /**< [in]: Specifies the value of 'deltaQ_v_dc' in AV1 (for future use only - deltaQ_v_dc is currently always internally set to same value as deltaQ_u_dc). */ + NV_ENC_QP_MAP_MODE qpMapMode; /**< [in]: This flag is used to interpret values in array specified by NV_ENC_PIC_PARAMS::qpDeltaMap. + Set this to NV_ENC_QP_MAP_EMPHASIS to treat values specified by NV_ENC_PIC_PARAMS::qpDeltaMap as Emphasis Level Map. + Emphasis Level can be assigned any value specified in enum NV_ENC_EMPHASIS_MAP_LEVEL. + Emphasis Level Map is used to specify regions to be encoded at varying levels of quality. + The hardware encoder adjusts the quantization within the image as per the provided emphasis map, + by adjusting the quantization parameter (QP) assigned to each macroblock. This adjustment is commonly called "Delta QP". + The adjustment depends on the absolute QP decided by the rate control algorithm, and is applied after the rate control has decided each macroblock's QP. + Since the Delta QP overrides rate control, enabling Emphasis Level Map may violate bitrate and VBV buffer size constraints. + Emphasis Level Map is useful in situations where client has a priori knowledge of the image complexity (e.g. via use of NVFBC's Classification feature) and encoding those high-complexity areas at higher quality (lower QP) is important, even at the possible cost of violating bitrate/VBV buffer size constraints + This feature is not supported when AQ( Spatial/Temporal) is enabled. + This feature is only supported for H264 codec currently. + + Set this to NV_ENC_QP_MAP_DELTA to treat values specified by NV_ENC_PIC_PARAMS::qpDeltaMap as QP Delta. This specifies QP modifier to be applied on top of the QP chosen by rate control + + Set this to NV_ENC_QP_MAP_DISABLED to ignore NV_ENC_PIC_PARAMS::qpDeltaMap values. In this case, qpDeltaMap should be set to NULL. + + Other values are reserved for future use.*/ + NV_ENC_MULTI_PASS multiPass; /**< [in]: This flag is used to enable multi-pass encoding for a given ::NV_ENC_PARAMS_RC_MODE. This flag is not valid for H264 and HEVC MEOnly mode */ + uint32_t alphaLayerBitrateRatio; /**< [in]: Specifies the ratio in which bitrate should be split between base and alpha layer. A value 'x' for this field will split the target bitrate in a ratio of x : 1 between base and alpha layer. + The default split ratio is 15.*/ + int8_t cbQPIndexOffset; /**< [in]: Specifies the value of 'chroma_qp_index_offset' in H264 / 'pps_cb_qp_offset' in HEVC / 'deltaQ_u_ac' in AV1.*/ + int8_t crQPIndexOffset; /**< [in]: Specifies the value of 'second_chroma_qp_index_offset' in H264 / 'pps_cr_qp_offset' in HEVC / 'deltaQ_v_ac' in AV1 (for future use only - deltaQ_v_ac is currently always internally set to same value as deltaQ_u_ac). */ + uint16_t reserved2; + uint32_t reserved[4]; + } NV_ENC_RC_PARAMS; + +/** macro for constructing the version field of ::_NV_ENC_RC_PARAMS */ +#define NV_ENC_RC_PARAMS_VER NVENCAPI_STRUCT_VERSION(1) + +#define MAX_NUM_CLOCK_TS 3 + +/** +* Clock Timestamp set parameters +* For H264, this structure is used to populate Picture Timing SEI when NV_ENC_CONFIG_H264::enableTimeCode is set to 1. +* For HEVC, this structure is used to populate Time Code SEI when NV_ENC_CONFIG_HEVC::enableTimeCodeSEI is set to 1. +* For more details, refer to Annex D of ITU-T Specification. +*/ + +typedef struct _NV_ENC_CLOCK_TIMESTAMP_SET +{ + uint32_t countingType : 1; /**< [in] Specifies the 'counting_type' */ + uint32_t discontinuityFlag : 1; /**< [in] Specifies the 'discontinuity_flag' */ + uint32_t cntDroppedFrames : 1; /**< [in] Specifies the 'cnt_dropped_flag' */ + uint32_t nFrames : 8; /**< [in] Specifies the value of 'n_frames' */ + uint32_t secondsValue : 6; /**< [in] Specifies the 'seconds_value' */ + uint32_t minutesValue : 6; /**< [in] Specifies the 'minutes_value' */ + uint32_t hoursValue : 5; /**< [in] Specifies the 'hours_value' */ + uint32_t reserved2 : 4; /**< [in] Reserved and must be set to 0 */ + uint32_t timeOffset; /**< [in] Specifies the 'time_offset_value' */ +} NV_ENC_CLOCK_TIMESTAMP_SET; + +typedef struct _NV_ENC_TIME_CODE +{ + NV_ENC_DISPLAY_PIC_STRUCT displayPicStruct; /**< [in] Display picStruct */ + NV_ENC_CLOCK_TIMESTAMP_SET clockTimestamp[MAX_NUM_CLOCK_TS]; /**< [in] Clock Timestamp set */ +} NV_ENC_TIME_CODE; + + +/** + * \struct _NV_ENC_CONFIG_H264_VUI_PARAMETERS + * H264 Video Usability Info parameters + */ +typedef struct _NV_ENC_CONFIG_H264_VUI_PARAMETERS +{ + uint32_t overscanInfoPresentFlag; /**< [in]: If set to 1 , it specifies that the overscanInfo is present */ + uint32_t overscanInfo; /**< [in]: Specifies the overscan info(as defined in Annex E of the ITU-T Specification). */ + uint32_t videoSignalTypePresentFlag; /**< [in]: If set to 1, it specifies that the videoFormat, videoFullRangeFlag and colourDescriptionPresentFlag are present. */ + NV_ENC_VUI_VIDEO_FORMAT videoFormat; /**< [in]: Specifies the source video format(as defined in Annex E of the ITU-T Specification).*/ + uint32_t videoFullRangeFlag; /**< [in]: Specifies the output range of the luma and chroma samples(as defined in Annex E of the ITU-T Specification). */ + uint32_t colourDescriptionPresentFlag; /**< [in]: If set to 1, it specifies that the colourPrimaries, transferCharacteristics and colourMatrix are present. */ + NV_ENC_VUI_COLOR_PRIMARIES colourPrimaries; /**< [in]: Specifies color primaries for converting to RGB(as defined in Annex E of the ITU-T Specification) */ + NV_ENC_VUI_TRANSFER_CHARACTERISTIC transferCharacteristics; /**< [in]: Specifies the opto-electronic transfer characteristics to use (as defined in Annex E of the ITU-T Specification) */ + NV_ENC_VUI_MATRIX_COEFFS colourMatrix; /**< [in]: Specifies the matrix coefficients used in deriving the luma and chroma from the RGB primaries (as defined in Annex E of the ITU-T Specification). */ + uint32_t chromaSampleLocationFlag; /**< [in]: If set to 1 , it specifies that the chromaSampleLocationTop and chromaSampleLocationBot are present.*/ + uint32_t chromaSampleLocationTop; /**< [in]: Specifies the chroma sample location for top field(as defined in Annex E of the ITU-T Specification) */ + uint32_t chromaSampleLocationBot; /**< [in]: Specifies the chroma sample location for bottom field(as defined in Annex E of the ITU-T Specification) */ + uint32_t bitstreamRestrictionFlag; /**< [in]: If set to 1, it specifies the bitstream restriction parameters are present in the bitstream.*/ + uint32_t timingInfoPresentFlag; /**< [in]: If set to 1, it specifies that the timingInfo is present and the 'numUnitInTicks' and 'timeScale' fields are specified by the application. */ + /**< [in]: If not set, the timingInfo may still be present with timing related fields calculated internally basedon the frame rate specified by the application. */ + uint32_t numUnitInTicks; /**< [in]: Specifies the number of time units of the clock(as defined in Annex E of the ITU-T Specification). */ + uint32_t timeScale; /**< [in]: Specifies the frquency of the clock(as defined in Annex E of the ITU-T Specification). */ + uint32_t reserved[12]; /**< [in]: Reserved and must be set to 0 */ +}NV_ENC_CONFIG_H264_VUI_PARAMETERS; + +typedef NV_ENC_CONFIG_H264_VUI_PARAMETERS NV_ENC_CONFIG_HEVC_VUI_PARAMETERS; + +/** + * \struct _NVENC_EXTERNAL_ME_HINT_COUNTS_PER_BLOCKTYPE + * External motion vector hint counts per block type. + * H264 and AV1 support multiple hint while HEVC supports one hint for each valid candidate. + */ +typedef struct _NVENC_EXTERNAL_ME_HINT_COUNTS_PER_BLOCKTYPE +{ + uint32_t numCandsPerBlk16x16 : 4; /**< [in]: Supported for H264, HEVC. It Specifies the number of candidates per 16x16 block. */ + uint32_t numCandsPerBlk16x8 : 4; /**< [in]: Supported for H264 only. Specifies the number of candidates per 16x8 block. */ + uint32_t numCandsPerBlk8x16 : 4; /**< [in]: Supported for H264 only. Specifies the number of candidates per 8x16 block. */ + uint32_t numCandsPerBlk8x8 : 4; /**< [in]: Supported for H264, HEVC. Specifies the number of candidates per 8x8 block. */ + uint32_t numCandsPerSb : 8; /**< [in]: Supported for AV1 only. Specifies the number of candidates per SB. */ + uint32_t reserved : 8; /**< [in]: Reserved for padding. */ + uint32_t reserved1[3]; /**< [in]: Reserved for future use. */ +} NVENC_EXTERNAL_ME_HINT_COUNTS_PER_BLOCKTYPE; + + +/** + * \struct _NVENC_EXTERNAL_ME_HINT + * External Motion Vector hint structure for H264 and HEVC. + */ +typedef struct _NVENC_EXTERNAL_ME_HINT +{ + int32_t mvx : 12; /**< [in]: Specifies the x component of integer pixel MV (relative to current MB) S12.0. */ + int32_t mvy : 10; /**< [in]: Specifies the y component of integer pixel MV (relative to current MB) S10.0 .*/ + int32_t refidx : 5; /**< [in]: Specifies the reference index (31=invalid). Current we support only 1 reference frame per direction for external hints, so \p refidx must be 0. */ + int32_t dir : 1; /**< [in]: Specifies the direction of motion estimation . 0=L0 1=L1.*/ + int32_t partType : 2; /**< [in]: Specifies the block partition type.0=16x16 1=16x8 2=8x16 3=8x8 (blocks in partition must be consecutive).*/ + int32_t lastofPart : 1; /**< [in]: Set to 1 for the last MV of (sub) partition */ + int32_t lastOfMB : 1; /**< [in]: Set to 1 for the last MV of macroblock. */ +} NVENC_EXTERNAL_ME_HINT; + +/** + * \struct _NVENC_EXTERNAL_ME_SB_HINT + * External Motion Vector SB hint structure for AV1 + */ +typedef struct _NVENC_EXTERNAL_ME_SB_HINT +{ + int16_t refidx : 5; /**< [in]: Specifies the reference index (31=invalid) */ + int16_t direction : 1; /**< [in]: Specifies the direction of motion estimation . 0=L0 1=L1.*/ + int16_t bi : 1; /**< [in]: Specifies reference mode 0=single mv, 1=compound mv */ + int16_t partition_type : 3; /**< [in]: Specifies the partition type: 0: 2NX2N, 1:2NxN, 2:Nx2N. reserved 3bits for future modes */ + int16_t x8 : 3; /**< [in]: Specifies the current partition's top left x position in 8 pixel unit */ + int16_t last_of_cu : 1; /**< [in]: Set to 1 for the last MV current CU */ + int16_t last_of_sb : 1; /**< [in]: Set to 1 for the last MV of current SB */ + int16_t reserved0 : 1; /**< [in]: Reserved and must be set to 0 */ + int16_t mvx : 14; /**< [in]: Specifies the x component of integer pixel MV (relative to current MB) S12.2. */ + int16_t cu_size : 2; /**< [in]: Specifies the CU size: 0: 8x8, 1: 16x16, 2:32x32, 3:64x64 */ + int16_t mvy : 12; /**< [in]: Specifies the y component of integer pixel MV (relative to current MB) S10.2 .*/ + int16_t y8 : 3; /**< [in]: Specifies the current partition's top left y position in 8 pixel unit */ + int16_t reserved1 : 1; /**< [in]: Reserved and must be set to 0 */ +} NVENC_EXTERNAL_ME_SB_HINT; + +/** + * \struct _NV_ENC_CONFIG_H264 + * H264 encoder configuration parameters + */ +typedef struct _NV_ENC_CONFIG_H264 +{ + uint32_t enableTemporalSVC :1; /**< [in]: Set to 1 to enable SVC temporal*/ + uint32_t enableStereoMVC :1; /**< [in]: Set to 1 to enable stereo MVC*/ + uint32_t hierarchicalPFrames :1; /**< [in]: Set to 1 to enable hierarchical P Frames */ + uint32_t hierarchicalBFrames :1; /**< [in]: Set to 1 to enable hierarchical B Frames */ + uint32_t outputBufferingPeriodSEI :1; /**< [in]: Set to 1 to write SEI buffering period syntax in the bitstream */ + uint32_t outputPictureTimingSEI :1; /**< [in]: Set to 1 to write SEI picture timing syntax in the bitstream. */ + uint32_t outputAUD :1; /**< [in]: Set to 1 to write access unit delimiter syntax in bitstream */ + uint32_t disableSPSPPS :1; /**< [in]: Set to 1 to disable writing of Sequence and Picture parameter info in bitstream */ + uint32_t outputFramePackingSEI :1; /**< [in]: Set to 1 to enable writing of frame packing arrangement SEI messages to bitstream */ + uint32_t outputRecoveryPointSEI :1; /**< [in]: Set to 1 to enable writing of recovery point SEI message */ + uint32_t enableIntraRefresh :1; /**< [in]: Set to 1 to enable gradual decoder refresh or intra refresh. If the GOP structure uses B frames this will be ignored */ + uint32_t enableConstrainedEncoding :1; /**< [in]: Set this to 1 to enable constrainedFrame encoding where each slice in the constrained picture is independent of other slices. + Constrained encoding works only with rectangular slices. + Check support for constrained encoding using ::NV_ENC_CAPS_SUPPORT_CONSTRAINED_ENCODING caps. */ + uint32_t repeatSPSPPS :1; /**< [in]: Set to 1 to enable writing of Sequence and Picture parameter for every IDR frame */ + uint32_t enableVFR :1; /**< [in]: Setting enableVFR=1 currently only sets the fixed_frame_rate_flag=0 in the VUI but otherwise + has no impact on the encoder behavior. For more details please refer to E.1 VUI syntax of H.264 standard. Note, however, that NVENC does not support VFR encoding and rate control. */ + uint32_t enableLTR :1; /**< [in]: Set to 1 to enable LTR (Long Term Reference) frame support. LTR can be used in two modes: "LTR Trust" mode and "LTR Per Picture" mode. + LTR Trust mode: In this mode, ltrNumFrames pictures after IDR are automatically marked as LTR. This mode is enabled by setting ltrTrustMode = 1. + Use of LTR Trust mode is strongly discouraged as this mode may be deprecated in future. + LTR Per Picture mode: In this mode, client can control whether the current picture should be marked as LTR. Enable this mode by setting + ltrTrustMode = 0 and ltrMarkFrame = 1 for the picture to be marked as LTR. This is the preferred mode + for using LTR. + Note that LTRs are not supported if encoding session is configured with B-frames */ + uint32_t qpPrimeYZeroTransformBypassFlag :1; /**< [in]: To enable lossless encode set this to 1, set QP to 0 and RC_mode to NV_ENC_PARAMS_RC_CONSTQP and profile to HIGH_444_PREDICTIVE_PROFILE. + Check support for lossless encoding using ::NV_ENC_CAPS_SUPPORT_LOSSLESS_ENCODE caps. */ + uint32_t useConstrainedIntraPred :1; /**< [in]: Set 1 to enable constrained intra prediction. */ + uint32_t enableFillerDataInsertion :1; /**< [in]: Set to 1 to enable insertion of filler data in the bitstream. + This flag will take effect only when one of the CBR rate + control modes (NV_ENC_PARAMS_RC_CBR, NV_ENC_PARAMS_RC_CBR_HQ, + NV_ENC_PARAMS_RC_CBR_LOWDELAY_HQ) is in use and both + NV_ENC_INITIALIZE_PARAMS::frameRateNum and + NV_ENC_INITIALIZE_PARAMS::frameRateDen are set to non-zero + values. Setting this field when + NV_ENC_INITIALIZE_PARAMS::enableOutputInVidmem is also set + is currently not supported and will make ::NvEncInitializeEncoder() + return an error. */ + uint32_t disableSVCPrefixNalu :1; /**< [in]: Set to 1 to disable writing of SVC Prefix NALU preceding each slice in bitstream. + Applicable only when temporal SVC is enabled (NV_ENC_CONFIG_H264::enableTemporalSVC = 1). */ + uint32_t enableScalabilityInfoSEI :1; /**< [in]: Set to 1 to enable writing of Scalability Information SEI message preceding each IDR picture in bitstream + Applicable only when temporal SVC is enabled (NV_ENC_CONFIG_H264::enableTemporalSVC = 1). */ + uint32_t singleSliceIntraRefresh :1; /**< [in]: Set to 1 to maintain single slice in frames during intra refresh. + Check support for single slice intra refresh using ::NV_ENC_CAPS_SINGLE_SLICE_INTRA_REFRESH caps. + This flag will be ignored if the value returned for ::NV_ENC_CAPS_SINGLE_SLICE_INTRA_REFRESH caps is false. */ + uint32_t enableTimeCode :1; /**< [in]: Set to 1 to enable writing of clock timestamp sets in picture timing SEI. Note that this flag will be ignored for D3D12 interface. */ + uint32_t reservedBitFields :10; /**< [in]: Reserved bitfields and must be set to 0 */ + uint32_t level; /**< [in]: Specifies the encoding level. Client is recommended to set this to NV_ENC_LEVEL_AUTOSELECT in order to enable the NvEncodeAPI interface to select the correct level. */ + uint32_t idrPeriod; /**< [in]: Specifies the IDR interval. If not set, this is made equal to gopLength in NV_ENC_CONFIG.Low latency application client can set IDR interval to NVENC_INFINITE_GOPLENGTH so that IDR frames are not inserted automatically. */ + uint32_t separateColourPlaneFlag; /**< [in]: Set to 1 to enable 4:4:4 separate colour planes */ + uint32_t disableDeblockingFilterIDC; /**< [in]: Specifies the deblocking filter mode. Permissible value range: [0,2]. This flag corresponds + to the flag disable_deblocking_filter_idc specified in section 7.4.3 of H.264 specification, + which specifies whether the operation of the deblocking filter shall be disabled across some + block edges of the slice and specifies for which edges the filtering is disabled. See section + 7.4.3 of H.264 specification for more details.*/ + uint32_t numTemporalLayers; /**< [in]: Specifies number of temporal layers to be used for hierarchical coding / temporal SVC. Valid value range is [1,::NV_ENC_CAPS_NUM_MAX_TEMPORAL_LAYERS] */ + uint32_t spsId; /**< [in]: Specifies the SPS id of the sequence header */ + uint32_t ppsId; /**< [in]: Specifies the PPS id of the picture header */ + NV_ENC_H264_ADAPTIVE_TRANSFORM_MODE adaptiveTransformMode; /**< [in]: Specifies the AdaptiveTransform Mode. Check support for AdaptiveTransform mode using ::NV_ENC_CAPS_SUPPORT_ADAPTIVE_TRANSFORM caps. */ + NV_ENC_H264_FMO_MODE fmoMode; /**< [in]: Specified the FMO Mode. Check support for FMO using ::NV_ENC_CAPS_SUPPORT_FMO caps. */ + NV_ENC_H264_BDIRECT_MODE bdirectMode; /**< [in]: Specifies the BDirect mode. Check support for BDirect mode using ::NV_ENC_CAPS_SUPPORT_BDIRECT_MODE caps.*/ + NV_ENC_H264_ENTROPY_CODING_MODE entropyCodingMode; /**< [in]: Specifies the entropy coding mode. Check support for CABAC mode using ::NV_ENC_CAPS_SUPPORT_CABAC caps. */ + NV_ENC_STEREO_PACKING_MODE stereoMode; /**< [in]: Specifies the stereo frame packing mode which is to be signaled in frame packing arrangement SEI */ + uint32_t intraRefreshPeriod; /**< [in]: Specifies the interval between successive intra refresh if enableIntrarefresh is set. Requires enableIntraRefresh to be set. + Will be disabled if NV_ENC_CONFIG::gopLength is not set to NVENC_INFINITE_GOPLENGTH. */ + uint32_t intraRefreshCnt; /**< [in]: Specifies the length of intra refresh in number of frames for periodic intra refresh. This value should be smaller than intraRefreshPeriod */ + uint32_t maxNumRefFrames; /**< [in]: Specifies the DPB size used for encoding. Setting it to 0 will let driver use the default DPB size. + The low latency application which wants to invalidate reference frame as an error resilience tool + is recommended to use a large DPB size so that the encoder can keep old reference frames which can be used if recent + frames are invalidated. */ + uint32_t sliceMode; /**< [in]: This parameter in conjunction with sliceModeData specifies the way in which the picture is divided into slices + sliceMode = 0 MB based slices, sliceMode = 1 Byte based slices, sliceMode = 2 MB row based slices, sliceMode = 3 numSlices in Picture. + When forceIntraRefreshWithFrameCnt is set it will have priority over sliceMode setting + When sliceMode == 0 and sliceModeData == 0 whole picture will be coded with one slice */ + uint32_t sliceModeData; /**< [in]: Specifies the parameter needed for sliceMode. For: + sliceMode = 0, sliceModeData specifies # of MBs in each slice (except last slice) + sliceMode = 1, sliceModeData specifies maximum # of bytes in each slice (except last slice) + sliceMode = 2, sliceModeData specifies # of MB rows in each slice (except last slice) + sliceMode = 3, sliceModeData specifies number of slices in the picture. Driver will divide picture into slices optimally */ + NV_ENC_CONFIG_H264_VUI_PARAMETERS h264VUIParameters; /**< [in]: Specifies the H264 video usability info parameters */ + uint32_t ltrNumFrames; /**< [in]: Specifies the number of LTR frames. This parameter has different meaning in two LTR modes. + In "LTR Trust" mode (ltrTrustMode = 1), encoder will mark the first ltrNumFrames base layer reference frames within each IDR interval as LTR. + In "LTR Per Picture" mode (ltrTrustMode = 0 and ltrMarkFrame = 1), ltrNumFrames specifies maximum number of LTR frames in DPB. */ + uint32_t ltrTrustMode; /**< [in]: Specifies the LTR operating mode. See comments near NV_ENC_CONFIG_H264::enableLTR for description of the two modes. + Set to 1 to use "LTR Trust" mode of LTR operation. Clients are discouraged to use "LTR Trust" mode as this mode may + be deprecated in future releases. + Set to 0 when using "LTR Per Picture" mode of LTR operation. */ + uint32_t chromaFormatIDC; /**< [in]: Specifies the chroma format. Should be set to 1 for yuv420 input, 3 for yuv444 input. + Check support for YUV444 encoding using ::NV_ENC_CAPS_SUPPORT_YUV444_ENCODE caps.*/ + uint32_t maxTemporalLayers; /**< [in]: Specifies the max temporal layer used for temporal SVC / hierarchical coding. + Defaut value of this field is NV_ENC_CAPS::NV_ENC_CAPS_NUM_MAX_TEMPORAL_LAYERS. Note that the value NV_ENC_CONFIG_H264::maxNumRefFrames should + be greater than or equal to (NV_ENC_CONFIG_H264::maxTemporalLayers - 2) * 2, for NV_ENC_CONFIG_H264::maxTemporalLayers >= 2.*/ + NV_ENC_BFRAME_REF_MODE useBFramesAsRef; /**< [in]: Specifies the B-Frame as reference mode. Check support for useBFramesAsRef mode using ::NV_ENC_CAPS_SUPPORT_BFRAME_REF_MODE caps.*/ + NV_ENC_NUM_REF_FRAMES numRefL0; /**< [in]: Specifies max number of reference frames in reference picture list L0, that can be used by hardware for prediction of a frame. + Check support for numRefL0 using ::NV_ENC_CAPS_SUPPORT_MULTIPLE_REF_FRAMES caps. */ + NV_ENC_NUM_REF_FRAMES numRefL1; /**< [in]: Specifies max number of reference frames in reference picture list L1, that can be used by hardware for prediction of a frame. + Check support for numRefL1 using ::NV_ENC_CAPS_SUPPORT_MULTIPLE_REF_FRAMES caps. */ + + uint32_t reserved1[267]; /**< [in]: Reserved and must be set to 0 */ + void* reserved2[64]; /**< [in]: Reserved and must be set to NULL */ +} NV_ENC_CONFIG_H264; + +/** + * \struct _NV_ENC_CONFIG_HEVC + * HEVC encoder configuration parameters to be set during initialization. + */ +typedef struct _NV_ENC_CONFIG_HEVC +{ + uint32_t level; /**< [in]: Specifies the level of the encoded bitstream.*/ + uint32_t tier; /**< [in]: Specifies the level tier of the encoded bitstream.*/ + NV_ENC_HEVC_CUSIZE minCUSize; /**< [in]: Specifies the minimum size of luma coding unit.*/ + NV_ENC_HEVC_CUSIZE maxCUSize; /**< [in]: Specifies the maximum size of luma coding unit. Currently NVENC SDK only supports maxCUSize equal to NV_ENC_HEVC_CUSIZE_32x32.*/ + uint32_t useConstrainedIntraPred :1; /**< [in]: Set 1 to enable constrained intra prediction. */ + uint32_t disableDeblockAcrossSliceBoundary :1; /**< [in]: Set 1 to disable in loop filtering across slice boundary.*/ + uint32_t outputBufferingPeriodSEI :1; /**< [in]: Set 1 to write SEI buffering period syntax in the bitstream */ + uint32_t outputPictureTimingSEI :1; /**< [in]: Set 1 to write SEI picture timing syntax in the bitstream */ + uint32_t outputAUD :1; /**< [in]: Set 1 to write Access Unit Delimiter syntax. */ + uint32_t enableLTR :1; /**< [in]: Set to 1 to enable LTR (Long Term Reference) frame support. LTR can be used in two modes: "LTR Trust" mode and "LTR Per Picture" mode. + LTR Trust mode: In this mode, ltrNumFrames pictures after IDR are automatically marked as LTR. This mode is enabled by setting ltrTrustMode = 1. + Use of LTR Trust mode is strongly discouraged as this mode may be deprecated in future releases. + LTR Per Picture mode: In this mode, client can control whether the current picture should be marked as LTR. Enable this mode by setting + ltrTrustMode = 0 and ltrMarkFrame = 1 for the picture to be marked as LTR. This is the preferred mode + for using LTR. + Note that LTRs are not supported if encoding session is configured with B-frames */ + uint32_t disableSPSPPS :1; /**< [in]: Set 1 to disable VPS, SPS and PPS signaling in the bitstream. */ + uint32_t repeatSPSPPS :1; /**< [in]: Set 1 to output VPS,SPS and PPS for every IDR frame.*/ + uint32_t enableIntraRefresh :1; /**< [in]: Set 1 to enable gradual decoder refresh or intra refresh. If the GOP structure uses B frames this will be ignored */ + uint32_t chromaFormatIDC :2; /**< [in]: Specifies the chroma format. Should be set to 1 for yuv420 input, 3 for yuv444 input.*/ + uint32_t pixelBitDepthMinus8 :3; /**< [in]: Specifies pixel bit depth minus 8. Should be set to 0 for 8 bit input, 2 for 10 bit input.*/ + uint32_t enableFillerDataInsertion :1; /**< [in]: Set to 1 to enable insertion of filler data in the bitstream. + This flag will take effect only when one of the CBR rate + control modes (NV_ENC_PARAMS_RC_CBR, NV_ENC_PARAMS_RC_CBR_HQ, + NV_ENC_PARAMS_RC_CBR_LOWDELAY_HQ) is in use and both + NV_ENC_INITIALIZE_PARAMS::frameRateNum and + NV_ENC_INITIALIZE_PARAMS::frameRateDen are set to non-zero + values. Setting this field when + NV_ENC_INITIALIZE_PARAMS::enableOutputInVidmem is also set + is currently not supported and will make ::NvEncInitializeEncoder() + return an error. */ + uint32_t enableConstrainedEncoding :1; /**< [in]: Set this to 1 to enable constrainedFrame encoding where each slice in the constrained picture is independent of other slices. + Constrained encoding works only with rectangular slices. + Check support for constrained encoding using ::NV_ENC_CAPS_SUPPORT_CONSTRAINED_ENCODING caps. */ + uint32_t enableAlphaLayerEncoding :1; /**< [in]: Set this to 1 to enable HEVC encode with alpha layer. */ + uint32_t singleSliceIntraRefresh :1; /**< [in]: Set this to 1 to maintain single slice frames during intra refresh. + Check support for single slice intra refresh using ::NV_ENC_CAPS_SINGLE_SLICE_INTRA_REFRESH caps. + This flag will be ignored if the value returned for ::NV_ENC_CAPS_SINGLE_SLICE_INTRA_REFRESH caps is false. */ + uint32_t outputRecoveryPointSEI :1; /**< [in]: Set to 1 to enable writing of recovery point SEI message */ + uint32_t outputTimeCodeSEI :1; /**< [in]: Set 1 to write SEI time code syntax in the bitstream. Note that this flag will be ignored for D3D12 interface.*/ + uint32_t reserved :12; /**< [in]: Reserved bitfields.*/ + uint32_t idrPeriod; /**< [in]: Specifies the IDR interval. If not set, this is made equal to gopLength in NV_ENC_CONFIG. Low latency application client can set IDR interval to NVENC_INFINITE_GOPLENGTH so that IDR frames are not inserted automatically. */ + uint32_t intraRefreshPeriod; /**< [in]: Specifies the interval between successive intra refresh if enableIntrarefresh is set. Requires enableIntraRefresh to be set. + Will be disabled if NV_ENC_CONFIG::gopLength is not set to NVENC_INFINITE_GOPLENGTH. */ + uint32_t intraRefreshCnt; /**< [in]: Specifies the length of intra refresh in number of frames for periodic intra refresh. This value should be smaller than intraRefreshPeriod */ + uint32_t maxNumRefFramesInDPB; /**< [in]: Specifies the maximum number of references frames in the DPB.*/ + uint32_t ltrNumFrames; /**< [in]: This parameter has different meaning in two LTR modes. + In "LTR Trust" mode (ltrTrustMode = 1), encoder will mark the first ltrNumFrames base layer reference frames within each IDR interval as LTR. + In "LTR Per Picture" mode (ltrTrustMode = 0 and ltrMarkFrame = 1), ltrNumFrames specifies maximum number of LTR frames in DPB. + These ltrNumFrames acts as a guidance to the encoder and are not necessarily honored. To achieve a right balance between the encoding + quality and keeping LTR frames in the DPB queue, the encoder can internally limit the number of LTR frames. + The number of LTR frames actually used depends upon the encoding preset being used; Faster encoding presets will use fewer LTR frames.*/ + uint32_t vpsId; /**< [in]: Specifies the VPS id of the video parameter set */ + uint32_t spsId; /**< [in]: Specifies the SPS id of the sequence header */ + uint32_t ppsId; /**< [in]: Specifies the PPS id of the picture header */ + uint32_t sliceMode; /**< [in]: This parameter in conjunction with sliceModeData specifies the way in which the picture is divided into slices + sliceMode = 0 CTU based slices, sliceMode = 1 Byte based slices, sliceMode = 2 CTU row based slices, sliceMode = 3, numSlices in Picture + When sliceMode == 0 and sliceModeData == 0 whole picture will be coded with one slice */ + uint32_t sliceModeData; /**< [in]: Specifies the parameter needed for sliceMode. For: + sliceMode = 0, sliceModeData specifies # of CTUs in each slice (except last slice) + sliceMode = 1, sliceModeData specifies maximum # of bytes in each slice (except last slice) + sliceMode = 2, sliceModeData specifies # of CTU rows in each slice (except last slice) + sliceMode = 3, sliceModeData specifies number of slices in the picture. Driver will divide picture into slices optimally */ + uint32_t maxTemporalLayersMinus1; /**< [in]: Specifies the max temporal layer used for hierarchical coding. */ + NV_ENC_CONFIG_HEVC_VUI_PARAMETERS hevcVUIParameters; /**< [in]: Specifies the HEVC video usability info parameters */ + uint32_t ltrTrustMode; /**< [in]: Specifies the LTR operating mode. See comments near NV_ENC_CONFIG_HEVC::enableLTR for description of the two modes. + Set to 1 to use "LTR Trust" mode of LTR operation. Clients are discouraged to use "LTR Trust" mode as this mode may + be deprecated in future releases. + Set to 0 when using "LTR Per Picture" mode of LTR operation. */ + NV_ENC_BFRAME_REF_MODE useBFramesAsRef; /**< [in]: Specifies the B-Frame as reference mode. Check support for useBFramesAsRef mode using ::NV_ENC_CAPS_SUPPORT_BFRAME_REF_MODE caps.*/ + NV_ENC_NUM_REF_FRAMES numRefL0; /**< [in]: Specifies max number of reference frames in reference picture list L0, that can be used by hardware for prediction of a frame. + Check support for numRefL0 using ::NV_ENC_CAPS_SUPPORT_MULTIPLE_REF_FRAMES caps. */ + NV_ENC_NUM_REF_FRAMES numRefL1; /**< [in]: Specifies max number of reference frames in reference picture list L1, that can be used by hardware for prediction of a frame. + Check support for numRefL1 using ::NV_ENC_CAPS_SUPPORT_MULTIPLE_REF_FRAMES caps. */ + uint32_t reserved1[214]; /**< [in]: Reserved and must be set to 0.*/ + void* reserved2[64]; /**< [in]: Reserved and must be set to NULL */ +} NV_ENC_CONFIG_HEVC; + +#define NV_MAX_TILE_COLS_AV1 64 +#define NV_MAX_TILE_ROWS_AV1 64 + +/** + * \struct _NV_ENC_FILM_GRAIN_PARAMS_AV1 + * AV1 Film Grain Parameters structure + */ + +typedef struct _NV_ENC_FILM_GRAIN_PARAMS_AV1 +{ + uint32_t applyGrain :1; /**< [in]: Set to 1 to specify film grain should be added to frame */ + uint32_t chromaScalingFromLuma :1; /**< [in]: Set to 1 to specify the chroma scaling is inferred from luma scaling */ + uint32_t overlapFlag :1; /**< [in]: Set to 1 to indicate that overlap between film grain blocks should be applied*/ + uint32_t clipToRestrictedRange :1; /**< [in]: Set to 1 to clip values to restricted (studio) range after adding film grain */ + uint32_t grainScalingMinus8 :2; /**< [in]: Represents the shift - 8 applied to the values of the chroma component */ + uint32_t arCoeffLag :2; /**< [in]: Specifies the number of auto-regressive coefficients for luma and chroma */ + uint32_t numYPoints :4; /**< [in]: Specifies the number of points for the piecewise linear scaling function of the luma component */ + uint32_t numCbPoints :4; /**< [in]: Specifies the number of points for the piecewise linear scaling function of the cb component */ + uint32_t numCrPoints :4; /**< [in]: Specifies the number of points for the piecewise linear scaling function of the cr component */ + uint32_t arCoeffShiftMinus6 :2; /**< [in]: specifies the range of the auto-regressive coefficients */ + uint32_t grainScaleShift :2; /**< [in]: Specifies how much the Gaussian random numbers should be scaled down during the grain synthesi process */ + uint32_t reserved1 :8; /**< [in]: Reserved bits field - should be set to 0 */ + uint8_t pointYValue[14]; /**< [in]: pointYValue[i]: x coordinate for i-th point of luma piecewise linear scaling function. Values on a scale of 0...255 */ + uint8_t pointYScaling[14]; /**< [in]: pointYScaling[i]: i-th point output value of luma piecewise linear scaling function */ + uint8_t pointCbValue[10]; /**< [in]: pointCbValue[i]: x coordinate for i-th point of cb piecewise linear scaling function. Values on a scale of 0...255 */ + uint8_t pointCbScaling[10]; /**< [in]: pointCbScaling[i]: i-th point output value of cb piecewise linear scaling function */ + uint8_t pointCrValue[10]; /**< [in]: pointCrValue[i]: x coordinate for i-th point of cr piecewise linear scaling function. Values on a scale of 0...255 */ + uint8_t pointCrScaling[10]; /**< [in]: pointCrScaling[i]: i-th point output value of cr piecewise linear scaling function */ + uint8_t arCoeffsYPlus128[24]; /**< [in]: Specifies auto-regressive coefficients used for the Y plane */ + uint8_t arCoeffsCbPlus128[25]; /**< [in]: Specifies auto-regressive coefficients used for the U plane */ + uint8_t arCoeffsCrPlus128[25]; /**< [in]: Specifies auto-regressive coefficients used for the V plane */ + uint8_t reserved2[2]; /**< [in]: Reserved bytes - should be set to 0 */ + uint8_t cbMult; /**< [in]: Represents a multiplier for the cb component used in derivation of the input index to the cb component scaling function */ + uint8_t cbLumaMult; /**< [in]: represents a multiplier for the average luma component used in derivation of the input index to the cb component scaling function. */ + uint16_t cbOffset; /**< [in]: Represents an offset used in derivation of the input index to the cb component scaling function */ + uint8_t crMult; /**< [in]: Represents a multiplier for the cr component used in derivation of the input index to the cr component scaling function */ + uint8_t crLumaMult; /**< [in]: represents a multiplier for the average luma component used in derivation of the input index to the cr component scaling function. */ + uint16_t crOffset; /**< [in]: Represents an offset used in derivation of the input index to the cr component scaling function */ +} NV_ENC_FILM_GRAIN_PARAMS_AV1; + +/** +* \struct _NV_ENC_CONFIG_AV1 +* AV1 encoder configuration parameters to be set during initialization. +*/ +typedef struct _NV_ENC_CONFIG_AV1 +{ + uint32_t level; /**< [in]: Specifies the level of the encoded bitstream.*/ + uint32_t tier; /**< [in]: Specifies the level tier of the encoded bitstream.*/ + NV_ENC_AV1_PART_SIZE minPartSize; /**< [in]: Specifies the minimum size of luma coding block partition.*/ + NV_ENC_AV1_PART_SIZE maxPartSize; /**< [in]: Specifies the maximum size of luma coding block partition.*/ + uint32_t outputAnnexBFormat : 1; /**< [in]: Set 1 to use Annex B format for bitstream output.*/ + uint32_t enableTimingInfo : 1; /**< [in]: Set 1 to write Timing Info into sequence/frame headers */ + uint32_t enableDecoderModelInfo : 1; /**< [in]: Set 1 to write Decoder Model Info into sequence/frame headers */ + uint32_t enableFrameIdNumbers : 1; /**< [in]: Set 1 to write Frame id numbers in bitstream */ + uint32_t disableSeqHdr : 1; /**< [in]: Set 1 to disable Sequence Header signaling in the bitstream. */ + uint32_t repeatSeqHdr : 1; /**< [in]: Set 1 to output Sequence Header for every Key frame.*/ + uint32_t enableIntraRefresh : 1; /**< [in]: Set 1 to enable gradual decoder refresh or intra refresh. If the GOP structure uses B frames this will be ignored */ + uint32_t chromaFormatIDC : 2; /**< [in]: Specifies the chroma format. Should be set to 1 for yuv420 input (yuv444 input currently not supported).*/ + uint32_t enableBitstreamPadding : 1; /**< [in]: Set 1 to enable bitstream padding. */ + uint32_t enableCustomTileConfig : 1; /**< [in]: Set 1 to enable custom tile configuration: numTileColumns and numTileRows must have non zero values and tileWidths and tileHeights must point to a valid address */ + uint32_t enableFilmGrainParams : 1; /**< [in]: Set 1 to enable custom film grain parameters: filmGrainParams must point to a valid address */ + uint32_t inputPixelBitDepthMinus8 : 3; /**< [in]: Specifies pixel bit depth minus 8 of video input. Should be set to 0 for 8 bit input, 2 for 10 bit input.*/ + uint32_t pixelBitDepthMinus8 : 3; /**< [in]: Specifies pixel bit depth minus 8 of encoded video. Should be set to 0 for 8 bit, 2 for 10 bit. + HW will do the bitdepth conversion internally from inputPixelBitDepthMinus8 -> pixelBitDepthMinus8 if bit dpeths differ + Support for 8 bit input to 10 bit encode conversion only */ + uint32_t reserved : 14; /**< [in]: Reserved bitfields.*/ + uint32_t idrPeriod; /**< [in]: Specifies the IDR/Key frame interval. If not set, this is made equal to gopLength in NV_ENC_CONFIG.Low latency application client can set IDR interval to NVENC_INFINITE_GOPLENGTH so that IDR frames are not inserted automatically. */ + uint32_t intraRefreshPeriod; /**< [in]: Specifies the interval between successive intra refresh if enableIntrarefresh is set. Requires enableIntraRefresh to be set. + Will be disabled if NV_ENC_CONFIG::gopLength is not set to NVENC_INFINITE_GOPLENGTH. */ + uint32_t intraRefreshCnt; /**< [in]: Specifies the length of intra refresh in number of frames for periodic intra refresh. This value should be smaller than intraRefreshPeriod */ + uint32_t maxNumRefFramesInDPB; /**< [in]: Specifies the maximum number of references frames in the DPB.*/ + uint32_t numTileColumns; /**< [in]: This parameter in conjunction with the flag enableCustomTileConfig and the array tileWidths[] specifies the way in which the picture is divided into tile columns. + When enableCustomTileConfig == 0, the picture will be uniformly divided into numTileColumns tile columns. If numTileColumns is not a power of 2, + it will be rounded down to the next power of 2 value. If numTileColumns == 0, the picture will be coded with the smallest number of vertical tiles as allowed by standard. + When enableCustomTileConfig == 1, numTileColumns must be > 0 and <= NV_MAX_TILE_COLS_AV1 and tileWidths must point to a valid array of numTileColumns entries. + Entry i specifies the width in 64x64 CTU unit of tile colum i. The sum of all the entries should be equal to the picture width in 64x64 CTU units. */ + uint32_t numTileRows; /**< [in]: This parameter in conjunction with the flag enableCustomTileConfig and the array tileHeights[] specifies the way in which the picture is divided into tiles rows + When enableCustomTileConfig == 0, the picture will be uniformly divided into numTileRows tile rows. If numTileRows is not a power of 2, + it will be rounded down to the next power of 2 value. If numTileRows == 0, the picture will be coded with the smallest number of horizontal tiles as allowed by standard. + When enableCustomTileConfig == 1, numTileRows must be > 0 and <= NV_MAX_TILE_ROWS_AV1 and tileHeights must point to a valid array of numTileRows entries. + Entry i specifies the height in 64x64 CTU unit of tile row i. The sum of all the entries should be equal to the picture hieght in 64x64 CTU units. */ + uint32_t *tileWidths; /**< [in]: If enableCustomTileConfig == 1, tileWidths[i] specifies the width of tile column i in 64x64 CTU unit, with 0 <= i <= numTileColumns -1. */ + uint32_t *tileHeights; /**< [in]: If enableCustomTileConfig == 1, tileHeights[i] specifies the height of tile row i in 64x64 CTU unit, with 0 <= i <= numTileRows -1. */ + uint32_t maxTemporalLayersMinus1; /**< [in]: Specifies the max temporal layer used for hierarchical coding. */ + NV_ENC_VUI_COLOR_PRIMARIES colorPrimaries; /**< [in]: as defined in section of ISO/IEC 23091-4/ITU-T H.273 */ + NV_ENC_VUI_TRANSFER_CHARACTERISTIC transferCharacteristics; /**< [in]: as defined in section of ISO/IEC 23091-4/ITU-T H.273 */ + NV_ENC_VUI_MATRIX_COEFFS matrixCoefficients; /**< [in]: as defined in section of ISO/IEC 23091-4/ITU-T H.273 */ + uint32_t colorRange; /**< [in]: 0: studio swing representation - 1: full swing representation */ + uint32_t chromaSamplePosition; /**< [in]: 0: unknown + 1: Horizontally collocated with luma (0,0) sample, between two vertical samples + 2: Co-located with luma (0,0) sample */ + NV_ENC_BFRAME_REF_MODE useBFramesAsRef; /**< [in]: Specifies the B-Frame as reference mode. Check support for useBFramesAsRef mode using ::NV_ENC_CAPS_SUPPORT_BFRAME_REF_MODE caps.*/ + NV_ENC_FILM_GRAIN_PARAMS_AV1 *filmGrainParams; /**< [in]: If enableFilmGrainParams == 1, filmGrainParams must point to a valid NV_ENC_FILM_GRAIN_PARAMS_AV1 structure */ + NV_ENC_NUM_REF_FRAMES numFwdRefs; /**< [in]: Specifies max number of forward reference frame used for prediction of a frame. It must be in range 1-4 (Last, Last2, last3 and Golden). It's a suggestive value not necessarily be honored always. */ + NV_ENC_NUM_REF_FRAMES numBwdRefs; /**< [in]: Specifies max number of L1 list reference frame used for prediction of a frame. It must be in range 1-3 (Backward, Altref2, Altref). It's a suggestive value not necessarily be honored always. */ + uint32_t reserved1[235]; /**< [in]: Reserved and must be set to 0.*/ + void* reserved2[62]; /**< [in]: Reserved and must be set to NULL */ +} NV_ENC_CONFIG_AV1; + +/** + * \struct _NV_ENC_CONFIG_H264_MEONLY + * H264 encoder configuration parameters for ME only Mode + * + */ +typedef struct _NV_ENC_CONFIG_H264_MEONLY +{ + uint32_t disablePartition16x16 :1; /**< [in]: Disable Motion Estimation on 16x16 blocks*/ + uint32_t disablePartition8x16 :1; /**< [in]: Disable Motion Estimation on 8x16 blocks*/ + uint32_t disablePartition16x8 :1; /**< [in]: Disable Motion Estimation on 16x8 blocks*/ + uint32_t disablePartition8x8 :1; /**< [in]: Disable Motion Estimation on 8x8 blocks*/ + uint32_t disableIntraSearch :1; /**< [in]: Disable Intra search during Motion Estimation*/ + uint32_t bStereoEnable :1; /**< [in]: Enable Stereo Mode for Motion Estimation where each view is independently executed*/ + uint32_t reserved :26; /**< [in]: Reserved and must be set to 0 */ + uint32_t reserved1 [255]; /**< [in]: Reserved and must be set to 0 */ + void* reserved2[64]; /**< [in]: Reserved and must be set to NULL */ +} NV_ENC_CONFIG_H264_MEONLY; + + +/** + * \struct _NV_ENC_CONFIG_HEVC_MEONLY + * HEVC encoder configuration parameters for ME only Mode + * + */ +typedef struct _NV_ENC_CONFIG_HEVC_MEONLY +{ + uint32_t reserved [256]; /**< [in]: Reserved and must be set to 0 */ + void* reserved1[64]; /**< [in]: Reserved and must be set to NULL */ +} NV_ENC_CONFIG_HEVC_MEONLY; + +/** + * \struct _NV_ENC_CODEC_CONFIG + * Codec-specific encoder configuration parameters to be set during initialization. + */ +typedef union _NV_ENC_CODEC_CONFIG +{ + NV_ENC_CONFIG_H264 h264Config; /**< [in]: Specifies the H.264-specific encoder configuration. */ + NV_ENC_CONFIG_HEVC hevcConfig; /**< [in]: Specifies the HEVC-specific encoder configuration. */ + NV_ENC_CONFIG_AV1 av1Config; /**< [in]: Specifies the AV1-specific encoder configuration. */ + NV_ENC_CONFIG_H264_MEONLY h264MeOnlyConfig; /**< [in]: Specifies the H.264-specific ME only encoder configuration. */ + NV_ENC_CONFIG_HEVC_MEONLY hevcMeOnlyConfig; /**< [in]: Specifies the HEVC-specific ME only encoder configuration. */ + uint32_t reserved[320]; /**< [in]: Reserved and must be set to 0 */ +} NV_ENC_CODEC_CONFIG; + + +/** + * \struct _NV_ENC_CONFIG + * Encoder configuration parameters to be set during initialization. + */ +typedef struct _NV_ENC_CONFIG +{ + uint32_t version; /**< [in]: Struct version. Must be set to ::NV_ENC_CONFIG_VER. */ + GUID profileGUID; /**< [in]: Specifies the codec profile GUID. If client specifies \p NV_ENC_CODEC_PROFILE_AUTOSELECT_GUID the NvEncodeAPI interface will select the appropriate codec profile. */ + uint32_t gopLength; /**< [in]: Specifies the number of pictures in one GOP. Low latency application client can set goplength to NVENC_INFINITE_GOPLENGTH so that keyframes are not inserted automatically. */ + int32_t frameIntervalP; /**< [in]: Specifies the GOP pattern as follows: \p frameIntervalP = 0: I, 1: IPP, 2: IBP, 3: IBBP If goplength is set to NVENC_INFINITE_GOPLENGTH \p frameIntervalP should be set to 1. */ + uint32_t monoChromeEncoding; /**< [in]: Set this to 1 to enable monochrome encoding for this session. */ + NV_ENC_PARAMS_FRAME_FIELD_MODE frameFieldMode; /**< [in]: Specifies the frame/field mode. + Check support for field encoding using ::NV_ENC_CAPS_SUPPORT_FIELD_ENCODING caps. + Using a frameFieldMode other than NV_ENC_PARAMS_FRAME_FIELD_MODE_FRAME for RGB input is not supported. */ + NV_ENC_MV_PRECISION mvPrecision; /**< [in]: Specifies the desired motion vector prediction precision. */ + NV_ENC_RC_PARAMS rcParams; /**< [in]: Specifies the rate control parameters for the current encoding session. */ + NV_ENC_CODEC_CONFIG encodeCodecConfig; /**< [in]: Specifies the codec specific config parameters through this union. */ + uint32_t reserved [278]; /**< [in]: Reserved and must be set to 0 */ + void* reserved2[64]; /**< [in]: Reserved and must be set to NULL */ +} NV_ENC_CONFIG; + +/** macro for constructing the version field of ::_NV_ENC_CONFIG */ +#define NV_ENC_CONFIG_VER (NVENCAPI_STRUCT_VERSION(8) | ( 1<<31 )) + +/** + * Tuning information of NVENC encoding (TuningInfo is not applicable to H264 and HEVC MEOnly mode). + */ +typedef enum NV_ENC_TUNING_INFO +{ + NV_ENC_TUNING_INFO_UNDEFINED = 0, /**< Undefined tuningInfo. Invalid value for encoding. */ + NV_ENC_TUNING_INFO_HIGH_QUALITY = 1, /**< Tune presets for latency tolerant encoding.*/ + NV_ENC_TUNING_INFO_LOW_LATENCY = 2, /**< Tune presets for low latency streaming.*/ + NV_ENC_TUNING_INFO_ULTRA_LOW_LATENCY = 3, /**< Tune presets for ultra low latency streaming.*/ + NV_ENC_TUNING_INFO_LOSSLESS = 4, /**< Tune presets for lossless encoding.*/ + NV_ENC_TUNING_INFO_COUNT /**< Count number of tuningInfos. Invalid value. */ +}NV_ENC_TUNING_INFO; + +/** + * \struct _NV_ENC_INITIALIZE_PARAMS + * Encode Session Initialization parameters. + */ +typedef struct _NV_ENC_INITIALIZE_PARAMS +{ + uint32_t version; /**< [in]: Struct version. Must be set to ::NV_ENC_INITIALIZE_PARAMS_VER. */ + GUID encodeGUID; /**< [in]: Specifies the Encode GUID for which the encoder is being created. ::NvEncInitializeEncoder() API will fail if this is not set, or set to unsupported value. */ + GUID presetGUID; /**< [in]: Specifies the preset for encoding. If the preset GUID is set then , the preset configuration will be applied before any other parameter. */ + uint32_t encodeWidth; /**< [in]: Specifies the encode width. If not set ::NvEncInitializeEncoder() API will fail. */ + uint32_t encodeHeight; /**< [in]: Specifies the encode height. If not set ::NvEncInitializeEncoder() API will fail. */ + uint32_t darWidth; /**< [in]: Specifies the display aspect ratio width (H264/HEVC) or the render width (AV1). */ + uint32_t darHeight; /**< [in]: Specifies the display aspect ratio height (H264/HEVC) or the render height (AV1). */ + uint32_t frameRateNum; /**< [in]: Specifies the numerator for frame rate used for encoding in frames per second ( Frame rate = frameRateNum / frameRateDen ). */ + uint32_t frameRateDen; /**< [in]: Specifies the denominator for frame rate used for encoding in frames per second ( Frame rate = frameRateNum / frameRateDen ). */ + uint32_t enableEncodeAsync; /**< [in]: Set this to 1 to enable asynchronous mode and is expected to use events to get picture completion notification. */ + uint32_t enablePTD; /**< [in]: Set this to 1 to enable the Picture Type Decision is be taken by the NvEncodeAPI interface. */ + uint32_t reportSliceOffsets :1; /**< [in]: Set this to 1 to enable reporting slice offsets in ::_NV_ENC_LOCK_BITSTREAM. NV_ENC_INITIALIZE_PARAMS::enableEncodeAsync must be set to 0 to use this feature. Client must set this to 0 if NV_ENC_CONFIG_H264::sliceMode is 1 on Kepler GPUs */ + uint32_t enableSubFrameWrite :1; /**< [in]: Set this to 1 to write out available bitstream to memory at subframe intervals. + If enableSubFrameWrite = 1, then the hardware encoder returns data as soon as a slice (H264/HEVC) or tile (AV1) has completed encoding. + This results in better encoding latency, but the downside is that the application has to keep polling via a call to nvEncLockBitstream API continuously to see if any encoded slice/tile data is available. + Use this mode if you feel that the marginal reduction in latency from sub-frame encoding is worth the increase in complexity due to CPU-based polling. */ + uint32_t enableExternalMEHints :1; /**< [in]: Set to 1 to enable external ME hints for the current frame. For NV_ENC_INITIALIZE_PARAMS::enablePTD=1 with B frames, programming L1 hints is optional for B frames since Client doesn't know internal GOP structure. + NV_ENC_PIC_PARAMS::meHintRefPicDist should preferably be set with enablePTD=1. */ + uint32_t enableMEOnlyMode :1; /**< [in]: Set to 1 to enable ME Only Mode .*/ + uint32_t enableWeightedPrediction :1; /**< [in]: Set this to 1 to enable weighted prediction. Not supported if encode session is configured for B-Frames (i.e. NV_ENC_CONFIG::frameIntervalP > 1 or preset >=P3 when tuningInfo = ::NV_ENC_TUNING_INFO_HIGH_QUALITY or + tuningInfo = ::NV_ENC_TUNING_INFO_LOSSLESS. This is because preset >=p3 internally enables B frames when tuningInfo = ::NV_ENC_TUNING_INFO_HIGH_QUALITY or ::NV_ENC_TUNING_INFO_LOSSLESS). */ + uint32_t enableOutputInVidmem :1; /**< [in]: Set this to 1 to enable output of NVENC in video memory buffer created by application. This feature is not supported for HEVC ME only mode. */ + uint32_t reservedBitFields :26; /**< [in]: Reserved bitfields and must be set to 0 */ + uint32_t privDataSize; /**< [in]: Reserved private data buffer size and must be set to 0 */ + void* privData; /**< [in]: Reserved private data buffer and must be set to NULL */ + NV_ENC_CONFIG* encodeConfig; /**< [in]: Specifies the advanced codec specific structure. If client has sent a valid codec config structure, it will override parameters set by the NV_ENC_INITIALIZE_PARAMS::presetGUID parameter. If set to NULL the NvEncodeAPI interface will use the NV_ENC_INITIALIZE_PARAMS::presetGUID to set the codec specific parameters. + Client can also optionally query the NvEncodeAPI interface to get codec specific parameters for a presetGUID using ::NvEncGetEncodePresetConfig() API. It can then modify (if required) some of the codec config parameters and send down a custom config structure as part of ::_NV_ENC_INITIALIZE_PARAMS. + Even in this case client is recommended to pass the same preset guid it has used in ::NvEncGetEncodePresetConfig() API to query the config structure; as NV_ENC_INITIALIZE_PARAMS::presetGUID. This will not override the custom config structure but will be used to determine other Encoder HW specific parameters not exposed in the API. */ + uint32_t maxEncodeWidth; /**< [in]: Maximum encode width to be used for current Encode session. + Client should allocate output buffers according to this dimension for dynamic resolution change. If set to 0, Encoder will not allow dynamic resolution change. */ + uint32_t maxEncodeHeight; /**< [in]: Maximum encode height to be allowed for current Encode session. + Client should allocate output buffers according to this dimension for dynamic resolution change. If set to 0, Encode will not allow dynamic resolution change. */ + NVENC_EXTERNAL_ME_HINT_COUNTS_PER_BLOCKTYPE maxMEHintCountsPerBlock[2]; /**< [in]: If Client wants to pass external motion vectors in NV_ENC_PIC_PARAMS::meExternalHints buffer it must specify the maximum number of hint candidates per block per direction for the encode session. + The NV_ENC_INITIALIZE_PARAMS::maxMEHintCountsPerBlock[0] is for L0 predictors and NV_ENC_INITIALIZE_PARAMS::maxMEHintCountsPerBlock[1] is for L1 predictors. + This client must also set NV_ENC_INITIALIZE_PARAMS::enableExternalMEHints to 1. */ + NV_ENC_TUNING_INFO tuningInfo; /**< [in]: Tuning Info of NVENC encoding(TuningInfo is not applicable to H264 and HEVC meonly mode). */ + NV_ENC_BUFFER_FORMAT bufferFormat; /**< [in]: Input buffer format. Used only when DX12 interface type is used */ + uint32_t reserved [287]; /**< [in]: Reserved and must be set to 0 */ + void* reserved2[64]; /**< [in]: Reserved and must be set to NULL */ +} NV_ENC_INITIALIZE_PARAMS; + +/** macro for constructing the version field of ::_NV_ENC_INITIALIZE_PARAMS */ +#define NV_ENC_INITIALIZE_PARAMS_VER (NVENCAPI_STRUCT_VERSION(5) | ( 1<<31 )) + + +/** + * \struct _NV_ENC_RECONFIGURE_PARAMS + * Encode Session Reconfigured parameters. + */ +typedef struct _NV_ENC_RECONFIGURE_PARAMS +{ + uint32_t version; /**< [in]: Struct version. Must be set to ::NV_ENC_RECONFIGURE_PARAMS_VER. */ + NV_ENC_INITIALIZE_PARAMS reInitEncodeParams; /**< [in]: Encoder session re-initialization parameters. + If reInitEncodeParams.encodeConfig is NULL and + reInitEncodeParams.presetGUID is the same as the preset + GUID specified on the call to NvEncInitializeEncoder(), + EncodeAPI will continue to use the existing encode + configuration. + If reInitEncodeParams.encodeConfig is NULL and + reInitEncodeParams.presetGUID is different from the preset + GUID specified on the call to NvEncInitializeEncoder(), + EncodeAPI will try to use the default configuration for + the preset specified by reInitEncodeParams.presetGUID. + In this case, reconfiguration may fail if the new + configuration is incompatible with the existing + configuration (e.g. the new configuration results in + a change in the GOP structure). */ + uint32_t resetEncoder :1; /**< [in]: This resets the rate control states and other internal encoder states. This should be used only with an IDR frame. + If NV_ENC_INITIALIZE_PARAMS::enablePTD is set to 1, encoder will force the frame type to IDR */ + uint32_t forceIDR :1; /**< [in]: Encode the current picture as an IDR picture. This flag is only valid when Picture type decision is taken by the Encoder + [_NV_ENC_INITIALIZE_PARAMS::enablePTD == 1]. */ + uint32_t reserved :30; + +}NV_ENC_RECONFIGURE_PARAMS; + +/** macro for constructing the version field of ::_NV_ENC_RECONFIGURE_PARAMS */ +#define NV_ENC_RECONFIGURE_PARAMS_VER (NVENCAPI_STRUCT_VERSION(1) | ( 1<<31 )) + +/** + * \struct _NV_ENC_PRESET_CONFIG + * Encoder preset config + */ +typedef struct _NV_ENC_PRESET_CONFIG +{ + uint32_t version; /**< [in]: Struct version. Must be set to ::NV_ENC_PRESET_CONFIG_VER. */ + NV_ENC_CONFIG presetCfg; /**< [out]: preset config returned by the Nvidia Video Encoder interface. */ + uint32_t reserved1[255]; /**< [in]: Reserved and must be set to 0 */ + void* reserved2[64]; /**< [in]: Reserved and must be set to NULL */ +}NV_ENC_PRESET_CONFIG; + +/** macro for constructing the version field of ::_NV_ENC_PRESET_CONFIG */ +#define NV_ENC_PRESET_CONFIG_VER (NVENCAPI_STRUCT_VERSION(4) | ( 1<<31 )) + + +/** + * \struct _NV_ENC_PIC_PARAMS_MVC + * MVC-specific parameters to be sent on a per-frame basis. + */ +typedef struct _NV_ENC_PIC_PARAMS_MVC +{ + uint32_t version; /**< [in]: Struct version. Must be set to ::NV_ENC_PIC_PARAMS_MVC_VER. */ + uint32_t viewID; /**< [in]: Specifies the view ID associated with the current input view. */ + uint32_t temporalID; /**< [in]: Specifies the temporal ID associated with the current input view. */ + uint32_t priorityID; /**< [in]: Specifies the priority ID associated with the current input view. Reserved and ignored by the NvEncodeAPI interface. */ + uint32_t reserved1[12]; /**< [in]: Reserved and must be set to 0. */ + void* reserved2[8]; /**< [in]: Reserved and must be set to NULL. */ +}NV_ENC_PIC_PARAMS_MVC; + +/** macro for constructing the version field of ::_NV_ENC_PIC_PARAMS_MVC */ +#define NV_ENC_PIC_PARAMS_MVC_VER NVENCAPI_STRUCT_VERSION(1) + + +/** + * \union _NV_ENC_PIC_PARAMS_H264_EXT + * H264 extension picture parameters + */ +typedef union _NV_ENC_PIC_PARAMS_H264_EXT +{ + NV_ENC_PIC_PARAMS_MVC mvcPicParams; /**< [in]: Specifies the MVC picture parameters. */ + uint32_t reserved1[32]; /**< [in]: Reserved and must be set to 0. */ +}NV_ENC_PIC_PARAMS_H264_EXT; + +/** + * \struct _NV_ENC_SEI_PAYLOAD + * User SEI message + */ +typedef struct _NV_ENC_SEI_PAYLOAD +{ + uint32_t payloadSize; /**< [in] SEI payload size in bytes. SEI payload must be byte aligned, as described in Annex D */ + uint32_t payloadType; /**< [in] SEI payload types and syntax can be found in Annex D of the H.264 Specification. */ + uint8_t *payload; /**< [in] pointer to user data */ +} NV_ENC_SEI_PAYLOAD; + +#define NV_ENC_H264_SEI_PAYLOAD NV_ENC_SEI_PAYLOAD + +/** + * \struct _NV_ENC_PIC_PARAMS_H264 + * H264 specific enc pic params. sent on a per frame basis. + */ +typedef struct _NV_ENC_PIC_PARAMS_H264 +{ + uint32_t displayPOCSyntax; /**< [in]: Specifies the display POC syntax This is required to be set if client is handling the picture type decision. */ + uint32_t reserved3; /**< [in]: Reserved and must be set to 0 */ + uint32_t refPicFlag; /**< [in]: Set to 1 for a reference picture. This is ignored if NV_ENC_INITIALIZE_PARAMS::enablePTD is set to 1. */ + uint32_t colourPlaneId; /**< [in]: Specifies the colour plane ID associated with the current input. */ + uint32_t forceIntraRefreshWithFrameCnt; /**< [in]: Forces an intra refresh with duration equal to intraRefreshFrameCnt. + When outputRecoveryPointSEI is set this is value is used for recovery_frame_cnt in recovery point SEI message + forceIntraRefreshWithFrameCnt cannot be used if B frames are used in the GOP structure specified */ + uint32_t constrainedFrame :1; /**< [in]: Set to 1 if client wants to encode this frame with each slice completely independent of other slices in the frame. + NV_ENC_INITIALIZE_PARAMS::enableConstrainedEncoding should be set to 1 */ + uint32_t sliceModeDataUpdate :1; /**< [in]: Set to 1 if client wants to change the sliceModeData field to specify new sliceSize Parameter + When forceIntraRefreshWithFrameCnt is set it will have priority over sliceMode setting */ + uint32_t ltrMarkFrame :1; /**< [in]: Set to 1 if client wants to mark this frame as LTR */ + uint32_t ltrUseFrames :1; /**< [in]: Set to 1 if client allows encoding this frame using the LTR frames specified in ltrFrameBitmap */ + uint32_t reservedBitFields :28; /**< [in]: Reserved bit fields and must be set to 0 */ + uint8_t* sliceTypeData; /**< [in]: Deprecated. */ + uint32_t sliceTypeArrayCnt; /**< [in]: Deprecated. */ + uint32_t seiPayloadArrayCnt; /**< [in]: Specifies the number of elements allocated in seiPayloadArray array. */ + NV_ENC_SEI_PAYLOAD* seiPayloadArray; /**< [in]: Array of SEI payloads which will be inserted for this frame. */ + uint32_t sliceMode; /**< [in]: This parameter in conjunction with sliceModeData specifies the way in which the picture is divided into slices + sliceMode = 0 MB based slices, sliceMode = 1 Byte based slices, sliceMode = 2 MB row based slices, sliceMode = 3, numSlices in Picture + When forceIntraRefreshWithFrameCnt is set it will have priority over sliceMode setting + When sliceMode == 0 and sliceModeData == 0 whole picture will be coded with one slice */ + uint32_t sliceModeData; /**< [in]: Specifies the parameter needed for sliceMode. For: + sliceMode = 0, sliceModeData specifies # of MBs in each slice (except last slice) + sliceMode = 1, sliceModeData specifies maximum # of bytes in each slice (except last slice) + sliceMode = 2, sliceModeData specifies # of MB rows in each slice (except last slice) + sliceMode = 3, sliceModeData specifies number of slices in the picture. Driver will divide picture into slices optimally */ + uint32_t ltrMarkFrameIdx; /**< [in]: Specifies the long term referenceframe index to use for marking this frame as LTR.*/ + uint32_t ltrUseFrameBitmap; /**< [in]: Specifies the associated bitmap of LTR frame indices to use when encoding this frame. */ + uint32_t ltrUsageMode; /**< [in]: Not supported. Reserved for future use and must be set to 0. */ + uint32_t forceIntraSliceCount; /**< [in]: Specifies the number of slices to be forced to Intra in the current picture. + This option along with forceIntraSliceIdx[] array needs to be used with sliceMode = 3 only */ + uint32_t *forceIntraSliceIdx; /**< [in]: Slice indices to be forced to intra in the current picture. Each slice index should be <= num_slices_in_picture -1. Index starts from 0 for first slice. + The number of entries in this array should be equal to forceIntraSliceCount */ + NV_ENC_PIC_PARAMS_H264_EXT h264ExtPicParams; /**< [in]: Specifies the H264 extension config parameters using this config. */ + NV_ENC_TIME_CODE timeCode; /**< [in]: Specifies the clock timestamp sets used in picture timing SEI. Applicable only when NV_ENC_CONFIG_H264::enableTimeCode is set to 1. */ + uint32_t reserved [203]; /**< [in]: Reserved and must be set to 0. */ + void* reserved2[61]; /**< [in]: Reserved and must be set to NULL. */ +} NV_ENC_PIC_PARAMS_H264; + +/** + * \struct _NV_ENC_PIC_PARAMS_HEVC + * HEVC specific enc pic params. sent on a per frame basis. + */ +typedef struct _NV_ENC_PIC_PARAMS_HEVC +{ + uint32_t displayPOCSyntax; /**< [in]: Specifies the display POC syntax This is required to be set if client is handling the picture type decision. */ + uint32_t refPicFlag; /**< [in]: Set to 1 for a reference picture. This is ignored if NV_ENC_INITIALIZE_PARAMS::enablePTD is set to 1. */ + uint32_t temporalId; /**< [in]: Specifies the temporal id of the picture */ + uint32_t forceIntraRefreshWithFrameCnt; /**< [in]: Forces an intra refresh with duration equal to intraRefreshFrameCnt. + When outputRecoveryPointSEI is set this is value is used for recovery_frame_cnt in recovery point SEI message + forceIntraRefreshWithFrameCnt cannot be used if B frames are used in the GOP structure specified */ + uint32_t constrainedFrame :1; /**< [in]: Set to 1 if client wants to encode this frame with each slice completely independent of other slices in the frame. + NV_ENC_INITIALIZE_PARAMS::enableConstrainedEncoding should be set to 1 */ + uint32_t sliceModeDataUpdate :1; /**< [in]: Set to 1 if client wants to change the sliceModeData field to specify new sliceSize Parameter + When forceIntraRefreshWithFrameCnt is set it will have priority over sliceMode setting */ + uint32_t ltrMarkFrame :1; /**< [in]: Set to 1 if client wants to mark this frame as LTR */ + uint32_t ltrUseFrames :1; /**< [in]: Set to 1 if client allows encoding this frame using the LTR frames specified in ltrFrameBitmap */ + uint32_t reservedBitFields :28; /**< [in]: Reserved bit fields and must be set to 0 */ + uint8_t* sliceTypeData; /**< [in]: Array which specifies the slice type used to force intra slice for a particular slice. Currently supported only for NV_ENC_CONFIG_H264::sliceMode == 3. + Client should allocate array of size sliceModeData where sliceModeData is specified in field of ::_NV_ENC_CONFIG_H264 + Array element with index n corresponds to nth slice. To force a particular slice to intra client should set corresponding array element to NV_ENC_SLICE_TYPE_I + all other array elements should be set to NV_ENC_SLICE_TYPE_DEFAULT */ + uint32_t sliceTypeArrayCnt; /**< [in]: Client should set this to the number of elements allocated in sliceTypeData array. If sliceTypeData is NULL then this should be set to 0 */ + uint32_t sliceMode; /**< [in]: This parameter in conjunction with sliceModeData specifies the way in which the picture is divided into slices + sliceMode = 0 CTU based slices, sliceMode = 1 Byte based slices, sliceMode = 2 CTU row based slices, sliceMode = 3, numSlices in Picture + When forceIntraRefreshWithFrameCnt is set it will have priority over sliceMode setting + When sliceMode == 0 and sliceModeData == 0 whole picture will be coded with one slice */ + uint32_t sliceModeData; /**< [in]: Specifies the parameter needed for sliceMode. For: + sliceMode = 0, sliceModeData specifies # of CTUs in each slice (except last slice) + sliceMode = 1, sliceModeData specifies maximum # of bytes in each slice (except last slice) + sliceMode = 2, sliceModeData specifies # of CTU rows in each slice (except last slice) + sliceMode = 3, sliceModeData specifies number of slices in the picture. Driver will divide picture into slices optimally */ + uint32_t ltrMarkFrameIdx; /**< [in]: Specifies the long term reference frame index to use for marking this frame as LTR.*/ + uint32_t ltrUseFrameBitmap; /**< [in]: Specifies the associated bitmap of LTR frame indices to use when encoding this frame. */ + uint32_t ltrUsageMode; /**< [in]: Not supported. Reserved for future use and must be set to 0. */ + uint32_t seiPayloadArrayCnt; /**< [in]: Specifies the number of elements allocated in seiPayloadArray array. */ + uint32_t reserved; /**< [in]: Reserved and must be set to 0. */ + NV_ENC_SEI_PAYLOAD* seiPayloadArray; /**< [in]: Array of SEI payloads which will be inserted for this frame. */ + NV_ENC_TIME_CODE timeCode; /**< [in]: Specifies the clock timestamp sets used in time code SEI. Applicable only when NV_ENC_CONFIG_HEVC::enableTimeCodeSEI is set to 1. */ + uint32_t reserved2 [237]; /**< [in]: Reserved and must be set to 0. */ + void* reserved3[61]; /**< [in]: Reserved and must be set to NULL. */ +} NV_ENC_PIC_PARAMS_HEVC; + +#define NV_ENC_AV1_OBU_PAYLOAD NV_ENC_SEI_PAYLOAD + +/** +* \struct _NV_ENC_PIC_PARAMS_AV1 +* AV1 specific enc pic params. sent on a per frame basis. +*/ +typedef struct _NV_ENC_PIC_PARAMS_AV1 +{ + uint32_t displayPOCSyntax; /**< [in]: Specifies the display POC syntax This is required to be set if client is handling the picture type decision. */ + uint32_t refPicFlag; /**< [in]: Set to 1 for a reference picture. This is ignored if NV_ENC_INITIALIZE_PARAMS::enablePTD is set to 1. */ + uint32_t temporalId; /**< [in]: Specifies the temporal id of the picture */ + uint32_t forceIntraRefreshWithFrameCnt; /**< [in]: Forces an intra refresh with duration equal to intraRefreshFrameCnt. + forceIntraRefreshWithFrameCnt cannot be used if B frames are used in the GOP structure specified */ + uint32_t goldenFrameFlag : 1; /**< [in]: Encode frame as Golden Frame. This is ignored if NV_ENC_INITIALIZE_PARAMS::enablePTD is set to 1. */ + uint32_t arfFrameFlag : 1; /**< [in]: Encode frame as Alternate Reference Frame. This is ignored if NV_ENC_INITIALIZE_PARAMS::enablePTD is set to 1. */ + uint32_t arf2FrameFlag : 1; /**< [in]: Encode frame as Alternate Reference 2 Frame. This is ignored if NV_ENC_INITIALIZE_PARAMS::enablePTD is set to 1. */ + uint32_t bwdFrameFlag : 1; /**< [in]: Encode frame as Backward Reference Frame. This is ignored if NV_ENC_INITIALIZE_PARAMS::enablePTD is set to 1. */ + uint32_t overlayFrameFlag : 1; /**< [in]: Encode frame as overlay frame. A previously encoded frame with the same displayPOCSyntax value should be present in reference frame buffer. + This is ignored if NV_ENC_INITIALIZE_PARAMS::enablePTD is set to 1. */ + uint32_t showExistingFrameFlag : 1; /**< [in]: When ovelayFrameFlag is set to 1, this flag controls the value of the show_existing_frame syntax element associated with the overlay frame. + This flag is added to the interface as a placeholder. Its value is ignored for now and always assumed to be set to 1. + This is ignored if NV_ENC_INITIALIZE_PARAMS::enablePTD is set to 1. */ + uint32_t errorResilientModeFlag : 1; /**< [in]: encode frame independently from previously encoded frames */ + + uint32_t tileConfigUpdate : 1; /**< [in]: Set to 1 if client wants to overwrite the default tile configuration with the tile parameters specified below + When forceIntraRefreshWithFrameCnt is set it will have priority over tileConfigUpdate setting */ + uint32_t enableCustomTileConfig : 1; /**< [in]: Set 1 to enable custom tile configuration: numTileColumns and numTileRows must have non zero values and tileWidths and tileHeights must point to a valid address */ + uint32_t filmGrainParamsUpdate : 1; /**< [in]: Set to 1 if client wants to update previous film grain parameters: filmGrainParams must point to a valid address and encoder must have been configured with film grain enabled */ + uint32_t reservedBitFields : 22; /**< [in]: Reserved bitfields and must be set to 0 */ + uint32_t numTileColumns; /**< [in]: This parameter in conjunction with the flag enableCustomTileConfig and the array tileWidths[] specifies the way in which the picture is divided into tile columns. + When enableCustomTileConfig == 0, the picture will be uniformly divided into numTileColumns tile columns. If numTileColumns is not a power of 2, + it will be rounded down to the next power of 2 value. If numTileColumns == 0, the picture will be coded with the smallest number of vertical tiles as allowed by standard. + When enableCustomTileConfig == 1, numTileColumns must be > 0 and <= NV_MAX_TILE_COLS_AV1 and tileWidths must point to a valid array of numTileColumns entries. + Entry i specifies the width in 64x64 CTU unit of tile colum i. The sum of all the entries should be equal to the picture width in 64x64 CTU units. */ + uint32_t numTileRows; /**< [in]: This parameter in conjunction with the flag enableCustomTileConfig and the array tileHeights[] specifies the way in which the picture is divided into tiles rows + When enableCustomTileConfig == 0, the picture will be uniformly divided into numTileRows tile rows. If numTileRows is not a power of 2, + it will be rounded down to the next power of 2 value. If numTileRows == 0, the picture will be coded with the smallest number of horizontal tiles as allowed by standard. + When enableCustomTileConfig == 1, numTileRows must be > 0 and <= NV_MAX_TILE_ROWS_AV1 and tileHeights must point to a valid array of numTileRows entries. + Entry i specifies the height in 64x64 CTU unit of tile row i. The sum of all the entries should be equal to the picture hieght in 64x64 CTU units. */ + uint32_t *tileWidths; /**< [in]: If enableCustomTileConfig == 1, tileWidths[i] specifies the width of tile column i in 64x64 CTU unit, with 0 <= i <= numTileColumns -1. */ + uint32_t *tileHeights; /**< [in]: If enableCustomTileConfig == 1, tileHeights[i] specifies the height of tile row i in 64x64 CTU unit, with 0 <= i <= numTileRows -1. */ + uint32_t obuPayloadArrayCnt; /**< [in]: Specifies the number of elements allocated in obuPayloadArray array. */ + uint32_t reserved; /**< [in]: Reserved and must be set to 0. */ + NV_ENC_AV1_OBU_PAYLOAD* obuPayloadArray; /**< [in]: Array of OBU payloads which will be inserted for this frame. */ + NV_ENC_FILM_GRAIN_PARAMS_AV1 *filmGrainParams; /**< [in]: If filmGrainParamsUpdate == 1, filmGrainParams must point to a valid NV_ENC_FILM_GRAIN_PARAMS_AV1 structure */ + uint32_t reserved2[247]; /**< [in]: Reserved and must be set to 0. */ + void* reserved3[61]; /**< [in]: Reserved and must be set to NULL. */ +} NV_ENC_PIC_PARAMS_AV1; + +/** + * Codec specific per-picture encoding parameters. + */ +typedef union _NV_ENC_CODEC_PIC_PARAMS +{ + NV_ENC_PIC_PARAMS_H264 h264PicParams; /**< [in]: H264 encode picture params. */ + NV_ENC_PIC_PARAMS_HEVC hevcPicParams; /**< [in]: HEVC encode picture params. */ + NV_ENC_PIC_PARAMS_AV1 av1PicParams; /**< [in]: AV1 encode picture params. */ + uint32_t reserved[256]; /**< [in]: Reserved and must be set to 0. */ +} NV_ENC_CODEC_PIC_PARAMS; + + +/** + * \struct _NV_ENC_PIC_PARAMS + * Encoding parameters that need to be sent on a per frame basis. + */ +typedef struct _NV_ENC_PIC_PARAMS +{ + uint32_t version; /**< [in]: Struct version. Must be set to ::NV_ENC_PIC_PARAMS_VER. */ + uint32_t inputWidth; /**< [in]: Specifies the input frame width */ + uint32_t inputHeight; /**< [in]: Specifies the input frame height */ + uint32_t inputPitch; /**< [in]: Specifies the input buffer pitch. If pitch value is not known, set this to inputWidth. */ + uint32_t encodePicFlags; /**< [in]: Specifies bit-wise OR of encode picture flags. See ::NV_ENC_PIC_FLAGS enum. */ + uint32_t frameIdx; /**< [in]: Specifies the frame index associated with the input frame [optional]. */ + uint64_t inputTimeStamp; /**< [in]: Specifies opaque data which is associated with the encoded frame, but not actually encoded in the output bitstream. + This opaque data can be used later to uniquely refer to the corresponding encoded frame. For example, it can be used + for identifying the frame to be invalidated in the reference picture buffer, if lost at the client. */ + uint64_t inputDuration; /**< [in]: Specifies duration of the input picture */ + NV_ENC_INPUT_PTR inputBuffer; /**< [in]: Specifies the input buffer pointer. Client must use a pointer obtained from ::NvEncCreateInputBuffer() or ::NvEncMapInputResource() APIs.*/ + NV_ENC_OUTPUT_PTR outputBitstream; /**< [in]: Specifies the output buffer pointer. + If NV_ENC_INITIALIZE_PARAMS::enableOutputInVidmem is set to 0, specifies the pointer to output buffer. Client should use a pointer obtained from ::NvEncCreateBitstreamBuffer() API. + If NV_ENC_INITIALIZE_PARAMS::enableOutputInVidmem is set to 1, client should allocate buffer in video memory for NV_ENC_ENCODE_OUT_PARAMS struct and encoded bitstream data. Client + should use a pointer obtained from ::NvEncMapInputResource() API, when mapping this output buffer and assign it to NV_ENC_PIC_PARAMS::outputBitstream. + First 256 bytes of this buffer should be interpreted as NV_ENC_ENCODE_OUT_PARAMS struct followed by encoded bitstream data. Recommended size for output buffer is sum of size of + NV_ENC_ENCODE_OUT_PARAMS struct and twice the input frame size for lower resolution eg. CIF and 1.5 times the input frame size for higher resolutions. If encoded bitstream size is + greater than the allocated buffer size for encoded bitstream, then the output buffer will have encoded bitstream data equal to buffer size. All CUDA operations on this buffer must use + the default stream. */ + void* completionEvent; /**< [in]: Specifies an event to be signaled on completion of encoding of this Frame [only if operating in Asynchronous mode]. Each output buffer should be associated with a distinct event pointer. */ + NV_ENC_BUFFER_FORMAT bufferFmt; /**< [in]: Specifies the input buffer format. */ + NV_ENC_PIC_STRUCT pictureStruct; /**< [in]: Specifies structure of the input picture. */ + NV_ENC_PIC_TYPE pictureType; /**< [in]: Specifies input picture type. Client required to be set explicitly by the client if the client has not set NV_ENC_INITALIZE_PARAMS::enablePTD to 1 while calling NvInitializeEncoder. */ + NV_ENC_CODEC_PIC_PARAMS codecPicParams; /**< [in]: Specifies the codec specific per-picture encoding parameters. */ + NVENC_EXTERNAL_ME_HINT_COUNTS_PER_BLOCKTYPE meHintCountsPerBlock[2]; /**< [in]: For H264 and Hevc, specifies the number of hint candidates per block per direction for the current frame. meHintCountsPerBlock[0] is for L0 predictors and meHintCountsPerBlock[1] is for L1 predictors. + The candidate count in NV_ENC_PIC_PARAMS::meHintCountsPerBlock[lx] must never exceed NV_ENC_INITIALIZE_PARAMS::maxMEHintCountsPerBlock[lx] provided during encoder initialization. */ + NVENC_EXTERNAL_ME_HINT *meExternalHints; /**< [in]: For H264 and Hevc, Specifies the pointer to ME external hints for the current frame. The size of ME hint buffer should be equal to number of macroblocks * the total number of candidates per macroblock. + The total number of candidates per MB per direction = 1*meHintCountsPerBlock[Lx].numCandsPerBlk16x16 + 2*meHintCountsPerBlock[Lx].numCandsPerBlk16x8 + 2*meHintCountsPerBlock[Lx].numCandsPerBlk8x8 + + 4*meHintCountsPerBlock[Lx].numCandsPerBlk8x8. For frames using bidirectional ME , the total number of candidates for single macroblock is sum of total number of candidates per MB for each direction (L0 and L1) */ + uint32_t reserved1[6]; /**< [in]: Reserved and must be set to 0 */ + void* reserved2[2]; /**< [in]: Reserved and must be set to NULL */ + int8_t *qpDeltaMap; /**< [in]: Specifies the pointer to signed byte array containing value per MB for H264, per CTB for HEVC and per SB for AV1 in raster scan order for the current picture, which will be interpreted depending on NV_ENC_RC_PARAMS::qpMapMode. + If NV_ENC_RC_PARAMS::qpMapMode is NV_ENC_QP_MAP_DELTA, qpDeltaMap specifies QP modifier per MB for H264, per CTB for HEVC and per SB for AV1. This QP modifier will be applied on top of the QP chosen by rate control. + If NV_ENC_RC_PARAMS::qpMapMode is NV_ENC_QP_MAP_EMPHASIS, qpDeltaMap specifies Emphasis Level Map per MB for H264. This level value along with QP chosen by rate control is used to + compute the QP modifier, which in turn is applied on top of QP chosen by rate control. + If NV_ENC_RC_PARAMS::qpMapMode is NV_ENC_QP_MAP_DISABLED, value in qpDeltaMap will be ignored.*/ + uint32_t qpDeltaMapSize; /**< [in]: Specifies the size in bytes of qpDeltaMap surface allocated by client and pointed to by NV_ENC_PIC_PARAMS::qpDeltaMap. Surface (array) should be picWidthInMbs * picHeightInMbs for H264, picWidthInCtbs * picHeightInCtbs for HEVC and + picWidthInSbs * picHeightInSbs for AV1 */ + uint32_t reservedBitFields; /**< [in]: Reserved bitfields and must be set to 0 */ + uint16_t meHintRefPicDist[2]; /**< [in]: Specifies temporal distance for reference picture (NVENC_EXTERNAL_ME_HINT::refidx = 0) used during external ME with NV_ENC_INITALIZE_PARAMS::enablePTD = 1 . meHintRefPicDist[0] is for L0 hints and meHintRefPicDist[1] is for L1 hints. + If not set, will internally infer distance of 1. Ignored for NV_ENC_INITALIZE_PARAMS::enablePTD = 0 */ + NV_ENC_INPUT_PTR alphaBuffer; /**< [in]: Specifies the input alpha buffer pointer. Client must use a pointer obtained from ::NvEncCreateInputBuffer() or ::NvEncMapInputResource() APIs. + Applicable only when encoding hevc with alpha layer is enabled. */ + NVENC_EXTERNAL_ME_SB_HINT *meExternalSbHints; /**< [in]: For AV1,Specifies the pointer to ME external SB hints for the current frame. The size of ME hint buffer should be equal to meSbHintsCount. */ + uint32_t meSbHintsCount; /**< [in]: For AV1, specifies the total number of external ME SB hint candidates for the frame + NV_ENC_PIC_PARAMS::meSbHintsCount must never exceed the total number of SBs in frame * the max number of candidates per SB provided during encoder initialization. + The max number of candidates per SB is maxMeHintCountsPerBlock[0].numCandsPerSb + maxMeHintCountsPerBlock[1].numCandsPerSb */ + uint32_t reserved3[285]; /**< [in]: Reserved and must be set to 0 */ + void* reserved4[58]; /**< [in]: Reserved and must be set to NULL */ +} NV_ENC_PIC_PARAMS; + +/** Macro for constructing the version field of ::_NV_ENC_PIC_PARAMS */ +#define NV_ENC_PIC_PARAMS_VER (NVENCAPI_STRUCT_VERSION(6) | ( 1<<31 )) + + +/** + * \struct _NV_ENC_MEONLY_PARAMS + * MEOnly parameters that need to be sent on a per motion estimation basis. + * NV_ENC_MEONLY_PARAMS::meExternalHints is supported for H264 only. + */ +typedef struct _NV_ENC_MEONLY_PARAMS +{ + uint32_t version; /**< [in]: Struct version. Must be set to NV_ENC_MEONLY_PARAMS_VER.*/ + uint32_t inputWidth; /**< [in]: Specifies the input frame width */ + uint32_t inputHeight; /**< [in]: Specifies the input frame height */ + NV_ENC_INPUT_PTR inputBuffer; /**< [in]: Specifies the input buffer pointer. Client must use a pointer obtained from NvEncCreateInputBuffer() or NvEncMapInputResource() APIs. */ + NV_ENC_INPUT_PTR referenceFrame; /**< [in]: Specifies the reference frame pointer */ + NV_ENC_OUTPUT_PTR mvBuffer; /**< [in]: Specifies the output buffer pointer. + If NV_ENC_INITIALIZE_PARAMS::enableOutputInVidmem is set to 0, specifies the pointer to motion vector data buffer allocated by NvEncCreateMVBuffer. + Client must lock mvBuffer using ::NvEncLockBitstream() API to get the motion vector data. + If NV_ENC_INITIALIZE_PARAMS::enableOutputInVidmem is set to 1, client should allocate buffer in video memory for storing the motion vector data. The size of this buffer must + be equal to total number of macroblocks multiplied by size of NV_ENC_H264_MV_DATA struct. Client should use a pointer obtained from ::NvEncMapInputResource() API, when mapping this + output buffer and assign it to NV_ENC_MEONLY_PARAMS::mvBuffer. All CUDA operations on this buffer must use the default stream. */ + NV_ENC_BUFFER_FORMAT bufferFmt; /**< [in]: Specifies the input buffer format. */ + void* completionEvent; /**< [in]: Specifies an event to be signaled on completion of motion estimation + of this Frame [only if operating in Asynchronous mode]. + Each output buffer should be associated with a distinct event pointer. */ + uint32_t viewID; /**< [in]: Specifies left or right viewID if NV_ENC_CONFIG_H264_MEONLY::bStereoEnable is set. + viewID can be 0,1 if bStereoEnable is set, 0 otherwise. */ + NVENC_EXTERNAL_ME_HINT_COUNTS_PER_BLOCKTYPE + meHintCountsPerBlock[2]; /**< [in]: Specifies the number of hint candidates per block for the current frame. meHintCountsPerBlock[0] is for L0 predictors. + The candidate count in NV_ENC_PIC_PARAMS::meHintCountsPerBlock[lx] must never exceed NV_ENC_INITIALIZE_PARAMS::maxMEHintCountsPerBlock[lx] provided during encoder initialization. */ + NVENC_EXTERNAL_ME_HINT *meExternalHints; /**< [in]: Specifies the pointer to ME external hints for the current frame. The size of ME hint buffer should be equal to number of macroblocks * the total number of candidates per macroblock. + The total number of candidates per MB per direction = 1*meHintCountsPerBlock[Lx].numCandsPerBlk16x16 + 2*meHintCountsPerBlock[Lx].numCandsPerBlk16x8 + 2*meHintCountsPerBlock[Lx].numCandsPerBlk8x8 + + 4*meHintCountsPerBlock[Lx].numCandsPerBlk8x8. For frames using bidirectional ME , the total number of candidates for single macroblock is sum of total number of candidates per MB for each direction (L0 and L1) */ + uint32_t reserved1[243]; /**< [in]: Reserved and must be set to 0 */ + void* reserved2[59]; /**< [in]: Reserved and must be set to NULL */ +} NV_ENC_MEONLY_PARAMS; + +/** NV_ENC_MEONLY_PARAMS struct version*/ +#define NV_ENC_MEONLY_PARAMS_VER NVENCAPI_STRUCT_VERSION(3) + + +/** + * \struct _NV_ENC_LOCK_BITSTREAM + * Bitstream buffer lock parameters. + */ +typedef struct _NV_ENC_LOCK_BITSTREAM +{ + uint32_t version; /**< [in]: Struct version. Must be set to ::NV_ENC_LOCK_BITSTREAM_VER. */ + uint32_t doNotWait :1; /**< [in]: If this flag is set, the NvEncodeAPI interface will return buffer pointer even if operation is not completed. If not set, the call will block until operation completes. */ + uint32_t ltrFrame :1; /**< [out]: Flag indicating this frame is marked as LTR frame */ + uint32_t getRCStats :1; /**< [in]: If this flag is set then lockBitstream call will add additional intra-inter MB count and average MVX, MVY */ + uint32_t reservedBitFields :29; /**< [in]: Reserved bit fields and must be set to 0 */ + void* outputBitstream; /**< [in]: Pointer to the bitstream buffer being locked. */ + uint32_t* sliceOffsets; /**< [in, out]: Array which receives the slice (H264/HEVC) or tile (AV1) offsets. This is not supported if NV_ENC_CONFIG_H264::sliceMode is 1 on Kepler GPUs. Array size must be equal to size of frame in MBs. */ + uint32_t frameIdx; /**< [out]: Frame no. for which the bitstream is being retrieved. */ + uint32_t hwEncodeStatus; /**< [out]: The NvEncodeAPI interface status for the locked picture. */ + uint32_t numSlices; /**< [out]: Number of slices (H264/HEVC) or tiles (AV1) in the encoded picture. Will be reported only if NV_ENC_INITIALIZE_PARAMS::reportSliceOffsets set to 1. */ + uint32_t bitstreamSizeInBytes; /**< [out]: Actual number of bytes generated and copied to the memory pointed by bitstreamBufferPtr. + When HEVC alpha layer encoding is enabled, this field reports the total encoded size in bytes i.e it is the encoded size of the base plus the alpha layer. + For AV1 when enablePTD is set, this field reports the total encoded size in bytes of all the encoded frames packed into the current output surface i.e. show frame plus all preceding no-show frames */ + uint64_t outputTimeStamp; /**< [out]: Presentation timestamp associated with the encoded output. */ + uint64_t outputDuration; /**< [out]: Presentation duration associates with the encoded output. */ + void* bitstreamBufferPtr; /**< [out]: Pointer to the generated output bitstream. + For MEOnly mode _NV_ENC_LOCK_BITSTREAM::bitstreamBufferPtr should be typecast to + NV_ENC_H264_MV_DATA/NV_ENC_HEVC_MV_DATA pointer respectively for H264/HEVC */ + NV_ENC_PIC_TYPE pictureType; /**< [out]: Picture type of the encoded picture. */ + NV_ENC_PIC_STRUCT pictureStruct; /**< [out]: Structure of the generated output picture. */ + uint32_t frameAvgQP; /**< [out]: Average QP of the frame. */ + uint32_t frameSatd; /**< [out]: Total SATD cost for whole frame. */ + uint32_t ltrFrameIdx; /**< [out]: Frame index associated with this LTR frame. */ + uint32_t ltrFrameBitmap; /**< [out]: Bitmap of LTR frames indices which were used for encoding this frame. Value of 0 if no LTR frames were used. */ + uint32_t temporalId; /**< [out]: TemporalId value of the frame when using temporalSVC encoding */ + uint32_t reserved[12]; /**< [in]: Reserved and must be set to 0 */ + uint32_t intraMBCount; /**< [out]: For H264, Number of Intra MBs in the encoded frame. For HEVC, Number of Intra CTBs in the encoded frame. For AV1, Number of Intra SBs in the encoded show frame. Supported only if _NV_ENC_LOCK_BITSTREAM::getRCStats set to 1. */ + uint32_t interMBCount; /**< [out]: For H264, Number of Inter MBs in the encoded frame, includes skip MBs. For HEVC, Number of Inter CTBs in the encoded frame. For AV1, Number of Inter SBs in the encoded show frame. Supported only if _NV_ENC_LOCK_BITSTREAM::getRCStats set to 1. */ + int32_t averageMVX; /**< [out]: Average Motion Vector in X direction for the encoded frame. Supported only if _NV_ENC_LOCK_BITSTREAM::getRCStats set to 1. */ + int32_t averageMVY; /**< [out]: Average Motion Vector in y direction for the encoded frame. Supported only if _NV_ENC_LOCK_BITSTREAM::getRCStats set to 1. */ + uint32_t alphaLayerSizeInBytes; /**< [out]: Number of bytes generated for the alpha layer in the encoded output. Applicable only when HEVC with alpha encoding is enabled. */ + + uint32_t reserved1[218]; /**< [in]: Reserved and must be set to 0 */ + void* reserved2[64]; /**< [in]: Reserved and must be set to NULL */ +} NV_ENC_LOCK_BITSTREAM; + +/** Macro for constructing the version field of ::_NV_ENC_LOCK_BITSTREAM */ +#define NV_ENC_LOCK_BITSTREAM_VER NVENCAPI_STRUCT_VERSION(2) + + +/** + * \struct _NV_ENC_LOCK_INPUT_BUFFER + * Uncompressed Input Buffer lock parameters. + */ +typedef struct _NV_ENC_LOCK_INPUT_BUFFER +{ + uint32_t version; /**< [in]: Struct version. Must be set to ::NV_ENC_LOCK_INPUT_BUFFER_VER. */ + uint32_t doNotWait :1; /**< [in]: Set to 1 to make ::NvEncLockInputBuffer() a unblocking call. If the encoding is not completed, driver will return ::NV_ENC_ERR_ENCODER_BUSY error code. */ + uint32_t reservedBitFields :31; /**< [in]: Reserved bitfields and must be set to 0 */ + NV_ENC_INPUT_PTR inputBuffer; /**< [in]: Pointer to the input buffer to be locked, client should pass the pointer obtained from ::NvEncCreateInputBuffer() or ::NvEncMapInputResource API. */ + void* bufferDataPtr; /**< [out]: Pointed to the locked input buffer data. Client can only access input buffer using the \p bufferDataPtr. */ + uint32_t pitch; /**< [out]: Pitch of the locked input buffer. */ + uint32_t reserved1[251]; /**< [in]: Reserved and must be set to 0 */ + void* reserved2[64]; /**< [in]: Reserved and must be set to NULL */ +} NV_ENC_LOCK_INPUT_BUFFER; + +/** Macro for constructing the version field of ::_NV_ENC_LOCK_INPUT_BUFFER */ +#define NV_ENC_LOCK_INPUT_BUFFER_VER NVENCAPI_STRUCT_VERSION(1) + + +/** + * \struct _NV_ENC_MAP_INPUT_RESOURCE + * Map an input resource to a Nvidia Encoder Input Buffer + */ +typedef struct _NV_ENC_MAP_INPUT_RESOURCE +{ + uint32_t version; /**< [in]: Struct version. Must be set to ::NV_ENC_MAP_INPUT_RESOURCE_VER. */ + uint32_t subResourceIndex; /**< [in]: Deprecated. Do not use. */ + void* inputResource; /**< [in]: Deprecated. Do not use. */ + NV_ENC_REGISTERED_PTR registeredResource; /**< [in]: The Registered resource handle obtained by calling NvEncRegisterInputResource. */ + NV_ENC_INPUT_PTR mappedResource; /**< [out]: Mapped pointer corresponding to the registeredResource. This pointer must be used in NV_ENC_PIC_PARAMS::inputBuffer parameter in ::NvEncEncodePicture() API. */ + NV_ENC_BUFFER_FORMAT mappedBufferFmt; /**< [out]: Buffer format of the outputResource. This buffer format must be used in NV_ENC_PIC_PARAMS::bufferFmt if client using the above mapped resource pointer. */ + uint32_t reserved1[251]; /**< [in]: Reserved and must be set to 0. */ + void* reserved2[63]; /**< [in]: Reserved and must be set to NULL */ +} NV_ENC_MAP_INPUT_RESOURCE; + +/** Macro for constructing the version field of ::_NV_ENC_MAP_INPUT_RESOURCE */ +#define NV_ENC_MAP_INPUT_RESOURCE_VER NVENCAPI_STRUCT_VERSION(4) + +/** + * \struct _NV_ENC_INPUT_RESOURCE_OPENGL_TEX + * NV_ENC_REGISTER_RESOURCE::resourceToRegister must be a pointer to a variable of this type, + * when NV_ENC_REGISTER_RESOURCE::resourceType is NV_ENC_INPUT_RESOURCE_TYPE_OPENGL_TEX + */ +typedef struct _NV_ENC_INPUT_RESOURCE_OPENGL_TEX +{ + uint32_t texture; /**< [in]: The name of the texture to be used. */ + uint32_t target; /**< [in]: Accepted values are GL_TEXTURE_RECTANGLE and GL_TEXTURE_2D. */ +} NV_ENC_INPUT_RESOURCE_OPENGL_TEX; + +/** \struct NV_ENC_FENCE_POINT_D3D12 +* Fence and fence value for synchronization. +*/ +typedef struct _NV_ENC_FENCE_POINT_D3D12 +{ + uint32_t version; /**< [in]: Struct version. Must be set to ::NV_ENC_FENCE_POINT_D3D12_VER. */ + uint32_t reserved; /**< [in]: Reserved and must be set to 0. */ + void* pFence; /**< [in]: Pointer to ID3D12Fence. This fence object is used for synchronization. */ + uint64_t waitValue; /**< [in]: Fence value to reach or exceed before the GPU operation. */ + uint64_t signalValue; /**< [in]: Fence value to set the fence to, after the GPU operation. */ + uint32_t bWait:1; /**< [in]: Wait on 'waitValue' if bWait is set to 1, before starting GPU operation. */ + uint32_t bSignal:1; /**< [in]: Signal on 'signalValue' if bSignal is set to 1, after GPU operation is complete. */ + uint32_t reservedBitField:30; /**< [in]: Reserved and must be set to 0. */ + uint32_t reserved1[7]; /**< [in]: Reserved and must be set to 0. */ +} NV_ENC_FENCE_POINT_D3D12; + +#define NV_ENC_FENCE_POINT_D3D12_VER NVENCAPI_STRUCT_VERSION(1) + +/** + * \struct _NV_ENC_INPUT_RESOURCE_D3D12 + * NV_ENC_PIC_PARAMS::inputBuffer and NV_ENC_PIC_PARAMS::alphaBuffer must be a pointer to a struct of this type, + * when D3D12 interface is used + */ +typedef struct _NV_ENC_INPUT_RESOURCE_D3D12 +{ + uint32_t version; /**< [in]: Struct version. Must be set to ::NV_ENC_INPUT_RESOURCE_D3D12_VER. */ + uint32_t reserved; /**< [in]: Reserved and must be set to 0. */ + NV_ENC_INPUT_PTR pInputBuffer; /**< [in]: Specifies the input surface pointer. Client must use a pointer obtained from NvEncMapInputResource() in NV_ENC_MAP_INPUT_RESOURCE::mappedResource + when mapping the input surface. */ + NV_ENC_FENCE_POINT_D3D12 inputFencePoint; /**< [in]: Specifies the fence and corresponding fence values to do GPU wait and signal. */ + uint32_t reserved1[16]; /**< [in]: Reserved and must be set to 0. */ + void* reserved2[16]; /**< [in]: Reserved and must be set to NULL. */ +} NV_ENC_INPUT_RESOURCE_D3D12; + +#define NV_ENC_INPUT_RESOURCE_D3D12_VER NVENCAPI_STRUCT_VERSION(1) + +/** + * \struct _NV_ENC_OUTPUT_RESOURCE_D3D12 + * NV_ENC_PIC_PARAMS::outputBitstream and NV_ENC_LOCK_BITSTREAM::outputBitstream must be a pointer to a struct of this type, + * when D3D12 interface is used + */ +typedef struct _NV_ENC_OUTPUT_RESOURCE_D3D12 +{ + uint32_t version; /**< [in]: Struct version. Must be set to ::NV_ENC_OUTPUT_RESOURCE_D3D12_VER. */ + uint32_t reserved; /**< [in]: Reserved and must be set to 0. */ + NV_ENC_INPUT_PTR pOutputBuffer; /**< [in]: Specifies the output buffer pointer. Client must use a pointer obtained from NvEncMapInputResource() in NV_ENC_MAP_INPUT_RESOURCE::mappedResource + when mapping output bitstream buffer */ + NV_ENC_FENCE_POINT_D3D12 outputFencePoint; /**< [in]: Specifies the fence and corresponding fence values to do GPU wait and signal.*/ + uint32_t reserved1[16]; /**< [in]: Reserved and must be set to 0. */ + void* reserved2[16]; /**< [in]: Reserved and must be set to NULL. */ +} NV_ENC_OUTPUT_RESOURCE_D3D12; + +#define NV_ENC_OUTPUT_RESOURCE_D3D12_VER NVENCAPI_STRUCT_VERSION(1) + +/** + * \struct _NV_ENC_REGISTER_RESOURCE + * Register a resource for future use with the Nvidia Video Encoder Interface. + */ +typedef struct _NV_ENC_REGISTER_RESOURCE +{ + uint32_t version; /**< [in]: Struct version. Must be set to ::NV_ENC_REGISTER_RESOURCE_VER. */ + NV_ENC_INPUT_RESOURCE_TYPE resourceType; /**< [in]: Specifies the type of resource to be registered. + Supported values are + ::NV_ENC_INPUT_RESOURCE_TYPE_DIRECTX, + ::NV_ENC_INPUT_RESOURCE_TYPE_CUDADEVICEPTR, + ::NV_ENC_INPUT_RESOURCE_TYPE_OPENGL_TEX */ + uint32_t width; /**< [in]: Input frame width. */ + uint32_t height; /**< [in]: Input frame height. */ + uint32_t pitch; /**< [in]: Input buffer pitch. + For ::NV_ENC_INPUT_RESOURCE_TYPE_DIRECTX resources, set this to 0. + For ::NV_ENC_INPUT_RESOURCE_TYPE_CUDADEVICEPTR resources, set this to + the pitch as obtained from cuMemAllocPitch(), or to the width in + bytes (if this resource was created by using cuMemAlloc()). This + value must be a multiple of 4. + For ::NV_ENC_INPUT_RESOURCE_TYPE_CUDAARRAY resources, set this to the + width of the allocation in bytes (i.e. + CUDA_ARRAY3D_DESCRIPTOR::Width * CUDA_ARRAY3D_DESCRIPTOR::NumChannels). + For ::NV_ENC_INPUT_RESOURCE_TYPE_OPENGL_TEX resources, set this to the + texture width multiplied by the number of components in the texture + format. */ + uint32_t subResourceIndex; /**< [in]: Subresource Index of the DirectX resource to be registered. Should be set to 0 for other interfaces. */ + void* resourceToRegister; /**< [in]: Handle to the resource that is being registered. */ + NV_ENC_REGISTERED_PTR registeredResource; /**< [out]: Registered resource handle. This should be used in future interactions with the Nvidia Video Encoder Interface. */ + NV_ENC_BUFFER_FORMAT bufferFormat; /**< [in]: Buffer format of resource to be registered. */ + NV_ENC_BUFFER_USAGE bufferUsage; /**< [in]: Usage of resource to be registered. */ + NV_ENC_FENCE_POINT_D3D12* pInputFencePoint; /**< [in]: Specifies the input fence and corresponding fence values to do GPU wait and signal. + To be used only when NV_ENC_REGISTER_RESOURCE::resourceToRegister represents D3D12 surface and + NV_ENC_BUFFER_USAGE::bufferUsage is NV_ENC_INPUT_IMAGE. + The fence NV_ENC_FENCE_POINT_D3D12::pFence and NV_ENC_FENCE_POINT_D3D12::waitValue will be used to do GPU wait + before starting GPU operation, if NV_ENC_FENCE_POINT_D3D12::bWait is set. + The fence NV_ENC_FENCE_POINT_D3D12::pFence and NV_ENC_FENCE_POINT_D3D12::signalValue will be used to do GPU signal + when GPU operation finishes, if NV_ENC_FENCE_POINT_D3D12::bSignal is set. */ + uint32_t reserved1[247]; /**< [in]: Reserved and must be set to 0. */ + void* reserved2[61]; /**< [in]: Reserved and must be set to NULL. */ +} NV_ENC_REGISTER_RESOURCE; + +/** Macro for constructing the version field of ::_NV_ENC_REGISTER_RESOURCE */ +#define NV_ENC_REGISTER_RESOURCE_VER NVENCAPI_STRUCT_VERSION(4) + +/** + * \struct _NV_ENC_STAT + * Encode Stats structure. + */ +typedef struct _NV_ENC_STAT +{ + uint32_t version; /**< [in]: Struct version. Must be set to ::NV_ENC_STAT_VER. */ + uint32_t reserved; /**< [in]: Reserved and must be set to 0 */ + NV_ENC_OUTPUT_PTR outputBitStream; /**< [out]: Specifies the pointer to output bitstream. */ + uint32_t bitStreamSize; /**< [out]: Size of generated bitstream in bytes. */ + uint32_t picType; /**< [out]: Picture type of encoded picture. See ::NV_ENC_PIC_TYPE. */ + uint32_t lastValidByteOffset; /**< [out]: Offset of last valid bytes of completed bitstream */ + uint32_t sliceOffsets[16]; /**< [out]: Offsets of each slice */ + uint32_t picIdx; /**< [out]: Picture number */ + uint32_t frameAvgQP; /**< [out]: Average QP of the frame. */ + uint32_t ltrFrame :1; /**< [out]: Flag indicating this frame is marked as LTR frame */ + uint32_t reservedBitFields :31; /**< [in]: Reserved bit fields and must be set to 0 */ + uint32_t ltrFrameIdx; /**< [out]: Frame index associated with this LTR frame. */ + uint32_t intraMBCount; /**< [out]: For H264, Number of Intra MBs in the encoded frame. For HEVC, Number of Intra CTBs in the encoded frame. */ + uint32_t interMBCount; /**< [out]: For H264, Number of Inter MBs in the encoded frame, includes skip MBs. For HEVC, Number of Inter CTBs in the encoded frame. */ + int32_t averageMVX; /**< [out]: Average Motion Vector in X direction for the encoded frame. */ + int32_t averageMVY; /**< [out]: Average Motion Vector in y direction for the encoded frame. */ + uint32_t reserved1[226]; /**< [in]: Reserved and must be set to 0 */ + void* reserved2[64]; /**< [in]: Reserved and must be set to NULL */ +} NV_ENC_STAT; + +/** Macro for constructing the version field of ::_NV_ENC_STAT */ +#define NV_ENC_STAT_VER NVENCAPI_STRUCT_VERSION(1) + + +/** + * \struct _NV_ENC_SEQUENCE_PARAM_PAYLOAD + * Sequence and picture paramaters payload. + */ +typedef struct _NV_ENC_SEQUENCE_PARAM_PAYLOAD +{ + uint32_t version; /**< [in]: Struct version. Must be set to ::NV_ENC_INITIALIZE_PARAMS_VER. */ + uint32_t inBufferSize; /**< [in]: Specifies the size of the spsppsBuffer provided by the client */ + uint32_t spsId; /**< [in]: Specifies the SPS id to be used in sequence header. Default value is 0. */ + uint32_t ppsId; /**< [in]: Specifies the PPS id to be used in picture header. Default value is 0. */ + void* spsppsBuffer; /**< [in]: Specifies bitstream header pointer of size NV_ENC_SEQUENCE_PARAM_PAYLOAD::inBufferSize. + It is the client's responsibility to manage this memory. */ + uint32_t* outSPSPPSPayloadSize; /**< [out]: Size of the sequence and picture header in bytes. */ + uint32_t reserved [250]; /**< [in]: Reserved and must be set to 0 */ + void* reserved2[64]; /**< [in]: Reserved and must be set to NULL */ +} NV_ENC_SEQUENCE_PARAM_PAYLOAD; + +/** Macro for constructing the version field of ::_NV_ENC_SEQUENCE_PARAM_PAYLOAD */ +#define NV_ENC_SEQUENCE_PARAM_PAYLOAD_VER NVENCAPI_STRUCT_VERSION(1) + + +/** + * Event registration/unregistration parameters. + */ +typedef struct _NV_ENC_EVENT_PARAMS +{ + uint32_t version; /**< [in]: Struct version. Must be set to ::NV_ENC_EVENT_PARAMS_VER. */ + uint32_t reserved; /**< [in]: Reserved and must be set to 0 */ + void* completionEvent; /**< [in]: Handle to event to be registered/unregistered with the NvEncodeAPI interface. */ + uint32_t reserved1[253]; /**< [in]: Reserved and must be set to 0 */ + void* reserved2[64]; /**< [in]: Reserved and must be set to NULL */ +} NV_ENC_EVENT_PARAMS; + +/** Macro for constructing the version field of ::_NV_ENC_EVENT_PARAMS */ +#define NV_ENC_EVENT_PARAMS_VER NVENCAPI_STRUCT_VERSION(1) + +/** + * Encoder Session Creation parameters + */ +typedef struct _NV_ENC_OPEN_ENCODE_SESSIONEX_PARAMS +{ + uint32_t version; /**< [in]: Struct version. Must be set to ::NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER. */ + NV_ENC_DEVICE_TYPE deviceType; /**< [in]: Specified the device Type */ + void* device; /**< [in]: Pointer to client device. */ + void* reserved; /**< [in]: Reserved and must be set to 0. */ + uint32_t apiVersion; /**< [in]: API version. Should be set to NVENCAPI_VERSION. */ + uint32_t reserved1[253]; /**< [in]: Reserved and must be set to 0 */ + void* reserved2[64]; /**< [in]: Reserved and must be set to NULL */ +} NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS; +/** Macro for constructing the version field of ::_NV_ENC_OPEN_ENCODE_SESSIONEX_PARAMS */ +#define NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER NVENCAPI_STRUCT_VERSION(1) + +/** @} */ /* END ENCODER_STRUCTURE */ + + +/** + * \addtogroup ENCODE_FUNC NvEncodeAPI Functions + * @{ + */ + +// NvEncOpenEncodeSession +/** + * \brief Opens an encoding session. + * + * Deprecated. + * + * \return + * ::NV_ENC_ERR_INVALID_CALL\n + * + */ +NVENCSTATUS NVENCAPI NvEncOpenEncodeSession (void* device, uint32_t deviceType, void** encoder); + +// NvEncGetEncodeGuidCount +/** + * \brief Retrieves the number of supported encode GUIDs. + * + * The function returns the number of codec GUIDs supported by the NvEncodeAPI + * interface. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [out] encodeGUIDCount + * Number of supported encode GUIDs. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncGetEncodeGUIDCount (void* encoder, uint32_t* encodeGUIDCount); + + +// NvEncGetEncodeGUIDs +/** + * \brief Retrieves an array of supported encoder codec GUIDs. + * + * The function returns an array of codec GUIDs supported by the NvEncodeAPI interface. + * The client must allocate an array where the NvEncodeAPI interface can + * fill the supported GUIDs and pass the pointer in \p *GUIDs parameter. + * The size of the array can be determined by using ::NvEncGetEncodeGUIDCount() API. + * The Nvidia Encoding interface returns the number of codec GUIDs it has actually + * filled in the GUID array in the \p GUIDCount parameter. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in] guidArraySize + * Number of GUIDs to retrieved. Should be set to the number retrieved using + * ::NvEncGetEncodeGUIDCount. + * \param [out] GUIDs + * Array of supported Encode GUIDs. + * \param [out] GUIDCount + * Number of supported Encode GUIDs. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncGetEncodeGUIDs (void* encoder, GUID* GUIDs, uint32_t guidArraySize, uint32_t* GUIDCount); + + +// NvEncGetEncodeProfileGuidCount +/** + * \brief Retrieves the number of supported profile GUIDs. + * + * The function returns the number of profile GUIDs supported for a given codec. + * The client must first enumerate the codec GUIDs supported by the NvEncodeAPI + * interface. After determining the codec GUID, it can query the NvEncodeAPI + * interface to determine the number of profile GUIDs supported for a particular + * codec GUID. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in] encodeGUID + * The codec GUID for which the profile GUIDs are being enumerated. + * \param [out] encodeProfileGUIDCount + * Number of encode profiles supported for the given encodeGUID. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncGetEncodeProfileGUIDCount (void* encoder, GUID encodeGUID, uint32_t* encodeProfileGUIDCount); + + +// NvEncGetEncodeProfileGUIDs +/** + * \brief Retrieves an array of supported encode profile GUIDs. + * + * The function returns an array of supported profile GUIDs for a particular + * codec GUID. The client must allocate an array where the NvEncodeAPI interface + * can populate the profile GUIDs. The client can determine the array size using + * ::NvEncGetEncodeProfileGUIDCount() API. The client must also validiate that the + * NvEncodeAPI interface supports the GUID the client wants to pass as \p encodeGUID + * parameter. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in] encodeGUID + * The encode GUID whose profile GUIDs are being enumerated. + * \param [in] guidArraySize + * Number of GUIDs to be retrieved. Should be set to the number retrieved using + * ::NvEncGetEncodeProfileGUIDCount. + * \param [out] profileGUIDs + * Array of supported Encode Profile GUIDs + * \param [out] GUIDCount + * Number of valid encode profile GUIDs in \p profileGUIDs array. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncGetEncodeProfileGUIDs (void* encoder, GUID encodeGUID, GUID* profileGUIDs, uint32_t guidArraySize, uint32_t* GUIDCount); + +// NvEncGetInputFormatCount +/** + * \brief Retrieve the number of supported Input formats. + * + * The function returns the number of supported input formats. The client must + * query the NvEncodeAPI interface to determine the supported input formats + * before creating the input surfaces. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in] encodeGUID + * Encode GUID, corresponding to which the number of supported input formats + * is to be retrieved. + * \param [out] inputFmtCount + * Number of input formats supported for specified Encode GUID. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_GENERIC \n + */ +NVENCSTATUS NVENCAPI NvEncGetInputFormatCount (void* encoder, GUID encodeGUID, uint32_t* inputFmtCount); + + +// NvEncGetInputFormats +/** + * \brief Retrieves an array of supported Input formats + * + * Returns an array of supported input formats The client must use the input + * format to create input surface using ::NvEncCreateInputBuffer() API. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in] encodeGUID + * Encode GUID, corresponding to which the number of supported input formats + * is to be retrieved. + *\param [in] inputFmtArraySize + * Size input format count array passed in \p inputFmts. + *\param [out] inputFmts + * Array of input formats supported for this Encode GUID. + *\param [out] inputFmtCount + * The number of valid input format types returned by the NvEncodeAPI + * interface in \p inputFmts array. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncGetInputFormats (void* encoder, GUID encodeGUID, NV_ENC_BUFFER_FORMAT* inputFmts, uint32_t inputFmtArraySize, uint32_t* inputFmtCount); + + +// NvEncGetEncodeCaps +/** + * \brief Retrieves the capability value for a specified encoder attribute. + * + * The function returns the capability value for a given encoder attribute. The + * client must validate the encodeGUID using ::NvEncGetEncodeGUIDs() API before + * calling this function. The encoder attribute being queried are enumerated in + * ::NV_ENC_CAPS_PARAM enum. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in] encodeGUID + * Encode GUID, corresponding to which the capability attribute is to be retrieved. + * \param [in] capsParam + * Used to specify attribute being queried. Refer ::NV_ENC_CAPS_PARAM for more + * details. + * \param [out] capsVal + * The value corresponding to the capability attribute being queried. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_GENERIC \n + */ +NVENCSTATUS NVENCAPI NvEncGetEncodeCaps (void* encoder, GUID encodeGUID, NV_ENC_CAPS_PARAM* capsParam, int* capsVal); + + +// NvEncGetEncodePresetCount +/** + * \brief Retrieves the number of supported preset GUIDs. + * + * The function returns the number of preset GUIDs available for a given codec. + * The client must validate the codec GUID using ::NvEncGetEncodeGUIDs() API + * before calling this function. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in] encodeGUID + * Encode GUID, corresponding to which the number of supported presets is to + * be retrieved. + * \param [out] encodePresetGUIDCount + * Receives the number of supported preset GUIDs. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncGetEncodePresetCount (void* encoder, GUID encodeGUID, uint32_t* encodePresetGUIDCount); + + +// NvEncGetEncodePresetGUIDs +/** + * \brief Receives an array of supported encoder preset GUIDs. + * + * The function returns an array of encode preset GUIDs available for a given codec. + * The client can directly use one of the preset GUIDs based upon the use case + * or target device. The preset GUID chosen can be directly used in + * NV_ENC_INITIALIZE_PARAMS::presetGUID parameter to ::NvEncEncodePicture() API. + * Alternately client can also use the preset GUID to retrieve the encoding config + * parameters being used by NvEncodeAPI interface for that given preset, using + * ::NvEncGetEncodePresetConfig() API. It can then modify preset config parameters + * as per its use case and send it to NvEncodeAPI interface as part of + * NV_ENC_INITIALIZE_PARAMS::encodeConfig parameter for NvEncInitializeEncoder() + * API. + * + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in] encodeGUID + * Encode GUID, corresponding to which the list of supported presets is to be + * retrieved. + * \param [in] guidArraySize + * Size of array of preset GUIDs passed in \p preset GUIDs + * \param [out] presetGUIDs + * Array of supported Encode preset GUIDs from the NvEncodeAPI interface + * to client. + * \param [out] encodePresetGUIDCount + * Receives the number of preset GUIDs returned by the NvEncodeAPI + * interface. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncGetEncodePresetGUIDs (void* encoder, GUID encodeGUID, GUID* presetGUIDs, uint32_t guidArraySize, uint32_t* encodePresetGUIDCount); + + +// NvEncGetEncodePresetConfig +/** + * \brief Returns a preset config structure supported for given preset GUID. + * + * The function returns a preset config structure for a given preset GUID. + * NvEncGetEncodePresetConfig() API is not applicable to AV1. + * Before using this function the client must enumerate the preset GUIDs available for + * a given codec. The preset config structure can be modified by the client depending + * upon its use case and can be then used to initialize the encoder using + * ::NvEncInitializeEncoder() API. The client can use this function only if it + * wants to modify the NvEncodeAPI preset configuration, otherwise it can + * directly use the preset GUID. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in] encodeGUID + * Encode GUID, corresponding to which the list of supported presets is to be + * retrieved. + * \param [in] presetGUID + * Preset GUID, corresponding to which the Encoding configurations is to be + * retrieved. + * \param [out] presetConfig + * The requested Preset Encoder Attribute set. Refer ::_NV_ENC_CONFIG for +* more details. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_INVALID_VERSION \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncGetEncodePresetConfig (void* encoder, GUID encodeGUID, GUID presetGUID, NV_ENC_PRESET_CONFIG* presetConfig); + +// NvEncGetEncodePresetConfigEx +/** + * \brief Returns a preset config structure supported for given preset GUID. + * + * The function returns a preset config structure for a given preset GUID and tuning info. + * NvEncGetEncodePresetConfigEx() API is not applicable to H264 and HEVC meonly mode. + * Before using this function the client must enumerate the preset GUIDs available for + * a given codec. The preset config structure can be modified by the client depending + * upon its use case and can be then used to initialize the encoder using + * ::NvEncInitializeEncoder() API. The client can use this function only if it + * wants to modify the NvEncodeAPI preset configuration, otherwise it can + * directly use the preset GUID. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in] encodeGUID + * Encode GUID, corresponding to which the list of supported presets is to be + * retrieved. + * \param [in] presetGUID + * Preset GUID, corresponding to which the Encoding configurations is to be + * retrieved. + * \param [in] tuningInfo + * tuning info, corresponding to which the Encoding configurations is to be + * retrieved. + * \param [out] presetConfig + * The requested Preset Encoder Attribute set. Refer ::_NV_ENC_CONFIG for + * more details. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_INVALID_VERSION \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncGetEncodePresetConfigEx (void* encoder, GUID encodeGUID, GUID presetGUID, NV_ENC_TUNING_INFO tuningInfo, NV_ENC_PRESET_CONFIG* presetConfig); + +// NvEncInitializeEncoder +/** + * \brief Initialize the encoder. + * + * This API must be used to initialize the encoder. The initialization parameter + * is passed using \p *createEncodeParams The client must send the following + * fields of the _NV_ENC_INITIALIZE_PARAMS structure with a valid value. + * - NV_ENC_INITIALIZE_PARAMS::encodeGUID + * - NV_ENC_INITIALIZE_PARAMS::encodeWidth + * - NV_ENC_INITIALIZE_PARAMS::encodeHeight + * + * The client can pass a preset GUID directly to the NvEncodeAPI interface using + * NV_ENC_INITIALIZE_PARAMS::presetGUID field. If the client doesn't pass + * NV_ENC_INITIALIZE_PARAMS::encodeConfig structure, the codec specific parameters + * will be selected based on the preset GUID. The preset GUID must have been + * validated by the client using ::NvEncGetEncodePresetGUIDs() API. + * If the client passes a custom ::_NV_ENC_CONFIG structure through + * NV_ENC_INITIALIZE_PARAMS::encodeConfig , it will override the codec specific parameters + * based on the preset GUID. It is recommended that even if the client passes a custom config, + * it should also send a preset GUID. In this case, the preset GUID passed by the client + * will not override any of the custom config parameters programmed by the client, + * it is only used as a hint by the NvEncodeAPI interface to determine certain encoder parameters + * which are not exposed to the client. + * + * There are two modes of operation for the encoder namely: + * - Asynchronous mode + * - Synchronous mode + * + * The client can select asynchronous or synchronous mode by setting the \p + * enableEncodeAsync field in ::_NV_ENC_INITIALIZE_PARAMS to 1 or 0 respectively. + *\par Asynchronous mode of operation: + * The Asynchronous mode can be enabled by setting NV_ENC_INITIALIZE_PARAMS::enableEncodeAsync to 1. + * The client operating in asynchronous mode must allocate completion event object + * for each output buffer and pass the completion event object in the + * ::NvEncEncodePicture() API. The client can create another thread and wait on + * the event object to be signaled by NvEncodeAPI interface on completion of the + * encoding process for the output frame. This should unblock the main thread from + * submitting work to the encoder. When the event is signaled the client can call + * NvEncodeAPI interfaces to copy the bitstream data using ::NvEncLockBitstream() + * API. This is the preferred mode of operation. + * + * NOTE: Asynchronous mode is not supported on Linux. + * + *\par Synchronous mode of operation: + * The client can select synchronous mode by setting NV_ENC_INITIALIZE_PARAMS::enableEncodeAsync to 0. + * The client working in synchronous mode can work in a single threaded or multi + * threaded mode. The client need not allocate any event objects. The client can + * only lock the bitstream data after NvEncodeAPI interface has returned + * ::NV_ENC_SUCCESS from encode picture. The NvEncodeAPI interface can return + * ::NV_ENC_ERR_NEED_MORE_INPUT error code from ::NvEncEncodePicture() API. The + * client must not lock the output buffer in such case but should send the next + * frame for encoding. The client must keep on calling ::NvEncEncodePicture() API + * until it returns ::NV_ENC_SUCCESS. \n + * The client must always lock the bitstream data in order in which it has submitted. + * This is true for both asynchronous and synchronous mode. + * + *\par Picture type decision: + * If the client is taking the picture type decision and it must disable the picture + * type decision module in NvEncodeAPI by setting NV_ENC_INITIALIZE_PARAMS::enablePTD + * to 0. In this case the client is required to send the picture in encoding + * order to NvEncodeAPI by doing the re-ordering for B frames. \n + * If the client doesn't want to take the picture type decision it can enable + * picture type decision module in the NvEncodeAPI interface by setting + * NV_ENC_INITIALIZE_PARAMS::enablePTD to 1 and send the input pictures in display + * order. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in] createEncodeParams + * Refer ::_NV_ENC_INITIALIZE_PARAMS for details. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_INVALID_VERSION \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncInitializeEncoder (void* encoder, NV_ENC_INITIALIZE_PARAMS* createEncodeParams); + + +// NvEncCreateInputBuffer +/** + * \brief Allocates Input buffer. + * + * This function is used to allocate an input buffer. The client must enumerate + * the input buffer format before allocating the input buffer resources. The + * NV_ENC_INPUT_PTR returned by the NvEncodeAPI interface in the + * NV_ENC_CREATE_INPUT_BUFFER::inputBuffer field can be directly used in + * ::NvEncEncodePicture() API. The number of input buffers to be allocated by the + * client must be at least 4 more than the number of B frames being used for encoding. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in,out] createInputBufferParams + * Pointer to the ::NV_ENC_CREATE_INPUT_BUFFER structure. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_INVALID_VERSION \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncCreateInputBuffer (void* encoder, NV_ENC_CREATE_INPUT_BUFFER* createInputBufferParams); + + +// NvEncDestroyInputBuffer +/** + * \brief Release an input buffers. + * + * This function is used to free an input buffer. If the client has allocated + * any input buffer using ::NvEncCreateInputBuffer() API, it must free those + * input buffers by calling this function. The client must release the input + * buffers before destroying the encoder using ::NvEncDestroyEncoder() API. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in] inputBuffer + * Pointer to the input buffer to be released. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_INVALID_VERSION \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncDestroyInputBuffer (void* encoder, NV_ENC_INPUT_PTR inputBuffer); + +// NvEncSetIOCudaStreams +/** + * \brief Set input and output CUDA stream for specified encoder attribute. + * + * Encoding may involve CUDA pre-processing on the input and post-processing on encoded output. + * This function is used to set input and output CUDA streams to pipeline the CUDA pre-processing + * and post-processing tasks. Clients should call this function before the call to + * NvEncUnlockInputBuffer(). If this function is not called, the default CUDA stream is used for + * input and output processing. After a successful call to this function, the streams specified + * in that call will replace the previously-used streams. + * This API is supported for NVCUVID interface only. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in] inputStream + * Pointer to CUstream which is used to process ::NV_ENC_PIC_PARAMS::inputFrame for encode. + * In case of ME-only mode, inputStream is used to process ::NV_ENC_MEONLY_PARAMS::inputBuffer and + * ::NV_ENC_MEONLY_PARAMS::referenceFrame + * \param [in] outputStream + * Pointer to CUstream which is used to process ::NV_ENC_PIC_PARAMS::outputBuffer for encode. + * In case of ME-only mode, outputStream is used to process ::NV_ENC_MEONLY_PARAMS::mvBuffer + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_INVALID_VERSION \n + * ::NV_ENC_ERR_GENERIC \n + */ +NVENCSTATUS NVENCAPI NvEncSetIOCudaStreams (void* encoder, NV_ENC_CUSTREAM_PTR inputStream, NV_ENC_CUSTREAM_PTR outputStream); + + +// NvEncCreateBitstreamBuffer +/** + * \brief Allocates an output bitstream buffer + * + * This function is used to allocate an output bitstream buffer and returns a + * NV_ENC_OUTPUT_PTR to bitstream buffer to the client in the + * NV_ENC_CREATE_BITSTREAM_BUFFER::bitstreamBuffer field. + * The client can only call this function after the encoder session has been + * initialized using ::NvEncInitializeEncoder() API. The minimum number of output + * buffers allocated by the client must be at least 4 more than the number of B + * B frames being used for encoding. The client can only access the output + * bitstream data by locking the \p bitstreamBuffer using the ::NvEncLockBitstream() + * function. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in,out] createBitstreamBufferParams + * Pointer ::NV_ENC_CREATE_BITSTREAM_BUFFER for details. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_INVALID_VERSION \n + * ::NV_ENC_ERR_ENCODER_NOT_INITIALIZED \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncCreateBitstreamBuffer (void* encoder, NV_ENC_CREATE_BITSTREAM_BUFFER* createBitstreamBufferParams); + + +// NvEncDestroyBitstreamBuffer +/** + * \brief Release a bitstream buffer. + * + * This function is used to release the output bitstream buffer allocated using + * the ::NvEncCreateBitstreamBuffer() function. The client must release the output + * bitstreamBuffer using this function before destroying the encoder session. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in] bitstreamBuffer + * Pointer to the bitstream buffer being released. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_INVALID_VERSION \n + * ::NV_ENC_ERR_ENCODER_NOT_INITIALIZED \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncDestroyBitstreamBuffer (void* encoder, NV_ENC_OUTPUT_PTR bitstreamBuffer); + +// NvEncEncodePicture +/** + * \brief Submit an input picture for encoding. + * + * This function is used to submit an input picture buffer for encoding. The + * encoding parameters are passed using \p *encodePicParams which is a pointer + * to the ::_NV_ENC_PIC_PARAMS structure. + * + * If the client has set NV_ENC_INITIALIZE_PARAMS::enablePTD to 0, then it must + * send a valid value for the following fields. + * - NV_ENC_PIC_PARAMS::pictureType + * - NV_ENC_PIC_PARAMS_H264::displayPOCSyntax (H264 only) + * - NV_ENC_PIC_PARAMS_H264::frameNumSyntax(H264 only) + * - NV_ENC_PIC_PARAMS_H264::refPicFlag(H264 only) + * + *\par MVC Encoding: + * For MVC encoding the client must call encode picture API for each view separately + * and must pass valid view id in NV_ENC_PIC_PARAMS_MVC::viewID field. Currently + * NvEncodeAPI only support stereo MVC so client must send viewID as 0 for base + * view and view ID as 1 for dependent view. + * + *\par Asynchronous Encoding + * If the client has enabled asynchronous mode of encoding by setting + * NV_ENC_INITIALIZE_PARAMS::enableEncodeAsync to 1 in the ::NvEncInitializeEncoder() + * API ,then the client must send a valid NV_ENC_PIC_PARAMS::completionEvent. + * Incase of asynchronous mode of operation, client can queue the ::NvEncEncodePicture() + * API commands from the main thread and then queue output buffers to be processed + * to a secondary worker thread. Before the locking the output buffers in the + * secondary thread , the client must wait on NV_ENC_PIC_PARAMS::completionEvent + * it has queued in ::NvEncEncodePicture() API call. The client must always process + * completion event and the output buffer in the same order in which they have been + * submitted for encoding. The NvEncodeAPI interface is responsible for any + * re-ordering required for B frames and will always ensure that encoded bitstream + * data is written in the same order in which output buffer is submitted. + * The NvEncodeAPI interface may return ::NV_ENC_ERR_NEED_MORE_INPUT error code for + * some ::NvEncEncodePicture() API calls but the client must not treat it as a fatal error. + * The NvEncodeAPI interface might not be able to submit an input picture buffer for encoding + * immediately due to re-ordering for B frames. + *\code + The below example shows how asynchronous encoding in case of 1 B frames + ------------------------------------------------------------------------ + Suppose the client allocated 4 input buffers(I1,I2..), 4 output buffers(O1,O2..) + and 4 completion events(E1, E2, ...). The NvEncodeAPI interface will need to + keep a copy of the input buffers for re-ordering and it allocates following + internal buffers (NvI1, NvI2...). These internal buffers are managed by NvEncodeAPI + and the client is not responsible for the allocating or freeing the memory of + the internal buffers. + + a) The client main thread will queue the following encode frame calls. + Note the picture type is unknown to the client, the decision is being taken by + NvEncodeAPI interface. The client should pass ::_NV_ENC_PIC_PARAMS parameter + consisting of allocated input buffer, output buffer and output events in successive + ::NvEncEncodePicture() API calls along with other required encode picture params. + For example: + 1st EncodePicture parameters - (I1, O1, E1) + 2nd EncodePicture parameters - (I2, O2, E2) + 3rd EncodePicture parameters - (I3, O3, E3) + + b) NvEncodeAPI SW will receive the following encode Commands from the client. + The left side shows input from client in the form (Input buffer, Output Buffer, + Output Event). The right hand side shows a possible picture type decision take by + the NvEncodeAPI interface. + (I1, O1, E1) ---P1 Frame + (I2, O2, E2) ---B2 Frame + (I3, O3, E3) ---P3 Frame + + c) NvEncodeAPI interface will make a copy of the input buffers to its internal + buffers for re-ordering. These copies are done as part of nvEncEncodePicture + function call from the client and NvEncodeAPI interface is responsible for + synchronization of copy operation with the actual encoding operation. + I1 --> NvI1 + I2 --> NvI2 + I3 --> NvI3 + + d) The NvEncodeAPI encodes I1 as P frame and submits I1 to encoder HW and returns ::NV_ENC_SUCCESS. + The NvEncodeAPI tries to encode I2 as B frame and fails with ::NV_ENC_ERR_NEED_MORE_INPUT error code. + The error is not fatal and it notifies client that I2 is not submitted to encoder immediately. + The NvEncodeAPI encodes I3 as P frame and submits I3 for encoding which will be used as backward + reference frame for I2. The NvEncodeAPI then submits I2 for encoding and returns ::NV_ENC_SUCESS. + Both the submission are part of the same ::NvEncEncodePicture() function call. + + e) After returning from ::NvEncEncodePicture() call , the client must queue the output + bitstream processing work to the secondary thread. The output bitstream processing + for asynchronous mode consist of first waiting on completion event(E1, E2..) + and then locking the output bitstream buffer(O1, O2..) for reading the encoded + data. The work queued to the secondary thread by the client is in the following order + (I1, O1, E1) + (I2, O2, E2) + (I3, O3, E3) + Note they are in the same order in which client calls ::NvEncEncodePicture() API + in \p step a). + + f) NvEncodeAPI interface will do the re-ordering such that Encoder HW will receive + the following encode commands: + (NvI1, O1, E1) ---P1 Frame + (NvI3, O2, E2) ---P3 Frame + (NvI2, O3, E3) ---B2 frame + + g) After the encoding operations are completed, the events will be signaled + by NvEncodeAPI interface in the following order : + (O1, E1) ---P1 Frame ,output bitstream copied to O1 and event E1 signaled. + (O2, E2) ---P3 Frame ,output bitstream copied to O2 and event E2 signaled. + (O3, E3) ---B2 Frame ,output bitstream copied to O3 and event E3 signaled. + + h) The client must lock the bitstream data using ::NvEncLockBitstream() API in + the order O1,O2,O3 to read the encoded data, after waiting for the events + to be signaled in the same order i.e E1, E2 and E3.The output processing is + done in the secondary thread in the following order: + Waits on E1, copies encoded bitstream from O1 + Waits on E2, copies encoded bitstream from O2 + Waits on E3, copies encoded bitstream from O3 + + -Note the client will receive the events signaling and output buffer in the + same order in which they have submitted for encoding. + -Note the LockBitstream will have picture type field which will notify the + output picture type to the clients. + -Note the input, output buffer and the output completion event are free to be + reused once NvEncodeAPI interfaced has signaled the event and the client has + copied the data from the output buffer. + + * \endcode + * + *\par Synchronous Encoding + * The client can enable synchronous mode of encoding by setting + * NV_ENC_INITIALIZE_PARAMS::enableEncodeAsync to 0 in ::NvEncInitializeEncoder() API. + * The NvEncodeAPI interface may return ::NV_ENC_ERR_NEED_MORE_INPUT error code for + * some ::NvEncEncodePicture() API calls when NV_ENC_INITIALIZE_PARAMS::enablePTD + * is set to 1, but the client must not treat it as a fatal error. The NvEncodeAPI + * interface might not be able to submit an input picture buffer for encoding + * immediately due to re-ordering for B frames. The NvEncodeAPI interface cannot + * submit the input picture which is decided to be encoded as B frame as it waits + * for backward reference from temporally subsequent frames. This input picture + * is buffered internally and waits for more input picture to arrive. The client + * must not call ::NvEncLockBitstream() API on the output buffers whose + * ::NvEncEncodePicture() API returns ::NV_ENC_ERR_NEED_MORE_INPUT. The client must + * wait for the NvEncodeAPI interface to return ::NV_ENC_SUCCESS before locking the + * output bitstreams to read the encoded bitstream data. The following example + * explains the scenario with synchronous encoding with 2 B frames. + *\code + The below example shows how synchronous encoding works in case of 1 B frames + ----------------------------------------------------------------------------- + Suppose the client allocated 4 input buffers(I1,I2..), 4 output buffers(O1,O2..) + and 4 completion events(E1, E2, ...). The NvEncodeAPI interface will need to + keep a copy of the input buffers for re-ordering and it allocates following + internal buffers (NvI1, NvI2...). These internal buffers are managed by NvEncodeAPI + and the client is not responsible for the allocating or freeing the memory of + the internal buffers. + + The client calls ::NvEncEncodePicture() API with input buffer I1 and output buffer O1. + The NvEncodeAPI decides to encode I1 as P frame and submits it to encoder + HW and returns ::NV_ENC_SUCCESS. + The client can now read the encoded data by locking the output O1 by calling + NvEncLockBitstream API. + + The client calls ::NvEncEncodePicture() API with input buffer I2 and output buffer O2. + The NvEncodeAPI decides to encode I2 as B frame and buffers I2 by copying it + to internal buffer and returns ::NV_ENC_ERR_NEED_MORE_INPUT. + The error is not fatal and it notifies client that it cannot read the encoded + data by locking the output O2 by calling ::NvEncLockBitstream() API without submitting + more work to the NvEncodeAPI interface. + + The client calls ::NvEncEncodePicture() with input buffer I3 and output buffer O3. + The NvEncodeAPI decides to encode I3 as P frame and it first submits I3 for + encoding which will be used as backward reference frame for I2. + The NvEncodeAPI then submits I2 for encoding and returns ::NV_ENC_SUCESS. Both + the submission are part of the same ::NvEncEncodePicture() function call. + The client can now read the encoded data for both the frames by locking the output + O2 followed by O3 ,by calling ::NvEncLockBitstream() API. + + The client must always lock the output in the same order in which it has submitted + to receive the encoded bitstream in correct encoding order. + + * \endcode + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in,out] encodePicParams + * Pointer to the ::_NV_ENC_PIC_PARAMS structure. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_INVALID_VERSION \n + * ::NV_ENC_ERR_ENCODER_BUSY \n + * ::NV_ENC_ERR_NEED_MORE_INPUT \n + * ::NV_ENC_ERR_ENCODER_NOT_INITIALIZED \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncEncodePicture (void* encoder, NV_ENC_PIC_PARAMS* encodePicParams); + + +// NvEncLockBitstream +/** + * \brief Lock output bitstream buffer + * + * This function is used to lock the bitstream buffer to read the encoded data. + * The client can only access the encoded data by calling this function. + * The pointer to client accessible encoded data is returned in the + * NV_ENC_LOCK_BITSTREAM::bitstreamBufferPtr field. The size of the encoded data + * in the output buffer is returned in the NV_ENC_LOCK_BITSTREAM::bitstreamSizeInBytes + * The NvEncodeAPI interface also returns the output picture type and picture structure + * of the encoded frame in NV_ENC_LOCK_BITSTREAM::pictureType and + * NV_ENC_LOCK_BITSTREAM::pictureStruct fields respectively. If the client has + * set NV_ENC_LOCK_BITSTREAM::doNotWait to 1, the function might return + * ::NV_ENC_ERR_LOCK_BUSY if client is operating in synchronous mode. This is not + * a fatal failure if NV_ENC_LOCK_BITSTREAM::doNotWait is set to 1. In the above case the client can + * retry the function after few milliseconds. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in,out] lockBitstreamBufferParams + * Pointer to the ::_NV_ENC_LOCK_BITSTREAM structure. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_INVALID_VERSION \n + * ::NV_ENC_ERR_LOCK_BUSY \n + * ::NV_ENC_ERR_ENCODER_NOT_INITIALIZED \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncLockBitstream (void* encoder, NV_ENC_LOCK_BITSTREAM* lockBitstreamBufferParams); + + +// NvEncUnlockBitstream +/** + * \brief Unlock the output bitstream buffer + * + * This function is used to unlock the output bitstream buffer after the client + * has read the encoded data from output buffer. The client must call this function + * to unlock the output buffer which it has previously locked using ::NvEncLockBitstream() + * function. Using a locked bitstream buffer in ::NvEncEncodePicture() API will cause + * the function to fail. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in,out] bitstreamBuffer + * bitstream buffer pointer being unlocked + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_ENCODER_NOT_INITIALIZED \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncUnlockBitstream (void* encoder, NV_ENC_OUTPUT_PTR bitstreamBuffer); + + +// NvLockInputBuffer +/** + * \brief Locks an input buffer + * + * This function is used to lock the input buffer to load the uncompressed YUV + * pixel data into input buffer memory. The client must pass the NV_ENC_INPUT_PTR + * it had previously allocated using ::NvEncCreateInputBuffer()in the + * NV_ENC_LOCK_INPUT_BUFFER::inputBuffer field. + * The NvEncodeAPI interface returns pointer to client accessible input buffer + * memory in NV_ENC_LOCK_INPUT_BUFFER::bufferDataPtr field. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in,out] lockInputBufferParams + * Pointer to the ::_NV_ENC_LOCK_INPUT_BUFFER structure + * + * \return + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_INVALID_VERSION \n + * ::NV_ENC_ERR_LOCK_BUSY \n + * ::NV_ENC_ERR_ENCODER_NOT_INITIALIZED \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncLockInputBuffer (void* encoder, NV_ENC_LOCK_INPUT_BUFFER* lockInputBufferParams); + + +// NvUnlockInputBuffer +/** + * \brief Unlocks the input buffer + * + * This function is used to unlock the input buffer memory previously locked for + * uploading YUV pixel data. The input buffer must be unlocked before being used + * again for encoding, otherwise NvEncodeAPI will fail the ::NvEncEncodePicture() + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in] inputBuffer + * Pointer to the input buffer that is being unlocked. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_VERSION \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_ENCODER_NOT_INITIALIZED \n + * ::NV_ENC_ERR_GENERIC \n + * + * + */ +NVENCSTATUS NVENCAPI NvEncUnlockInputBuffer (void* encoder, NV_ENC_INPUT_PTR inputBuffer); + + +// NvEncGetEncodeStats +/** + * \brief Get encoding statistics. + * + * This function is used to retrieve the encoding statistics. + * This API is not supported when encode device type is CUDA. + * Note that this API will be removed in future Video Codec SDK release. + * Clients should use NvEncLockBitstream() API to retrieve the encoding statistics. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in,out] encodeStats + * Pointer to the ::_NV_ENC_STAT structure. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_ENCODER_NOT_INITIALIZED \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncGetEncodeStats (void* encoder, NV_ENC_STAT* encodeStats); + + +// NvEncGetSequenceParams +/** + * \brief Get encoded sequence and picture header. + * + * This function can be used to retrieve the sequence and picture header out of + * band. The client must call this function only after the encoder has been + * initialized using ::NvEncInitializeEncoder() function. The client must + * allocate the memory where the NvEncodeAPI interface can copy the bitstream + * header and pass the pointer to the memory in NV_ENC_SEQUENCE_PARAM_PAYLOAD::spsppsBuffer. + * The size of buffer is passed in the field NV_ENC_SEQUENCE_PARAM_PAYLOAD::inBufferSize. + * The NvEncodeAPI interface will copy the bitstream header payload and returns + * the actual size of the bitstream header in the field + * NV_ENC_SEQUENCE_PARAM_PAYLOAD::outSPSPPSPayloadSize. + * The client must call ::NvEncGetSequenceParams() function from the same thread which is + * being used to call ::NvEncEncodePicture() function. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in,out] sequenceParamPayload + * Pointer to the ::_NV_ENC_SEQUENCE_PARAM_PAYLOAD structure. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_VERSION \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_ENCODER_NOT_INITIALIZED \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncGetSequenceParams (void* encoder, NV_ENC_SEQUENCE_PARAM_PAYLOAD* sequenceParamPayload); + +// NvEncGetSequenceParamEx +/** + * \brief Get sequence and picture header. + * + * This function can be used to retrieve the sequence and picture header out of band, even when + * encoder has not been initialized using ::NvEncInitializeEncoder() function. + * The client must allocate the memory where the NvEncodeAPI interface can copy the bitstream + * header and pass the pointer to the memory in NV_ENC_SEQUENCE_PARAM_PAYLOAD::spsppsBuffer. + * The size of buffer is passed in the field NV_ENC_SEQUENCE_PARAM_PAYLOAD::inBufferSize. + * If encoder has not been initialized using ::NvEncInitializeEncoder() function, client must + * send NV_ENC_INITIALIZE_PARAMS as input. The NV_ENC_INITIALIZE_PARAMS passed must be same as the + * one which will be used for initializing encoder using ::NvEncInitializeEncoder() function later. + * If encoder is already initialized using ::NvEncInitializeEncoder() function, the provided + * NV_ENC_INITIALIZE_PARAMS structure is ignored. The NvEncodeAPI interface will copy the bitstream + * header payload and returns the actual size of the bitstream header in the field + * NV_ENC_SEQUENCE_PARAM_PAYLOAD::outSPSPPSPayloadSize. The client must call ::NvEncGetSequenceParamsEx() + * function from the same thread which is being used to call ::NvEncEncodePicture() function. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in] encInitParams + * Pointer to the _NV_ENC_INITIALIZE_PARAMS structure. + * \param [in,out] sequenceParamPayload + * Pointer to the ::_NV_ENC_SEQUENCE_PARAM_PAYLOAD structure. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_VERSION \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncGetSequenceParamEx (void* encoder, NV_ENC_INITIALIZE_PARAMS* encInitParams, NV_ENC_SEQUENCE_PARAM_PAYLOAD* sequenceParamPayload); + +// NvEncRegisterAsyncEvent +/** + * \brief Register event for notification to encoding completion. + * + * This function is used to register the completion event with NvEncodeAPI + * interface. The event is required when the client has configured the encoder to + * work in asynchronous mode. In this mode the client needs to send a completion + * event with every output buffer. The NvEncodeAPI interface will signal the + * completion of the encoding process using this event. Only after the event is + * signaled the client can get the encoded data using ::NvEncLockBitstream() function. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in] eventParams + * Pointer to the ::_NV_ENC_EVENT_PARAMS structure. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_VERSION \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_ENCODER_NOT_INITIALIZED \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncRegisterAsyncEvent (void* encoder, NV_ENC_EVENT_PARAMS* eventParams); + + +// NvEncUnregisterAsyncEvent +/** + * \brief Unregister completion event. + * + * This function is used to unregister completion event which has been previously + * registered using ::NvEncRegisterAsyncEvent() function. The client must unregister + * all events before destroying the encoder using ::NvEncDestroyEncoder() function. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in] eventParams + * Pointer to the ::_NV_ENC_EVENT_PARAMS structure. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_VERSION \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_ENCODER_NOT_INITIALIZED \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncUnregisterAsyncEvent (void* encoder, NV_ENC_EVENT_PARAMS* eventParams); + + +// NvEncMapInputResource +/** + * \brief Map an externally created input resource pointer for encoding. + * + * Maps an externally allocated input resource [using and returns a NV_ENC_INPUT_PTR + * which can be used for encoding in the ::NvEncEncodePicture() function. The + * mapped resource is returned in the field NV_ENC_MAP_INPUT_RESOURCE::outputResourcePtr. + * The NvEncodeAPI interface also returns the buffer format of the mapped resource + * in the field NV_ENC_MAP_INPUT_RESOURCE::outbufferFmt. + * This function provides synchronization guarantee that any graphics work submitted + * on the input buffer is completed before the buffer is used for encoding. This is + * also true for compute (i.e. CUDA) work, provided that the previous workload using + * the input resource was submitted to the default stream. + * The client should not access any input buffer while they are mapped by the encoder. + * For D3D12 interface type, this function does not provide synchronization guarantee. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in,out] mapInputResParams + * Pointer to the ::_NV_ENC_MAP_INPUT_RESOURCE structure. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_VERSION \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_ENCODER_NOT_INITIALIZED \n + * ::NV_ENC_ERR_RESOURCE_NOT_REGISTERED \n + * ::NV_ENC_ERR_MAP_FAILED \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncMapInputResource (void* encoder, NV_ENC_MAP_INPUT_RESOURCE* mapInputResParams); + + +// NvEncUnmapInputResource +/** + * \brief UnMaps a NV_ENC_INPUT_PTR which was mapped for encoding + * + * + * UnMaps an input buffer which was previously mapped using ::NvEncMapInputResource() + * API. The mapping created using ::NvEncMapInputResource() should be invalidated + * using this API before the external resource is destroyed by the client. The client + * must unmap the buffer after ::NvEncLockBitstream() API returns successfully for encode + * work submitted using the mapped input buffer. + * + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in] mappedInputBuffer + * Pointer to the NV_ENC_INPUT_PTR + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_VERSION \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_ENCODER_NOT_INITIALIZED \n + * ::NV_ENC_ERR_RESOURCE_NOT_REGISTERED \n + * ::NV_ENC_ERR_RESOURCE_NOT_MAPPED \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncUnmapInputResource (void* encoder, NV_ENC_INPUT_PTR mappedInputBuffer); + +// NvEncDestroyEncoder +/** + * \brief Destroy Encoding Session + * + * Destroys the encoder session previously created using ::NvEncOpenEncodeSession() + * function. The client must flush the encoder before freeing any resources. In order + * to flush the encoder the client must pass a NULL encode picture packet and either + * wait for the ::NvEncEncodePicture() function to return in synchronous mode or wait + * for the flush event to be signaled by the encoder in asynchronous mode. + * The client must free all the input and output resources created using the + * NvEncodeAPI interface before destroying the encoder. If the client is operating + * in asynchronous mode, it must also unregister the completion events previously + * registered. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncDestroyEncoder (void* encoder); + +// NvEncInvalidateRefFrames +/** + * \brief Invalidate reference frames + * + * Invalidates reference frame based on the time stamp provided by the client. + * The encoder marks any reference frames or any frames which have been reconstructed + * using the corrupt frame as invalid for motion estimation and uses older reference + * frames for motion estimation. The encoder forces the current frame to be encoded + * as an intra frame if no reference frames are left after invalidation process. + * This is useful for low latency application for error resiliency. The client + * is recommended to set NV_ENC_CONFIG_H264::maxNumRefFrames to a large value so + * that encoder can keep a backup of older reference frames in the DPB and can use them + * for motion estimation when the newer reference frames have been invalidated. + * This API can be called multiple times. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in] invalidRefFrameTimeStamp + * Timestamp of the invalid reference frames which needs to be invalidated. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncInvalidateRefFrames(void* encoder, uint64_t invalidRefFrameTimeStamp); + +// NvEncOpenEncodeSessionEx +/** + * \brief Opens an encoding session. + * + * Opens an encoding session and returns a pointer to the encoder interface in + * the \p **encoder parameter. The client should start encoding process by calling + * this API first. + * The client must pass a pointer to IDirect3DDevice9 device or CUDA context in the \p *device parameter. + * For the OpenGL interface, \p device must be NULL. An OpenGL context must be current when + * calling all NvEncodeAPI functions. + * If the creation of encoder session fails, the client must call ::NvEncDestroyEncoder API + * before exiting. + * + * \param [in] openSessionExParams + * Pointer to a ::NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS structure. + * \param [out] encoder + * Encode Session pointer to the NvEncodeAPI interface. + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_NO_ENCODE_DEVICE \n + * ::NV_ENC_ERR_UNSUPPORTED_DEVICE \n + * ::NV_ENC_ERR_INVALID_DEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncOpenEncodeSessionEx (NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS *openSessionExParams, void** encoder); + +// NvEncRegisterResource +/** + * \brief Registers a resource with the Nvidia Video Encoder Interface. + * + * Registers a resource with the Nvidia Video Encoder Interface for book keeping. + * The client is expected to pass the registered resource handle as well, while calling ::NvEncMapInputResource API. + * + * \param [in] encoder + * Pointer to the NVEncodeAPI interface. + * + * \param [in] registerResParams + * Pointer to a ::_NV_ENC_REGISTER_RESOURCE structure + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_VERSION \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_ENCODER_NOT_INITIALIZED \n + * ::NV_ENC_ERR_RESOURCE_REGISTER_FAILED \n + * ::NV_ENC_ERR_GENERIC \n + * ::NV_ENC_ERR_UNIMPLEMENTED \n + * + */ +NVENCSTATUS NVENCAPI NvEncRegisterResource (void* encoder, NV_ENC_REGISTER_RESOURCE* registerResParams); + +// NvEncUnregisterResource +/** + * \brief Unregisters a resource previously registered with the Nvidia Video Encoder Interface. + * + * Unregisters a resource previously registered with the Nvidia Video Encoder Interface. + * The client is expected to unregister any resource that it has registered with the + * Nvidia Video Encoder Interface before destroying the resource. + * + * \param [in] encoder + * Pointer to the NVEncodeAPI interface. + * + * \param [in] registeredResource + * The registered resource pointer that was returned in ::NvEncRegisterResource. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_VERSION \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_ENCODER_NOT_INITIALIZED \n + * ::NV_ENC_ERR_RESOURCE_NOT_REGISTERED \n + * ::NV_ENC_ERR_GENERIC \n + * ::NV_ENC_ERR_UNIMPLEMENTED \n + * + */ +NVENCSTATUS NVENCAPI NvEncUnregisterResource (void* encoder, NV_ENC_REGISTERED_PTR registeredResource); + +// NvEncReconfigureEncoder +/** + * \brief Reconfigure an existing encoding session. + * + * Reconfigure an existing encoding session. + * The client should call this API to change/reconfigure the parameter passed during + * NvEncInitializeEncoder API call. + * Currently Reconfiguration of following are not supported. + * Change in GOP structure. + * Change in sync-Async mode. + * Change in MaxWidth & MaxHeight. + * Change in PTD mode. + * + * Resolution change is possible only if maxEncodeWidth & maxEncodeHeight of NV_ENC_INITIALIZE_PARAMS + * is set while creating encoder session. + * + * \param [in] encoder + * Pointer to the NVEncodeAPI interface. + * + * \param [in] reInitEncodeParams + * Pointer to a ::NV_ENC_RECONFIGURE_PARAMS structure. + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_NO_ENCODE_DEVICE \n + * ::NV_ENC_ERR_UNSUPPORTED_DEVICE \n + * ::NV_ENC_ERR_INVALID_DEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_GENERIC \n + * + */ +NVENCSTATUS NVENCAPI NvEncReconfigureEncoder (void *encoder, NV_ENC_RECONFIGURE_PARAMS* reInitEncodeParams); + + + +// NvEncCreateMVBuffer +/** + * \brief Allocates output MV buffer for ME only mode. + * + * This function is used to allocate an output MV buffer. The size of the mvBuffer is + * dependent on the frame height and width of the last ::NvEncCreateInputBuffer() call. + * The NV_ENC_OUTPUT_PTR returned by the NvEncodeAPI interface in the + * ::NV_ENC_CREATE_MV_BUFFER::mvBuffer field should be used in + * ::NvEncRunMotionEstimationOnly() API. + * Client must lock ::NV_ENC_CREATE_MV_BUFFER::mvBuffer using ::NvEncLockBitstream() API to get the motion vector data. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in,out] createMVBufferParams + * Pointer to the ::NV_ENC_CREATE_MV_BUFFER structure. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_INVALID_VERSION \n + * ::NV_ENC_ERR_GENERIC \n + */ +NVENCSTATUS NVENCAPI NvEncCreateMVBuffer (void* encoder, NV_ENC_CREATE_MV_BUFFER* createMVBufferParams); + + +// NvEncDestroyMVBuffer +/** + * \brief Release an output MV buffer for ME only mode. + * + * This function is used to release the output MV buffer allocated using + * the ::NvEncCreateMVBuffer() function. The client must release the output + * mvBuffer using this function before destroying the encoder session. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in] mvBuffer + * Pointer to the mvBuffer being released. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_INVALID_VERSION \n + * ::NV_ENC_ERR_ENCODER_NOT_INITIALIZED \n + * ::NV_ENC_ERR_GENERIC \n + */ +NVENCSTATUS NVENCAPI NvEncDestroyMVBuffer (void* encoder, NV_ENC_OUTPUT_PTR mvBuffer); + + +// NvEncRunMotionEstimationOnly +/** + * \brief Submit an input picture and reference frame for motion estimation in ME only mode. + * + * This function is used to submit the input frame and reference frame for motion + * estimation. The ME parameters are passed using *meOnlyParams which is a pointer + * to ::_NV_ENC_MEONLY_PARAMS structure. + * Client must lock ::NV_ENC_CREATE_MV_BUFFER::mvBuffer using ::NvEncLockBitstream() API to get the motion vector data. + * to get motion vector data. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * \param [in] meOnlyParams + * Pointer to the ::_NV_ENC_MEONLY_PARAMS structure. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + * ::NV_ENC_ERR_INVALID_ENCODERDEVICE \n + * ::NV_ENC_ERR_DEVICE_NOT_EXIST \n + * ::NV_ENC_ERR_UNSUPPORTED_PARAM \n + * ::NV_ENC_ERR_OUT_OF_MEMORY \n + * ::NV_ENC_ERR_INVALID_PARAM \n + * ::NV_ENC_ERR_INVALID_VERSION \n + * ::NV_ENC_ERR_NEED_MORE_INPUT \n + * ::NV_ENC_ERR_ENCODER_NOT_INITIALIZED \n + * ::NV_ENC_ERR_GENERIC \n + */ +NVENCSTATUS NVENCAPI NvEncRunMotionEstimationOnly (void* encoder, NV_ENC_MEONLY_PARAMS* meOnlyParams); + +// NvEncodeAPIGetMaxSupportedVersion +/** + * \brief Get the largest NvEncodeAPI version supported by the driver. + * + * This function can be used by clients to determine if the driver supports + * the NvEncodeAPI header the application was compiled with. + * + * \param [out] version + * Pointer to the requested value. The 4 least significant bits in the returned + * indicate the minor version and the rest of the bits indicate the major + * version of the largest supported version. + * + * \return + * ::NV_ENC_SUCCESS \n + * ::NV_ENC_ERR_INVALID_PTR \n + */ +NVENCSTATUS NVENCAPI NvEncodeAPIGetMaxSupportedVersion (uint32_t* version); + + +// NvEncGetLastErrorString +/** + * \brief Get the description of the last error reported by the API. + * + * This function returns a null-terminated string that can be used by clients to better understand the reason + * for failure of a previous API call. + * + * \param [in] encoder + * Pointer to the NvEncodeAPI interface. + * + * \return + * Pointer to buffer containing the details of the last error encountered by the API. + */ +const char * NVENCAPI NvEncGetLastErrorString (void* encoder); + + +/// \cond API PFN +/* + * Defines API function pointers + */ +typedef NVENCSTATUS (NVENCAPI* PNVENCOPENENCODESESSION) (void* device, uint32_t deviceType, void** encoder); +typedef NVENCSTATUS (NVENCAPI* PNVENCGETENCODEGUIDCOUNT) (void* encoder, uint32_t* encodeGUIDCount); +typedef NVENCSTATUS (NVENCAPI* PNVENCGETENCODEGUIDS) (void* encoder, GUID* GUIDs, uint32_t guidArraySize, uint32_t* GUIDCount); +typedef NVENCSTATUS (NVENCAPI* PNVENCGETENCODEPROFILEGUIDCOUNT) (void* encoder, GUID encodeGUID, uint32_t* encodeProfileGUIDCount); +typedef NVENCSTATUS (NVENCAPI* PNVENCGETENCODEPROFILEGUIDS) (void* encoder, GUID encodeGUID, GUID* profileGUIDs, uint32_t guidArraySize, uint32_t* GUIDCount); +typedef NVENCSTATUS (NVENCAPI* PNVENCGETINPUTFORMATCOUNT) (void* encoder, GUID encodeGUID, uint32_t* inputFmtCount); +typedef NVENCSTATUS (NVENCAPI* PNVENCGETINPUTFORMATS) (void* encoder, GUID encodeGUID, NV_ENC_BUFFER_FORMAT* inputFmts, uint32_t inputFmtArraySize, uint32_t* inputFmtCount); +typedef NVENCSTATUS (NVENCAPI* PNVENCGETENCODECAPS) (void* encoder, GUID encodeGUID, NV_ENC_CAPS_PARAM* capsParam, int* capsVal); +typedef NVENCSTATUS (NVENCAPI* PNVENCGETENCODEPRESETCOUNT) (void* encoder, GUID encodeGUID, uint32_t* encodePresetGUIDCount); +typedef NVENCSTATUS (NVENCAPI* PNVENCGETENCODEPRESETGUIDS) (void* encoder, GUID encodeGUID, GUID* presetGUIDs, uint32_t guidArraySize, uint32_t* encodePresetGUIDCount); +typedef NVENCSTATUS (NVENCAPI* PNVENCGETENCODEPRESETCONFIG) (void* encoder, GUID encodeGUID, GUID presetGUID, NV_ENC_PRESET_CONFIG* presetConfig); +typedef NVENCSTATUS (NVENCAPI* PNVENCGETENCODEPRESETCONFIGEX) (void* encoder, GUID encodeGUID, GUID presetGUID, NV_ENC_TUNING_INFO tuningInfo, NV_ENC_PRESET_CONFIG* presetConfig); +typedef NVENCSTATUS (NVENCAPI* PNVENCINITIALIZEENCODER) (void* encoder, NV_ENC_INITIALIZE_PARAMS* createEncodeParams); +typedef NVENCSTATUS (NVENCAPI* PNVENCCREATEINPUTBUFFER) (void* encoder, NV_ENC_CREATE_INPUT_BUFFER* createInputBufferParams); +typedef NVENCSTATUS (NVENCAPI* PNVENCDESTROYINPUTBUFFER) (void* encoder, NV_ENC_INPUT_PTR inputBuffer); +typedef NVENCSTATUS (NVENCAPI* PNVENCCREATEBITSTREAMBUFFER) (void* encoder, NV_ENC_CREATE_BITSTREAM_BUFFER* createBitstreamBufferParams); +typedef NVENCSTATUS (NVENCAPI* PNVENCDESTROYBITSTREAMBUFFER) (void* encoder, NV_ENC_OUTPUT_PTR bitstreamBuffer); +typedef NVENCSTATUS (NVENCAPI* PNVENCENCODEPICTURE) (void* encoder, NV_ENC_PIC_PARAMS* encodePicParams); +typedef NVENCSTATUS (NVENCAPI* PNVENCLOCKBITSTREAM) (void* encoder, NV_ENC_LOCK_BITSTREAM* lockBitstreamBufferParams); +typedef NVENCSTATUS (NVENCAPI* PNVENCUNLOCKBITSTREAM) (void* encoder, NV_ENC_OUTPUT_PTR bitstreamBuffer); +typedef NVENCSTATUS (NVENCAPI* PNVENCLOCKINPUTBUFFER) (void* encoder, NV_ENC_LOCK_INPUT_BUFFER* lockInputBufferParams); +typedef NVENCSTATUS (NVENCAPI* PNVENCUNLOCKINPUTBUFFER) (void* encoder, NV_ENC_INPUT_PTR inputBuffer); +typedef NVENCSTATUS (NVENCAPI* PNVENCGETENCODESTATS) (void* encoder, NV_ENC_STAT* encodeStats); +typedef NVENCSTATUS (NVENCAPI* PNVENCGETSEQUENCEPARAMS) (void* encoder, NV_ENC_SEQUENCE_PARAM_PAYLOAD* sequenceParamPayload); +typedef NVENCSTATUS (NVENCAPI* PNVENCREGISTERASYNCEVENT) (void* encoder, NV_ENC_EVENT_PARAMS* eventParams); +typedef NVENCSTATUS (NVENCAPI* PNVENCUNREGISTERASYNCEVENT) (void* encoder, NV_ENC_EVENT_PARAMS* eventParams); +typedef NVENCSTATUS (NVENCAPI* PNVENCMAPINPUTRESOURCE) (void* encoder, NV_ENC_MAP_INPUT_RESOURCE* mapInputResParams); +typedef NVENCSTATUS (NVENCAPI* PNVENCUNMAPINPUTRESOURCE) (void* encoder, NV_ENC_INPUT_PTR mappedInputBuffer); +typedef NVENCSTATUS (NVENCAPI* PNVENCDESTROYENCODER) (void* encoder); +typedef NVENCSTATUS (NVENCAPI* PNVENCINVALIDATEREFFRAMES) (void* encoder, uint64_t invalidRefFrameTimeStamp); +typedef NVENCSTATUS (NVENCAPI* PNVENCOPENENCODESESSIONEX) (NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS *openSessionExParams, void** encoder); +typedef NVENCSTATUS (NVENCAPI* PNVENCREGISTERRESOURCE) (void* encoder, NV_ENC_REGISTER_RESOURCE* registerResParams); +typedef NVENCSTATUS (NVENCAPI* PNVENCUNREGISTERRESOURCE) (void* encoder, NV_ENC_REGISTERED_PTR registeredRes); +typedef NVENCSTATUS (NVENCAPI* PNVENCRECONFIGUREENCODER) (void* encoder, NV_ENC_RECONFIGURE_PARAMS* reInitEncodeParams); + +typedef NVENCSTATUS (NVENCAPI* PNVENCCREATEMVBUFFER) (void* encoder, NV_ENC_CREATE_MV_BUFFER* createMVBufferParams); +typedef NVENCSTATUS (NVENCAPI* PNVENCDESTROYMVBUFFER) (void* encoder, NV_ENC_OUTPUT_PTR mvBuffer); +typedef NVENCSTATUS (NVENCAPI* PNVENCRUNMOTIONESTIMATIONONLY) (void* encoder, NV_ENC_MEONLY_PARAMS* meOnlyParams); +typedef const char * (NVENCAPI* PNVENCGETLASTERROR) (void* encoder); +typedef NVENCSTATUS (NVENCAPI* PNVENCSETIOCUDASTREAMS) (void* encoder, NV_ENC_CUSTREAM_PTR inputStream, NV_ENC_CUSTREAM_PTR outputStream); +typedef NVENCSTATUS (NVENCAPI* PNVENCGETSEQUENCEPARAMEX) (void* encoder, NV_ENC_INITIALIZE_PARAMS* encInitParams, NV_ENC_SEQUENCE_PARAM_PAYLOAD* sequenceParamPayload); + + +/// \endcond + + +/** @} */ /* END ENCODE_FUNC */ + +/** + * \ingroup ENCODER_STRUCTURE + * NV_ENCODE_API_FUNCTION_LIST + */ +typedef struct _NV_ENCODE_API_FUNCTION_LIST +{ + uint32_t version; /**< [in]: Client should pass NV_ENCODE_API_FUNCTION_LIST_VER. */ + uint32_t reserved; /**< [in]: Reserved and should be set to 0. */ + PNVENCOPENENCODESESSION nvEncOpenEncodeSession; /**< [out]: Client should access ::NvEncOpenEncodeSession() API through this pointer. */ + PNVENCGETENCODEGUIDCOUNT nvEncGetEncodeGUIDCount; /**< [out]: Client should access ::NvEncGetEncodeGUIDCount() API through this pointer. */ + PNVENCGETENCODEPRESETCOUNT nvEncGetEncodeProfileGUIDCount; /**< [out]: Client should access ::NvEncGetEncodeProfileGUIDCount() API through this pointer.*/ + PNVENCGETENCODEPRESETGUIDS nvEncGetEncodeProfileGUIDs; /**< [out]: Client should access ::NvEncGetEncodeProfileGUIDs() API through this pointer. */ + PNVENCGETENCODEGUIDS nvEncGetEncodeGUIDs; /**< [out]: Client should access ::NvEncGetEncodeGUIDs() API through this pointer. */ + PNVENCGETINPUTFORMATCOUNT nvEncGetInputFormatCount; /**< [out]: Client should access ::NvEncGetInputFormatCount() API through this pointer. */ + PNVENCGETINPUTFORMATS nvEncGetInputFormats; /**< [out]: Client should access ::NvEncGetInputFormats() API through this pointer. */ + PNVENCGETENCODECAPS nvEncGetEncodeCaps; /**< [out]: Client should access ::NvEncGetEncodeCaps() API through this pointer. */ + PNVENCGETENCODEPRESETCOUNT nvEncGetEncodePresetCount; /**< [out]: Client should access ::NvEncGetEncodePresetCount() API through this pointer. */ + PNVENCGETENCODEPRESETGUIDS nvEncGetEncodePresetGUIDs; /**< [out]: Client should access ::NvEncGetEncodePresetGUIDs() API through this pointer. */ + PNVENCGETENCODEPRESETCONFIG nvEncGetEncodePresetConfig; /**< [out]: Client should access ::NvEncGetEncodePresetConfig() API through this pointer. */ + PNVENCINITIALIZEENCODER nvEncInitializeEncoder; /**< [out]: Client should access ::NvEncInitializeEncoder() API through this pointer. */ + PNVENCCREATEINPUTBUFFER nvEncCreateInputBuffer; /**< [out]: Client should access ::NvEncCreateInputBuffer() API through this pointer. */ + PNVENCDESTROYINPUTBUFFER nvEncDestroyInputBuffer; /**< [out]: Client should access ::NvEncDestroyInputBuffer() API through this pointer. */ + PNVENCCREATEBITSTREAMBUFFER nvEncCreateBitstreamBuffer; /**< [out]: Client should access ::NvEncCreateBitstreamBuffer() API through this pointer. */ + PNVENCDESTROYBITSTREAMBUFFER nvEncDestroyBitstreamBuffer; /**< [out]: Client should access ::NvEncDestroyBitstreamBuffer() API through this pointer. */ + PNVENCENCODEPICTURE nvEncEncodePicture; /**< [out]: Client should access ::NvEncEncodePicture() API through this pointer. */ + PNVENCLOCKBITSTREAM nvEncLockBitstream; /**< [out]: Client should access ::NvEncLockBitstream() API through this pointer. */ + PNVENCUNLOCKBITSTREAM nvEncUnlockBitstream; /**< [out]: Client should access ::NvEncUnlockBitstream() API through this pointer. */ + PNVENCLOCKINPUTBUFFER nvEncLockInputBuffer; /**< [out]: Client should access ::NvEncLockInputBuffer() API through this pointer. */ + PNVENCUNLOCKINPUTBUFFER nvEncUnlockInputBuffer; /**< [out]: Client should access ::NvEncUnlockInputBuffer() API through this pointer. */ + PNVENCGETENCODESTATS nvEncGetEncodeStats; /**< [out]: Client should access ::NvEncGetEncodeStats() API through this pointer. */ + PNVENCGETSEQUENCEPARAMS nvEncGetSequenceParams; /**< [out]: Client should access ::NvEncGetSequenceParams() API through this pointer. */ + PNVENCREGISTERASYNCEVENT nvEncRegisterAsyncEvent; /**< [out]: Client should access ::NvEncRegisterAsyncEvent() API through this pointer. */ + PNVENCUNREGISTERASYNCEVENT nvEncUnregisterAsyncEvent; /**< [out]: Client should access ::NvEncUnregisterAsyncEvent() API through this pointer. */ + PNVENCMAPINPUTRESOURCE nvEncMapInputResource; /**< [out]: Client should access ::NvEncMapInputResource() API through this pointer. */ + PNVENCUNMAPINPUTRESOURCE nvEncUnmapInputResource; /**< [out]: Client should access ::NvEncUnmapInputResource() API through this pointer. */ + PNVENCDESTROYENCODER nvEncDestroyEncoder; /**< [out]: Client should access ::NvEncDestroyEncoder() API through this pointer. */ + PNVENCINVALIDATEREFFRAMES nvEncInvalidateRefFrames; /**< [out]: Client should access ::NvEncInvalidateRefFrames() API through this pointer. */ + PNVENCOPENENCODESESSIONEX nvEncOpenEncodeSessionEx; /**< [out]: Client should access ::NvEncOpenEncodeSession() API through this pointer. */ + PNVENCREGISTERRESOURCE nvEncRegisterResource; /**< [out]: Client should access ::NvEncRegisterResource() API through this pointer. */ + PNVENCUNREGISTERRESOURCE nvEncUnregisterResource; /**< [out]: Client should access ::NvEncUnregisterResource() API through this pointer. */ + PNVENCRECONFIGUREENCODER nvEncReconfigureEncoder; /**< [out]: Client should access ::NvEncReconfigureEncoder() API through this pointer. */ + void* reserved1; + PNVENCCREATEMVBUFFER nvEncCreateMVBuffer; /**< [out]: Client should access ::NvEncCreateMVBuffer API through this pointer. */ + PNVENCDESTROYMVBUFFER nvEncDestroyMVBuffer; /**< [out]: Client should access ::NvEncDestroyMVBuffer API through this pointer. */ + PNVENCRUNMOTIONESTIMATIONONLY nvEncRunMotionEstimationOnly; /**< [out]: Client should access ::NvEncRunMotionEstimationOnly API through this pointer. */ + PNVENCGETLASTERROR nvEncGetLastErrorString; /**< [out]: Client should access ::nvEncGetLastErrorString API through this pointer. */ + PNVENCSETIOCUDASTREAMS nvEncSetIOCudaStreams; /**< [out]: Client should access ::nvEncSetIOCudaStreams API through this pointer. */ + PNVENCGETENCODEPRESETCONFIGEX nvEncGetEncodePresetConfigEx; /**< [out]: Client should access ::NvEncGetEncodePresetConfigEx() API through this pointer. */ + PNVENCGETSEQUENCEPARAMEX nvEncGetSequenceParamEx; /**< [out]: Client should access ::NvEncGetSequenceParamEx() API through this pointer. */ + void* reserved2[277]; /**< [in]: Reserved and must be set to NULL */ +} NV_ENCODE_API_FUNCTION_LIST; + +/** Macro for constructing the version field of ::_NV_ENCODEAPI_FUNCTION_LIST. */ +#define NV_ENCODE_API_FUNCTION_LIST_VER NVENCAPI_STRUCT_VERSION(2) + +// NvEncodeAPICreateInstance +/** + * \ingroup ENCODE_FUNC + * Entry Point to the NvEncodeAPI interface. + * + * Creates an instance of the NvEncodeAPI interface, and populates the + * pFunctionList with function pointers to the API routines implemented by the + * NvEncodeAPI interface. + * + * \param [out] functionList + * + * \return + * ::NV_ENC_SUCCESS + * ::NV_ENC_ERR_INVALID_PTR + */ +NVENCSTATUS NVENCAPI NvEncodeAPICreateInstance(NV_ENCODE_API_FUNCTION_LIST *functionList); + +#ifdef __cplusplus +} +#endif + + +#endif + diff --git a/webrtc-sys/src/nvidia/NvCodec/include/nvcuvid.h b/webrtc-sys/src/nvidia/NvCodec/include/nvcuvid.h new file mode 100644 index 000000000..7e78535b7 --- /dev/null +++ b/webrtc-sys/src/nvidia/NvCodec/include/nvcuvid.h @@ -0,0 +1,501 @@ +/* + * This copyright notice applies to this header file only: + * + * Copyright (c) 2010-2022 NVIDIA Corporation + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the software, and to permit persons to whom the + * software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +/********************************************************************************************************************/ +//! \file nvcuvid.h +//! NVDECODE API provides video decoding interface to NVIDIA GPU devices. +//! \date 2015-2022 +//! This file contains the interface constants, structure definitions and function prototypes. +/********************************************************************************************************************/ + +#if !defined(__NVCUVID_H__) +#define __NVCUVID_H__ + +#include "cuviddec.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +#define MAX_CLOCK_TS 3 + +/***********************************************/ +//! +//! High-level helper APIs for video sources +//! +/***********************************************/ + +typedef void *CUvideosource; +typedef void *CUvideoparser; +typedef long long CUvideotimestamp; + + +/************************************************************************/ +//! \enum cudaVideoState +//! Video source state enums +//! Used in cuvidSetVideoSourceState and cuvidGetVideoSourceState APIs +/************************************************************************/ +typedef enum { + cudaVideoState_Error = -1, /**< Error state (invalid source) */ + cudaVideoState_Stopped = 0, /**< Source is stopped (or reached end-of-stream) */ + cudaVideoState_Started = 1 /**< Source is running and delivering data */ +} cudaVideoState; + +/************************************************************************/ +//! \enum cudaAudioCodec +//! Audio compression enums +//! Used in CUAUDIOFORMAT structure +/************************************************************************/ +typedef enum { + cudaAudioCodec_MPEG1=0, /**< MPEG-1 Audio */ + cudaAudioCodec_MPEG2, /**< MPEG-2 Audio */ + cudaAudioCodec_MP3, /**< MPEG-1 Layer III Audio */ + cudaAudioCodec_AC3, /**< Dolby Digital (AC3) Audio */ + cudaAudioCodec_LPCM, /**< PCM Audio */ + cudaAudioCodec_AAC, /**< AAC Audio */ +} cudaAudioCodec; + +/************************************************************************/ +//! \ingroup STRUCTS +//! \struct HEVCTIMECODESET +//! Used to store Time code extracted from Time code SEI in HEVC codec +/************************************************************************/ +typedef struct _HEVCTIMECODESET +{ + unsigned int time_offset_value; + unsigned short n_frames; + unsigned char clock_timestamp_flag; + unsigned char units_field_based_flag; + unsigned char counting_type; + unsigned char full_timestamp_flag; + unsigned char discontinuity_flag; + unsigned char cnt_dropped_flag; + unsigned char seconds_value; + unsigned char minutes_value; + unsigned char hours_value; + unsigned char seconds_flag; + unsigned char minutes_flag; + unsigned char hours_flag; + unsigned char time_offset_length; + unsigned char reserved; +} HEVCTIMECODESET; + +/************************************************************************/ +//! \ingroup STRUCTS +//! \struct HEVCSEITIMECODE +//! Used to extract Time code SEI in HEVC codec +/************************************************************************/ +typedef struct _HEVCSEITIMECODE +{ + HEVCTIMECODESET time_code_set[MAX_CLOCK_TS]; + unsigned char num_clock_ts; +} HEVCSEITIMECODE; + +/**********************************************************************************/ +//! \ingroup STRUCTS +//! \struct CUSEIMESSAGE; +//! Used in CUVIDSEIMESSAGEINFO structure +/**********************************************************************************/ +typedef struct _CUSEIMESSAGE +{ + unsigned char sei_message_type; /**< OUT: SEI Message Type */ + unsigned char reserved[3]; + unsigned int sei_message_size; /**< OUT: SEI Message Size */ +} CUSEIMESSAGE; + +/************************************************************************************************/ +//! \ingroup STRUCTS +//! \struct CUVIDEOFORMAT +//! Video format +//! Used in cuvidGetSourceVideoFormat API +/************************************************************************************************/ +typedef struct +{ + cudaVideoCodec codec; /**< OUT: Compression format */ + /** + * OUT: frame rate = numerator / denominator (for example: 30000/1001) + */ + struct { + /**< OUT: frame rate numerator (0 = unspecified or variable frame rate) */ + unsigned int numerator; + /**< OUT: frame rate denominator (0 = unspecified or variable frame rate) */ + unsigned int denominator; + } frame_rate; + unsigned char progressive_sequence; /**< OUT: 0=interlaced, 1=progressive */ + unsigned char bit_depth_luma_minus8; /**< OUT: high bit depth luma. E.g, 2 for 10-bitdepth, 4 for 12-bitdepth */ + unsigned char bit_depth_chroma_minus8; /**< OUT: high bit depth chroma. E.g, 2 for 10-bitdepth, 4 for 12-bitdepth */ + unsigned char min_num_decode_surfaces; /**< OUT: Minimum number of decode surfaces to be allocated for correct + decoding. The client can send this value in ulNumDecodeSurfaces + (in CUVIDDECODECREATEINFO structure). + This guarantees correct functionality and optimal video memory + usage but not necessarily the best performance, which depends on + the design of the overall application. The optimal number of + decode surfaces (in terms of performance and memory utilization) + should be decided by experimentation for each application, but it + cannot go below min_num_decode_surfaces. + If this value is used for ulNumDecodeSurfaces then it must be + returned to parser during sequence callback. */ + unsigned int coded_width; /**< OUT: coded frame width in pixels */ + unsigned int coded_height; /**< OUT: coded frame height in pixels */ + /** + * area of the frame that should be displayed + * typical example: + * coded_width = 1920, coded_height = 1088 + * display_area = { 0,0,1920,1080 } + */ + struct { + int left; /**< OUT: left position of display rect */ + int top; /**< OUT: top position of display rect */ + int right; /**< OUT: right position of display rect */ + int bottom; /**< OUT: bottom position of display rect */ + } display_area; + cudaVideoChromaFormat chroma_format; /**< OUT: Chroma format */ + unsigned int bitrate; /**< OUT: video bitrate (bps, 0=unknown) */ + /** + * OUT: Display Aspect Ratio = x:y (4:3, 16:9, etc) + */ + struct { + int x; + int y; + } display_aspect_ratio; + /** + * Video Signal Description + * Refer section E.2.1 (VUI parameters semantics) of H264 spec file + */ + struct { + unsigned char video_format : 3; /**< OUT: 0-Component, 1-PAL, 2-NTSC, 3-SECAM, 4-MAC, 5-Unspecified */ + unsigned char video_full_range_flag : 1; /**< OUT: indicates the black level and luma and chroma range */ + unsigned char reserved_zero_bits : 4; /**< Reserved bits */ + unsigned char color_primaries; /**< OUT: chromaticity coordinates of source primaries */ + unsigned char transfer_characteristics; /**< OUT: opto-electronic transfer characteristic of the source picture */ + unsigned char matrix_coefficients; /**< OUT: used in deriving luma and chroma signals from RGB primaries */ + } video_signal_description; + unsigned int seqhdr_data_length; /**< OUT: Additional bytes following (CUVIDEOFORMATEX) */ +} CUVIDEOFORMAT; + +/****************************************************************/ +//! \ingroup STRUCTS +//! \struct CUVIDOPERATINGPOINTINFO +//! Operating point information of scalable bitstream +/****************************************************************/ +typedef struct +{ + cudaVideoCodec codec; + union + { + struct + { + unsigned char operating_points_cnt; + unsigned char reserved24_bits[3]; + unsigned short operating_points_idc[32]; + } av1; + unsigned char CodecReserved[1024]; + }; +} CUVIDOPERATINGPOINTINFO; + +/**********************************************************************************/ +//! \ingroup STRUCTS +//! \struct CUVIDSEIMESSAGEINFO +//! Used in cuvidParseVideoData API with PFNVIDSEIMSGCALLBACK pfnGetSEIMsg +/**********************************************************************************/ +typedef struct _CUVIDSEIMESSAGEINFO +{ + void *pSEIData; /**< OUT: SEI Message Data */ + CUSEIMESSAGE *pSEIMessage; /**< OUT: SEI Message Info */ + unsigned int sei_message_count; /**< OUT: SEI Message Count */ + unsigned int picIdx; /**< OUT: SEI Message Pic Index */ +} CUVIDSEIMESSAGEINFO; + +/****************************************************************/ +//! \ingroup STRUCTS +//! \struct CUVIDAV1SEQHDR +//! AV1 specific sequence header information +/****************************************************************/ +typedef struct { + unsigned int max_width; + unsigned int max_height; + unsigned char reserved[1016]; +} CUVIDAV1SEQHDR; + +/****************************************************************/ +//! \ingroup STRUCTS +//! \struct CUVIDEOFORMATEX +//! Video format including raw sequence header information +//! Used in cuvidGetSourceVideoFormat API +/****************************************************************/ +typedef struct +{ + CUVIDEOFORMAT format; /**< OUT: CUVIDEOFORMAT structure */ + union { + CUVIDAV1SEQHDR av1; + unsigned char raw_seqhdr_data[1024]; /**< OUT: Sequence header data */ + }; +} CUVIDEOFORMATEX; + +/****************************************************************/ +//! \ingroup STRUCTS +//! \struct CUAUDIOFORMAT +//! Audio formats +//! Used in cuvidGetSourceAudioFormat API +/****************************************************************/ +typedef struct +{ + cudaAudioCodec codec; /**< OUT: Compression format */ + unsigned int channels; /**< OUT: number of audio channels */ + unsigned int samplespersec; /**< OUT: sampling frequency */ + unsigned int bitrate; /**< OUT: For uncompressed, can also be used to determine bits per sample */ + unsigned int reserved1; /**< Reserved for future use */ + unsigned int reserved2; /**< Reserved for future use */ +} CUAUDIOFORMAT; + + +/***************************************************************/ +//! \enum CUvideopacketflags +//! Data packet flags +//! Used in CUVIDSOURCEDATAPACKET structure +/***************************************************************/ +typedef enum { + CUVID_PKT_ENDOFSTREAM = 0x01, /**< Set when this is the last packet for this stream */ + CUVID_PKT_TIMESTAMP = 0x02, /**< Timestamp is valid */ + CUVID_PKT_DISCONTINUITY = 0x04, /**< Set when a discontinuity has to be signalled */ + CUVID_PKT_ENDOFPICTURE = 0x08, /**< Set when the packet contains exactly one frame or one field */ + CUVID_PKT_NOTIFY_EOS = 0x10, /**< If this flag is set along with CUVID_PKT_ENDOFSTREAM, an additional (dummy) + display callback will be invoked with null value of CUVIDPARSERDISPINFO which + should be interpreted as end of the stream. */ +} CUvideopacketflags; + +/*****************************************************************************/ +//! \ingroup STRUCTS +//! \struct CUVIDSOURCEDATAPACKET +//! Data Packet +//! Used in cuvidParseVideoData API +//! IN for cuvidParseVideoData +/*****************************************************************************/ +typedef struct _CUVIDSOURCEDATAPACKET +{ + unsigned long flags; /**< IN: Combination of CUVID_PKT_XXX flags */ + unsigned long payload_size; /**< IN: number of bytes in the payload (may be zero if EOS flag is set) */ + const unsigned char *payload; /**< IN: Pointer to packet payload data (may be NULL if EOS flag is set) */ + CUvideotimestamp timestamp; /**< IN: Presentation time stamp (10MHz clock), only valid if + CUVID_PKT_TIMESTAMP flag is set */ +} CUVIDSOURCEDATAPACKET; + +// Callback for packet delivery +typedef int (CUDAAPI *PFNVIDSOURCECALLBACK)(void *, CUVIDSOURCEDATAPACKET *); + +/**************************************************************************************************************************/ +//! \ingroup STRUCTS +//! \struct CUVIDSOURCEPARAMS +//! Describes parameters needed in cuvidCreateVideoSource API +//! NVDECODE API is intended for HW accelerated video decoding so CUvideosource doesn't have audio demuxer for all supported +//! containers. It's recommended to clients to use their own or third party demuxer if audio support is needed. +/**************************************************************************************************************************/ +typedef struct _CUVIDSOURCEPARAMS +{ + unsigned int ulClockRate; /**< IN: Time stamp units in Hz (0=default=10000000Hz) */ + unsigned int bAnnexb : 1; /**< IN: AV1 annexB stream */ + unsigned int uReserved : 31; /**< Reserved for future use - set to zero */ + unsigned int uReserved1[6]; /**< Reserved for future use - set to zero */ + void *pUserData; /**< IN: User private data passed in to the data handlers */ + PFNVIDSOURCECALLBACK pfnVideoDataHandler; /**< IN: Called to deliver video packets */ + PFNVIDSOURCECALLBACK pfnAudioDataHandler; /**< IN: Called to deliver audio packets. */ + void *pvReserved2[8]; /**< Reserved for future use - set to NULL */ +} CUVIDSOURCEPARAMS; + + +/**********************************************/ +//! \ingroup ENUMS +//! \enum CUvideosourceformat_flags +//! CUvideosourceformat_flags +//! Used in cuvidGetSourceVideoFormat API +/**********************************************/ +typedef enum { + CUVID_FMT_EXTFORMATINFO = 0x100 /**< Return extended format structure (CUVIDEOFORMATEX) */ +} CUvideosourceformat_flags; + +#if !defined(__APPLE__) +/***************************************************************************************************************************/ +//! \ingroup FUNCTS +//! \fn CUresult CUDAAPI cuvidCreateVideoSource(CUvideosource *pObj, const char *pszFileName, CUVIDSOURCEPARAMS *pParams) +//! Create CUvideosource object. CUvideosource spawns demultiplexer thread that provides two callbacks: +//! pfnVideoDataHandler() and pfnAudioDataHandler() +//! NVDECODE API is intended for HW accelerated video decoding so CUvideosource doesn't have audio demuxer for all supported +//! containers. It's recommended to clients to use their own or third party demuxer if audio support is needed. +/***************************************************************************************************************************/ +CUresult CUDAAPI cuvidCreateVideoSource(CUvideosource *pObj, const char *pszFileName, CUVIDSOURCEPARAMS *pParams); + +/***************************************************************************************************************************/ +//! \ingroup FUNCTS +//! \fn CUresult CUDAAPI cuvidCreateVideoSourceW(CUvideosource *pObj, const wchar_t *pwszFileName, CUVIDSOURCEPARAMS *pParams) +//! Create video source +/***************************************************************************************************************************/ +CUresult CUDAAPI cuvidCreateVideoSourceW(CUvideosource *pObj, const wchar_t *pwszFileName, CUVIDSOURCEPARAMS *pParams); + +/********************************************************************/ +//! \ingroup FUNCTS +//! \fn CUresult CUDAAPI cuvidDestroyVideoSource(CUvideosource obj) +//! Destroy video source +/********************************************************************/ +CUresult CUDAAPI cuvidDestroyVideoSource(CUvideosource obj); + +/******************************************************************************************/ +//! \ingroup FUNCTS +//! \fn CUresult CUDAAPI cuvidSetVideoSourceState(CUvideosource obj, cudaVideoState state) +//! Set video source state to: +//! cudaVideoState_Started - to signal the source to run and deliver data +//! cudaVideoState_Stopped - to stop the source from delivering the data +//! cudaVideoState_Error - invalid source +/******************************************************************************************/ +CUresult CUDAAPI cuvidSetVideoSourceState(CUvideosource obj, cudaVideoState state); + +/******************************************************************************************/ +//! \ingroup FUNCTS +//! \fn cudaVideoState CUDAAPI cuvidGetVideoSourceState(CUvideosource obj) +//! Get video source state +//! Returns: +//! cudaVideoState_Started - if Source is running and delivering data +//! cudaVideoState_Stopped - if Source is stopped or reached end-of-stream +//! cudaVideoState_Error - if Source is in error state +/******************************************************************************************/ +cudaVideoState CUDAAPI cuvidGetVideoSourceState(CUvideosource obj); + +/******************************************************************************************************************/ +//! \ingroup FUNCTS +//! \fn CUresult CUDAAPI cuvidGetSourceVideoFormat(CUvideosource obj, CUVIDEOFORMAT *pvidfmt, unsigned int flags) +//! Gets video source format in pvidfmt, flags is set to combination of CUvideosourceformat_flags as per requirement +/******************************************************************************************************************/ +CUresult CUDAAPI cuvidGetSourceVideoFormat(CUvideosource obj, CUVIDEOFORMAT *pvidfmt, unsigned int flags); + +/**************************************************************************************************************************/ +//! \ingroup FUNCTS +//! \fn CUresult CUDAAPI cuvidGetSourceAudioFormat(CUvideosource obj, CUAUDIOFORMAT *paudfmt, unsigned int flags) +//! Get audio source format +//! NVDECODE API is intended for HW accelerated video decoding so CUvideosource doesn't have audio demuxer for all supported +//! containers. It's recommended to clients to use their own or third party demuxer if audio support is needed. +/**************************************************************************************************************************/ +CUresult CUDAAPI cuvidGetSourceAudioFormat(CUvideosource obj, CUAUDIOFORMAT *paudfmt, unsigned int flags); + +#endif +/**********************************************************************************/ +//! \ingroup STRUCTS +//! \struct CUVIDPARSERDISPINFO +//! Used in cuvidParseVideoData API with PFNVIDDISPLAYCALLBACK pfnDisplayPicture +/**********************************************************************************/ +typedef struct _CUVIDPARSERDISPINFO +{ + int picture_index; /**< OUT: Index of the current picture */ + int progressive_frame; /**< OUT: 1 if progressive frame; 0 otherwise */ + int top_field_first; /**< OUT: 1 if top field is displayed first; 0 otherwise */ + int repeat_first_field; /**< OUT: Number of additional fields (1=ivtc, 2=frame doubling, 4=frame tripling, + -1=unpaired field) */ + CUvideotimestamp timestamp; /**< OUT: Presentation time stamp */ +} CUVIDPARSERDISPINFO; + +/***********************************************************************************************************************/ +//! Parser callbacks +//! The parser will call these synchronously from within cuvidParseVideoData(), whenever there is sequence change or a picture +//! is ready to be decoded and/or displayed. First argument in functions is "void *pUserData" member of structure CUVIDSOURCEPARAMS +//! Return values from these callbacks are interpreted as below. If the callbacks return failure, it will be propagated by +//! cuvidParseVideoData() to the application. +//! Parser picks default operating point as 0 and outputAllLayers flag as 0 if PFNVIDOPPOINTCALLBACK is not set or return value is +//! -1 or invalid operating point. +//! PFNVIDSEQUENCECALLBACK : 0: fail, 1: succeeded, > 1: override dpb size of parser (set by CUVIDPARSERPARAMS::ulMaxNumDecodeSurfaces +//! while creating parser) +//! PFNVIDDECODECALLBACK : 0: fail, >=1: succeeded +//! PFNVIDDISPLAYCALLBACK : 0: fail, >=1: succeeded +//! PFNVIDOPPOINTCALLBACK : <0: fail, >=0: succeeded (bit 0-9: OperatingPoint, bit 10-10: outputAllLayers, bit 11-30: reserved) +//! PFNVIDSEIMSGCALLBACK : 0: fail, >=1: succeeded +/***********************************************************************************************************************/ +typedef int (CUDAAPI *PFNVIDSEQUENCECALLBACK)(void *, CUVIDEOFORMAT *); +typedef int (CUDAAPI *PFNVIDDECODECALLBACK)(void *, CUVIDPICPARAMS *); +typedef int (CUDAAPI *PFNVIDDISPLAYCALLBACK)(void *, CUVIDPARSERDISPINFO *); +typedef int (CUDAAPI *PFNVIDOPPOINTCALLBACK)(void *, CUVIDOPERATINGPOINTINFO*); +typedef int (CUDAAPI *PFNVIDSEIMSGCALLBACK) (void *, CUVIDSEIMESSAGEINFO *); + +/**************************************/ +//! \ingroup STRUCTS +//! \struct CUVIDPARSERPARAMS +//! Used in cuvidCreateVideoParser API +/**************************************/ +typedef struct _CUVIDPARSERPARAMS +{ + cudaVideoCodec CodecType; /**< IN: cudaVideoCodec_XXX */ + unsigned int ulMaxNumDecodeSurfaces; /**< IN: Max # of decode surfaces (parser will cycle through these) */ + unsigned int ulClockRate; /**< IN: Timestamp units in Hz (0=default=10000000Hz) */ + unsigned int ulErrorThreshold; /**< IN: % Error threshold (0-100) for calling pfnDecodePicture (100=always + IN: call pfnDecodePicture even if picture bitstream is fully corrupted) */ + unsigned int ulMaxDisplayDelay; /**< IN: Max display queue delay (improves pipelining of decode with display) + 0=no delay (recommended values: 2..4) */ + unsigned int bAnnexb : 1; /**< IN: AV1 annexB stream */ + unsigned int uReserved : 31; /**< Reserved for future use - set to zero */ + unsigned int uReserved1[4]; /**< IN: Reserved for future use - set to 0 */ + void *pUserData; /**< IN: User data for callbacks */ + PFNVIDSEQUENCECALLBACK pfnSequenceCallback; /**< IN: Called before decoding frames and/or whenever there is a fmt change */ + PFNVIDDECODECALLBACK pfnDecodePicture; /**< IN: Called when a picture is ready to be decoded (decode order) */ + PFNVIDDISPLAYCALLBACK pfnDisplayPicture; /**< IN: Called whenever a picture is ready to be displayed (display order) */ + PFNVIDOPPOINTCALLBACK pfnGetOperatingPoint; /**< IN: Called from AV1 sequence header to get operating point of a AV1 + scalable bitstream */ + PFNVIDSEIMSGCALLBACK pfnGetSEIMsg; /**< IN: Called when all SEI messages are parsed for particular frame */ + void *pvReserved2[5]; /**< Reserved for future use - set to NULL */ + CUVIDEOFORMATEX *pExtVideoInfo; /**< IN: [Optional] sequence header data from system layer */ +} CUVIDPARSERPARAMS; + +/************************************************************************************************/ +//! \ingroup FUNCTS +//! \fn CUresult CUDAAPI cuvidCreateVideoParser(CUvideoparser *pObj, CUVIDPARSERPARAMS *pParams) +//! Create video parser object and initialize +/************************************************************************************************/ +CUresult CUDAAPI cuvidCreateVideoParser(CUvideoparser *pObj, CUVIDPARSERPARAMS *pParams); + +/************************************************************************************************/ +//! \ingroup FUNCTS +//! \fn CUresult CUDAAPI cuvidParseVideoData(CUvideoparser obj, CUVIDSOURCEDATAPACKET *pPacket) +//! Parse the video data from source data packet in pPacket +//! Extracts parameter sets like SPS, PPS, bitstream etc. from pPacket and +//! calls back pfnDecodePicture with CUVIDPICPARAMS data for kicking of HW decoding +//! calls back pfnSequenceCallback with CUVIDEOFORMAT data for initial sequence header or when +//! the decoder encounters a video format change +//! calls back pfnDisplayPicture with CUVIDPARSERDISPINFO data to display a video frame +/************************************************************************************************/ +CUresult CUDAAPI cuvidParseVideoData(CUvideoparser obj, CUVIDSOURCEDATAPACKET *pPacket); + +/************************************************************************************************/ +//! \ingroup FUNCTS +//! \fn CUresult CUDAAPI cuvidDestroyVideoParser(CUvideoparser obj) +//! Destroy the video parser +/************************************************************************************************/ +CUresult CUDAAPI cuvidDestroyVideoParser(CUvideoparser obj); + +/**********************************************************************************************/ + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif // __NVCUVID_H__ + + diff --git a/webrtc-sys/src/nvidia/cuda_context.cpp b/webrtc-sys/src/nvidia/cuda_context.cpp new file mode 100644 index 000000000..82c21df2b --- /dev/null +++ b/webrtc-sys/src/nvidia/cuda_context.cpp @@ -0,0 +1,123 @@ +#include "cuda_context.h" + +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +#if defined(WIN32) +#include +#else +#include +#endif + +#include + +#if defined(WIN32) +static const char CUDA_DYNAMIC_LIBRARY[] = "nvcuda.dll"; +#else +static const char CUDA_DYNAMIC_LIBRARY[] = "libcuda.so.1"; +#endif + +namespace livekit { + +#define __CUCTX_CUDA_CALL(call, ret) \ + CUresult err__ = call; \ + if (err__ != CUDA_SUCCESS) { \ + const char* szErrName = NULL; \ + cuGetErrorName(err__, &szErrName); \ + RTC_LOG(LS_ERROR) << "CudaContext error " << szErrName; \ + return ret; \ + } + +#define CUCTX_CUDA_CALL_ERROR(call) \ + do { \ + __CUCTX_CUDA_CALL(call, err__); \ + } while (0) + +static void* s_module_ptr = nullptr; +static const int kRequiredDriverVersion = 11000; + +static bool load_cuda_modules() { + if (s_module_ptr) + return true; + +#if defined(WIN32) + // dll delay load + HMODULE module = LoadLibrary(TEXT("nvcuda.dll")); + if (!module) { + RTC_LOG(LS_INFO) << "nvcuda.dll is not found."; + return false; + } + s_module_ptr = module; +#elif defined(__linux__) + s_module_ptr = dlopen("libcuda.so.1", RTLD_LAZY | RTLD_GLOBAL); + if (!s_module_ptr) + return false; + + // Close handle immediately because going to call `dlopen` again + // in the implib module when cuda api called on Linux. + dlclose(s_module_ptr); + s_module_ptr = nullptr; +#endif + return true; +} + +bool CudaContext::Initialize() { + // Initialize CUDA context + + bool success = load_cuda_modules(); + if (!success) { + std::cout << "Failed to load CUDA modules. maybe the NVIDIA driver is not installed?" << std::endl; + return false; + } + + int numDevices = 0; + CUdevice cu_device = 0; + CUcontext context = nullptr; + + int driverVersion = 0; + + CUCTX_CUDA_CALL_ERROR(cuDriverGetVersion(&driverVersion)); + if (kRequiredDriverVersion > driverVersion) { + RTC_LOG(LS_ERROR) + << "CUDA driver version is not higher than the required version. " + << driverVersion; + return false; + } + + CUCTX_CUDA_CALL_ERROR(cuInit(0)); + CUCTX_CUDA_CALL_ERROR(cuDeviceGet(&cu_device, 0)); + + char device_name[80]; + CUCTX_CUDA_CALL_ERROR( + cuDeviceGetName(device_name, sizeof(device_name), cu_device)); + RTC_LOG(LS_INFO) << "CUDA device name: " << device_name; + + CUCTX_CUDA_CALL_ERROR(cuCtxCreate(&context, 0, cu_device)); + if (context == nullptr) { + RTC_LOG(LS_ERROR) << "Failed to create CUDA context."; + return false; + } + + cu_device_ = cu_device; + cu_context_ = context; + + return true; +} + +void CudaContext::Shutdown() { + // Shutdown CUDA context + if (cu_context_) { + cuCtxDestroy(cu_context_); + cu_context_ = nullptr; + } + if (s_module_ptr) { +#if defined(WIN32) + FreeLibrary((HMODULE)s_module_ptr); +#elif defined(__linux__) + dlclose(s_module_ptr); +#endif + s_module_ptr = nullptr; + } +} + +} // namespace livekit diff --git a/webrtc-sys/src/nvidia/cuda_context.h b/webrtc-sys/src/nvidia/cuda_context.h new file mode 100644 index 000000000..6011c10ee --- /dev/null +++ b/webrtc-sys/src/nvidia/cuda_context.h @@ -0,0 +1,26 @@ +#ifndef WEBRTC_SYS_NVIDIA_CUDA_CONTEXT_H +#define WEBRTC_SYS_NVIDIA_CUDA_CONTEXT_H + +#include + +namespace livekit { + +class CudaContext { + public: + CudaContext() = default; + ~CudaContext() { Shutdown(); } + + bool Initialize(); + bool IsInitialized() const { return cu_context_ != nullptr; } + CUcontext GetContext() const { return cu_context_; } + CUdevice GetDevice() const { return cu_device_; } + void Shutdown(); + + private: + CUdevice cu_device_ = 0; + CUcontext cu_context_ = nullptr; +}; + +} // namespace livekit + +#endif // WEBRTC_SYS_NVIDIA_CUDA_CONTEXT_H diff --git a/webrtc-sys/src/nvidia/h264_decoder_impl.cpp b/webrtc-sys/src/nvidia/h264_decoder_impl.cpp new file mode 100644 index 000000000..3ba449b38 --- /dev/null +++ b/webrtc-sys/src/nvidia/h264_decoder_impl.cpp @@ -0,0 +1,186 @@ +#include "h264_decoder_impl.h" + +#include +#include +#include +#include + +#include "NvDecoder/NvDecoder.h" +#include "Utils/NvCodecUtils.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { + +ColorSpace ExtractH264ColorSpace(const CUVIDEOFORMAT& format) { + return ColorSpace( + static_cast( + format.video_signal_description.color_primaries), + static_cast( + format.video_signal_description.transfer_characteristics), + static_cast( + format.video_signal_description.matrix_coefficients), + static_cast( + format.video_signal_description.video_full_range_flag)); +} + +NvidiaH264DecoderImpl::NvidiaH264DecoderImpl(CUcontext context) + : cu_context_(context), + decoder_(nullptr), + is_configured_decoder_(false), + decoded_complete_callback_(nullptr), + buffer_pool_(false) {} + +NvidiaH264DecoderImpl::~NvidiaH264DecoderImpl() { + Release(); +} + +VideoDecoder::DecoderInfo NvidiaH264DecoderImpl::GetDecoderInfo() const { + VideoDecoder::DecoderInfo info; + info.implementation_name = "NVIDIA H264 Decoder"; + info.is_hardware_accelerated = true; + return info; +} + +bool NvidiaH264DecoderImpl::Configure(const Settings& settings) { + if (settings.codec_type() != kVideoCodecH264) { + RTC_LOG(LS_ERROR) + << "initialization failed on codectype is not kVideoCodecH264"; + return false; + } + if (!settings.max_render_resolution().Valid()) { + RTC_LOG(LS_ERROR) + << "initialization failed on codec_settings width < 0 or height < 0"; + return false; + } + + settings_ = settings; + + const CUresult result = cuCtxSetCurrent(cu_context_); + if (!ck(result)) { + RTC_LOG(LS_ERROR) << "initialization failed on cuCtxSetCurrent result" + << result; + return false; + } + + // todo(kazuki): Max resolution is differred each architecture. + // Refer to the table in Video Decoder Capabilities. + // https://docs.nvidia.com/video-technologies/video-codec-sdk/nvdec-video-decoder-api-prog-guide + int maxWidth = 4096; + int maxHeight = 4096; + + // bUseDeviceFrame: allocate in memory or cuda device memory + decoder_ = std::make_unique( + cu_context_, false, cudaVideoCodec_H264, true, false, nullptr, nullptr, + false, maxWidth, maxHeight); + return true; +} + +int32_t NvidiaH264DecoderImpl::RegisterDecodeCompleteCallback( + DecodedImageCallback* callback) { + this->decoded_complete_callback_ = callback; + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t NvidiaH264DecoderImpl::Release() { + buffer_pool_.Release(); + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t NvidiaH264DecoderImpl::Decode(const EncodedImage& input_image, + bool missing_frames, + int64_t render_time_ms) { + CUcontext current; + if (!ck(cuCtxGetCurrent(¤t))) { + RTC_LOG(LS_ERROR) << "decode failed on cuCtxGetCurrent is failed"; + return WEBRTC_VIDEO_CODEC_UNINITIALIZED; + } + if (current != cu_context_) { + RTC_LOG(LS_ERROR) + << "decode failed on not match current context and hold context"; + return WEBRTC_VIDEO_CODEC_UNINITIALIZED; + } + if (decoded_complete_callback_ == nullptr) { + RTC_LOG(LS_ERROR) << "decode failed on not set m_decodedCompleteCallback"; + return WEBRTC_VIDEO_CODEC_UNINITIALIZED; + } + if (!input_image.data() || !input_image.size()) { + RTC_LOG(LS_ERROR) << "decode failed on input image is null"; + return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; + } + + h264_bitstream_parser_.ParseBitstream(input_image); + absl::optional qp = h264_bitstream_parser_.GetLastSliceQp(); + absl::optional sps = h264_bitstream_parser_.sps(); + + if (is_configured_decoder_) { + if (!sps || + sps.value().width != static_cast(decoder_->GetWidth()) || + sps.value().height != static_cast(decoder_->GetHeight())) { + decoder_->setReconfigParams(nullptr, nullptr); + } + } + + int nFrameReturnd = 0; + do { + nFrameReturnd = decoder_->Decode( + input_image.data(), static_cast(input_image.size()), + CUVID_PKT_TIMESTAMP, input_image.RtpTimestamp()); + } while (nFrameReturnd == 0); + + is_configured_decoder_ = true; + + // todo: support other output format + // Chromium's H264 Encoder is output on NV12, so currently only NV12 is + // supported. + if (decoder_->GetOutputFormat() != cudaVideoSurfaceFormat_NV12) { + RTC_LOG(LS_ERROR) << "not supported this format: " + << decoder_->GetOutputFormat(); + return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; + } + + // Pass on color space from input frame if explicitly specified. + const ColorSpace& color_space = + input_image.ColorSpace() + ? *input_image.ColorSpace() + : ExtractH264ColorSpace(decoder_->GetVideoFormatInfo()); + + for (int i = 0; i < nFrameReturnd; i++) { + int64_t timeStamp; + uint8_t* pFrame = decoder_->GetFrame(&timeStamp); + + rtc::scoped_refptr i420_buffer = + buffer_pool_.CreateI420Buffer(decoder_->GetWidth(), + decoder_->GetHeight()); + + int result; + { + result = libyuv::NV12ToI420( + pFrame, decoder_->GetDeviceFramePitch(), + pFrame + decoder_->GetHeight() * decoder_->GetDeviceFramePitch(), + decoder_->GetDeviceFramePitch(), i420_buffer->MutableDataY(), + i420_buffer->StrideY(), i420_buffer->MutableDataU(), + i420_buffer->StrideU(), i420_buffer->MutableDataV(), + i420_buffer->StrideV(), decoder_->GetWidth(), decoder_->GetHeight()); + } + + if (result) { + RTC_LOG(LS_INFO) << "libyuv::NV12ToI420 failed. error:" << result; + } + + VideoFrame decoded_frame = + VideoFrame::Builder() + .set_video_frame_buffer(i420_buffer) + .set_timestamp_rtp(static_cast(timeStamp)) + .set_color_space(color_space) + .build(); + + // todo: measurement decoding time + absl::optional decodetime; + decoded_complete_callback_->Decoded(decoded_frame, decodetime, qp); + } + + return WEBRTC_VIDEO_CODEC_OK; +} + +} // end namespace webrtc diff --git a/webrtc-sys/src/nvidia/h264_decoder_impl.h b/webrtc-sys/src/nvidia/h264_decoder_impl.h new file mode 100644 index 000000000..882f8f0f7 --- /dev/null +++ b/webrtc-sys/src/nvidia/h264_decoder_impl.h @@ -0,0 +1,57 @@ +#ifndef WEBRTC_NVIDIA_H264_DECODER_IMPL_H_ +#define WEBRTC_NVIDIA_H264_DECODER_IMPL_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "NvDecoder/NvDecoder.h" + +namespace webrtc { + +class H264BitstreamParserEx : public ::webrtc::H264BitstreamParser { + public: + absl::optional sps() { return sps_; } + absl::optional pps() { return pps_; } +}; + +class NvidiaH264DecoderImpl : public VideoDecoder { + public: + NvidiaH264DecoderImpl(CUcontext context); + NvidiaH264DecoderImpl(const NvidiaH264DecoderImpl&) = delete; + NvidiaH264DecoderImpl& operator=(const NvidiaH264DecoderImpl&) = delete; + ~NvidiaH264DecoderImpl() override; + + bool Configure(const Settings& settings) override; + int32_t Decode(const EncodedImage& input_image, + bool missing_frames, + int64_t render_time_ms) override; + int32_t RegisterDecodeCompleteCallback( + DecodedImageCallback* callback) override; + int32_t Release() override; + DecoderInfo GetDecoderInfo() const override; + + private: + CUcontext cu_context_; + std::unique_ptr decoder_; + bool is_configured_decoder_; + + Settings settings_; + + DecodedImageCallback* decoded_complete_callback_ = nullptr; + webrtc::VideoFrameBufferPool buffer_pool_; + H264BitstreamParserEx h264_bitstream_parser_; +}; + +} // end namespace webrtc + +#endif // WEBRTC_NVIDIA_H264_DECODER_IMPL_H_ \ No newline at end of file diff --git a/webrtc-sys/src/nvidia/h264_encoder_impl.cpp b/webrtc-sys/src/nvidia/h264_encoder_impl.cpp new file mode 100644 index 000000000..17ad27e92 --- /dev/null +++ b/webrtc-sys/src/nvidia/h264_encoder_impl.cpp @@ -0,0 +1,455 @@ +#include "h264_encoder_impl.h" + + +#include +#include +#include + +#include "absl/strings/match.h" +#include "absl/types/optional.h" +#include "api/video/video_codec_constants.h" +#include "api/video_codecs/scalability_mode.h" +#include +#include "common_video/libyuv/include/webrtc_libyuv.h" +#include "modules/video_coding/include/video_codec_interface.h" +#include "modules/video_coding/include/video_error_codes.h" +#include "modules/video_coding/svc/create_scalability_structure.h" +#include "modules/video_coding/utility/simulcast_rate_allocator.h" +#include "modules/video_coding/utility/simulcast_utility.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/time_utils.h" +#include "system_wrappers/include/metrics.h" +#include "third_party/libyuv/include/libyuv/convert.h" +#include "third_party/libyuv/include/libyuv/scale.h" + +namespace webrtc { + +// Used by histograms. Values of entries should not be changed. +enum H264EncoderImplEvent { + kH264EncoderEventInit = 0, + kH264EncoderEventError = 1, + kH264EncoderEventMax = 16, +}; + + +NV_ENC_LEVEL H264LevelToNvEncLevel(webrtc::H264Level level) { + switch (level) { + case H264Level::kLevel1_b: + return NV_ENC_LEVEL_H264_1b; + case H264Level::kLevel1: + return NV_ENC_LEVEL_H264_1; + case H264Level::kLevel1_1: + return NV_ENC_LEVEL_H264_11; + case H264Level::kLevel1_2: + return NV_ENC_LEVEL_H264_12; + case H264Level::kLevel1_3: + return NV_ENC_LEVEL_H264_13; + case H264Level::kLevel2: + return NV_ENC_LEVEL_H264_2; + case H264Level::kLevel2_1: + return NV_ENC_LEVEL_H264_21; + case H264Level::kLevel2_2: + return NV_ENC_LEVEL_H264_22; + case H264Level::kLevel3: + return NV_ENC_LEVEL_H264_3; + case H264Level::kLevel3_1: + return NV_ENC_LEVEL_H264_31; + case H264Level::kLevel3_2: + return NV_ENC_LEVEL_H264_32; + case H264Level::kLevel4: + return NV_ENC_LEVEL_H264_4; + case H264Level::kLevel4_1: + return NV_ENC_LEVEL_H264_41; + case H264Level::kLevel4_2: + return NV_ENC_LEVEL_H264_42; + case H264Level::kLevel5: + return NV_ENC_LEVEL_H264_5; + case H264Level::kLevel5_1: + return NV_ENC_LEVEL_H264_51; + case H264Level::kLevel5_2: + return NV_ENC_LEVEL_H264_52; + } + return NV_ENC_LEVEL_AUTOSELECT; // Default value. +} + + +NvidiaH264EncoderImpl::NvidiaH264EncoderImpl( + const webrtc::Environment& env, + CUcontext context, + CUmemorytype memory_type, + NV_ENC_BUFFER_FORMAT nv_format, + const SdpVideoFormat& format) + : encoder_(nullptr), + cu_context_(context), + cu_memory_type_(memory_type), + cu_scaled_array_(nullptr), + nv_format_(nv_format), + packetization_mode_( + H264EncoderSettings::Parse(format).packetization_mode), + format_(format) { + std::string hexString = format_.parameters.at("profile-level-id"); + absl::optional profile_level_id = + webrtc::ParseH264ProfileLevelId(hexString.c_str()); + if (profile_level_id.has_value()) { + profile_ = profile_level_id->profile; + level_ = profile_level_id->level; + } + + nv_enc_level_ = NV_ENC_LEVEL_AUTOSELECT; + if (level_ != H264Level::kLevel1_b) { + // Convert H264Level to NV_ENC_LEVEL. + nv_enc_level_ = webrtc::H264LevelToNvEncLevel(level_); + } + + RTC_CHECK_NE(cu_memory_type_, CU_MEMORYTYPE_HOST); +} + +NvidiaH264EncoderImpl::~NvidiaH264EncoderImpl() { + Release(); +} + +void NvidiaH264EncoderImpl::ReportInit() { + if (has_reported_init_) + return; + RTC_HISTOGRAM_ENUMERATION("WebRTC.Video.H264EncoderImpl.Event", + kH264EncoderEventInit, kH264EncoderEventMax); + has_reported_init_ = true; +} + +void NvidiaH264EncoderImpl::ReportError() { + if (has_reported_error_) + return; + RTC_HISTOGRAM_ENUMERATION("WebRTC.Video.H264EncoderImpl.Event", + kH264EncoderEventError, kH264EncoderEventMax); + has_reported_error_ = true; +} + +int32_t NvidiaH264EncoderImpl::InitEncode( + const VideoCodec* inst, + const VideoEncoder::Settings& settings) { + if (!inst || inst->codecType != kVideoCodecH264) { + ReportError(); + return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; + } + if (inst->maxFramerate == 0) { + ReportError(); + return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; + } + if (inst->width < 1 || inst->height < 1) { + ReportError(); + return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; + } + + int32_t release_ret = Release(); + if (release_ret != WEBRTC_VIDEO_CODEC_OK) { + ReportError(); + return release_ret; + } + + codec_ = *inst; + + // Code expects simulcastStream resolutions to be correct, make sure they are + // filled even when there are no simulcast layers. + if (codec_.numberOfSimulcastStreams == 0) { + codec_.simulcastStream[0].width = codec_.width; + codec_.simulcastStream[0].height = codec_.height; + } + + // Initialize encoded image. Default buffer size: size of unencoded data. + const size_t new_capacity = + CalcBufferSize(VideoType::kI420, codec_.width, codec_.height); + encoded_image_.SetEncodedData(EncodedImageBuffer::Create(new_capacity)); + encoded_image_._encodedWidth = codec_.width; + encoded_image_._encodedHeight = codec_.height; + encoded_image_.set_size(0); + + configuration_.sending = false; + configuration_.frame_dropping_on = codec_.GetFrameDropEnabled(); + configuration_.key_frame_interval = codec_.H264()->keyFrameInterval; + + configuration_.width = codec_.width; + configuration_.height = codec_.height; + + configuration_.max_frame_rate = codec_.maxFramerate; + configuration_.target_bps = codec_.startBitrate * 1000; + configuration_.max_bps = codec_.maxBitrate * 1000; + + const CUresult result = cuCtxSetCurrent(cu_context_); + if (result != CUDA_SUCCESS) { + return WEBRTC_VIDEO_CODEC_ENCODER_FAILURE; + } + + // Some NVIDIA GPUs have a limited Encode Session count. + // We can't get the Session count, so catching NvEncThrow to avoid the crash. + // refer: + // https://developer.nvidia.com/video-encode-and-decode-gpu-support-matrix-new + try { + if (cu_memory_type_ == CU_MEMORYTYPE_DEVICE) { + encoder_ = std::make_unique(cu_context_, codec_.width, + codec_.height, nv_format_, 0); + } else { + RTC_DCHECK_NOTREACHED(); + } + } catch (const NVENCException& e) { + // todo: If Encoder initialization fails, need to notify for Managed side. + RTC_LOG(LS_ERROR) << "Failed Initialize NvEncoder " << e.what(); + return WEBRTC_VIDEO_CODEC_ERROR; + } + + nv_initialize_params_.version = NV_ENC_INITIALIZE_PARAMS_VER; + nv_encode_config_.version = NV_ENC_CONFIG_VER; + nv_initialize_params_.encodeConfig = &nv_encode_config_; + + GUID encodeGuid = NV_ENC_CODEC_H264_GUID; + GUID presetGuid = NV_ENC_PRESET_P4_GUID; + + encoder_->CreateDefaultEncoderParams(&nv_initialize_params_, encodeGuid, + presetGuid, + NV_ENC_TUNING_INFO_ULTRA_LOW_LATENCY); + + nv_initialize_params_.frameRateNum = + static_cast(configuration_.max_frame_rate); + nv_initialize_params_.frameRateDen = 1; + nv_initialize_params_.bufferFormat = nv_format_; + + nv_encode_config_.profileGUID = nv_profile_guid_; + nv_encode_config_.gopLength = NVENC_INFINITE_GOPLENGTH; + nv_encode_config_.frameIntervalP = 1; + nv_encode_config_.encodeCodecConfig.h264Config.level = nv_enc_level_; + nv_encode_config_.encodeCodecConfig.h264Config.idrPeriod = + NVENC_INFINITE_GOPLENGTH; + nv_encode_config_.rcParams.version = NV_ENC_RC_PARAMS_VER; + nv_encode_config_.rcParams.rateControlMode = NV_ENC_PARAMS_RC_CBR; + nv_encode_config_.rcParams.averageBitRate = configuration_.target_bps; + nv_encode_config_.rcParams.vbvBufferSize = + (nv_encode_config_.rcParams.averageBitRate * + nv_initialize_params_.frameRateDen / + nv_initialize_params_.frameRateNum) * + 5; + nv_encode_config_.rcParams.vbvInitialDelay = + nv_encode_config_.rcParams.vbvBufferSize; + + try { + encoder_->CreateEncoder(&nv_initialize_params_); + } catch (const NVENCException& e) { + RTC_LOG(LS_ERROR) << "Failed Initialize NvEncoder " << e.what(); + return WEBRTC_VIDEO_CODEC_ERROR; + } + + SimulcastRateAllocator init_allocator(codec_); + VideoBitrateAllocation allocation = + init_allocator.Allocate(VideoBitrateAllocationParameters( + DataRate::KilobitsPerSec(codec_.startBitrate), codec_.maxFramerate)); + SetRates(RateControlParameters(allocation, codec_.maxFramerate)); + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t NvidiaH264EncoderImpl::RegisterEncodeCompleteCallback( + EncodedImageCallback* callback) { + RTC_DCHECK(callback); + encoded_image_callback_ = callback; + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t NvidiaH264EncoderImpl::Release() { + if (encoder_) { + encoder_->DestroyEncoder(); + encoder_ = nullptr; + } + if (cu_scaled_array_) { + cuArrayDestroy(cu_scaled_array_); + cu_scaled_array_ = nullptr; + } + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t NvidiaH264EncoderImpl::Encode( + const VideoFrame& input_frame, + const std::vector* frame_types) { + if (!encoder_) { + ReportError(); + return WEBRTC_VIDEO_CODEC_UNINITIALIZED; + } + if (!encoded_image_callback_) { + RTC_LOG(LS_WARNING) + << "InitEncode() has been called, but a callback function " + "has not been set with RegisterEncodeCompleteCallback()"; + ReportError(); + return WEBRTC_VIDEO_CODEC_UNINITIALIZED; + } + + rtc::scoped_refptr frame_buffer = + input_frame.video_frame_buffer()->ToI420(); + if (!frame_buffer) { + RTC_LOG(LS_ERROR) << "Failed to convert " + << VideoFrameBufferTypeToString( + input_frame.video_frame_buffer()->type()) + << " image to I420. Can't encode frame."; + return WEBRTC_VIDEO_CODEC_ENCODER_FAILURE; + } + RTC_CHECK(frame_buffer->type() == VideoFrameBuffer::Type::kI420); + + bool is_keyframe_needed = false; + if (configuration_.key_frame_request && configuration_.sending) { + is_keyframe_needed = true; + } + + bool send_key_frame = + is_keyframe_needed || + (frame_types && (*frame_types)[0] == VideoFrameType::kVideoFrameKey); + if (send_key_frame) { + is_keyframe_needed = true; + configuration_.key_frame_request = false; + } + + RTC_DCHECK_EQ(configuration_.width, frame_buffer->width()); + RTC_DCHECK_EQ(configuration_.height, frame_buffer->height()); + + if (!configuration_.sending) { + return WEBRTC_VIDEO_CODEC_NO_OUTPUT; + } + + if (frame_types != nullptr) { + // Skip frame? + if ((*frame_types)[0] == VideoFrameType::kEmptyFrame) { + return WEBRTC_VIDEO_CODEC_NO_OUTPUT; + } + } + + try { + const NvEncInputFrame* nv_enc_input_frame = encoder_->GetNextInputFrame(); + + if (cu_memory_type_ == CU_MEMORYTYPE_DEVICE) { + NvEncoderCuda::CopyToDeviceFrame( + cu_context_, (void*)frame_buffer->DataY(), 0, + reinterpret_cast(nv_enc_input_frame->inputPtr), + nv_enc_input_frame->pitch, input_frame.width(), input_frame.height(), + CU_MEMORYTYPE_DEVICE, nv_enc_input_frame->bufferFormat, + nv_enc_input_frame->chromaOffsets, nv_enc_input_frame->numChromaPlanes); + } + + NV_ENC_PIC_PARAMS pic_params = NV_ENC_PIC_PARAMS(); + pic_params.version = NV_ENC_PIC_PARAMS_VER; + pic_params.encodePicFlags = 0; + if (is_keyframe_needed) { + pic_params.encodePicFlags = NV_ENC_PIC_FLAG_FORCEINTRA | + NV_ENC_PIC_FLAG_FORCEIDR | + NV_ENC_PIC_FLAG_OUTPUT_SPSPPS; + configuration_.key_frame_request = false; + } + + std::vector> bit_stream; + encoder_->EncodeFrame(bit_stream, &pic_params); + + for (std::vector& packet : bit_stream) { + int32_t result = ProcessEncodedFrame(packet, input_frame); + if (result != WEBRTC_VIDEO_CODEC_OK) { + return result; + } + } + } catch (const NVENCException& e) { + RTC_LOG(LS_ERROR) << "Failed EncodeFrame NvEncoder " << e.what(); + return WEBRTC_VIDEO_CODEC_ENCODER_FAILURE; + } + + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t NvidiaH264EncoderImpl::ProcessEncodedFrame( + std::vector& packet, + const ::webrtc::VideoFrame& inputFrame) { + encoded_image_._encodedWidth = encoder_->GetEncodeWidth(); + encoded_image_._encodedHeight = encoder_->GetEncodeHeight(); + encoded_image_.SetRtpTimestamp(inputFrame.timestamp()); + encoded_image_.SetSimulcastIndex(0); + encoded_image_.ntp_time_ms_ = inputFrame.ntp_time_ms(); + encoded_image_.capture_time_ms_ = inputFrame.render_time_ms(); + encoded_image_.rotation_ = inputFrame.rotation(); + encoded_image_.content_type_ = VideoContentType::UNSPECIFIED; + encoded_image_.timing_.flags = VideoSendTiming::kInvalid; + encoded_image_._frameType = VideoFrameType::kVideoFrameDelta; + encoded_image_.SetColorSpace(inputFrame.color_space()); + std::vector naluIndices = + H264::FindNaluIndices(packet.data(), packet.size()); + for (uint32_t i = 0; i < naluIndices.size(); i++) { + const H264::NaluType naluType = + H264::ParseNaluType(packet[naluIndices[i].payload_start_offset]); + if (naluType == H264::kIdr) { + encoded_image_._frameType = VideoFrameType::kVideoFrameKey; + break; + } + } + + encoded_image_.SetEncodedData( + EncodedImageBuffer::Create(packet.data(), packet.size())); + encoded_image_.set_size(packet.size()); + + h264_bitstream_parser_.ParseBitstream(encoded_image_); + encoded_image_.qp_ = h264_bitstream_parser_.GetLastSliceQp().value_or(-1); + + CodecSpecificInfo codecInfo; + codecInfo.codecType = kVideoCodecH264; + codecInfo.codecSpecific.H264.packetization_mode = + H264PacketizationMode::NonInterleaved; + + const auto result = + encoded_image_callback_->OnEncodedImage(encoded_image_, &codecInfo); + if (result.error != EncodedImageCallback::Result::OK) { + RTC_LOG(LS_ERROR) << "Encode m_encodedCompleteCallback failed " + << result.error; + return WEBRTC_VIDEO_CODEC_ERROR; + } + return WEBRTC_VIDEO_CODEC_OK; +} + +VideoEncoder::EncoderInfo NvidiaH264EncoderImpl::GetEncoderInfo() const { + EncoderInfo info; + info.supports_native_handle = false; + info.implementation_name = "NVIDIA H264 Encoder"; + info.scaling_settings = VideoEncoder::ScalingSettings::kOff; + info.is_hardware_accelerated = true; + info.supports_simulcast = false; + info.preferred_pixel_formats = {VideoFrameBuffer::Type::kI420}; + return info; +} + +void NvidiaH264EncoderImpl::SetRates( + const RateControlParameters& parameters) { + if (!encoder_) { + RTC_LOG(LS_WARNING) << "SetRates() while uninitialized."; + return; + } + + if (parameters.framerate_fps < 1.0) { + RTC_LOG(LS_WARNING) << "Invalid frame rate: " << parameters.framerate_fps; + return; + } + + if (parameters.bitrate.get_sum_bps() == 0) { + configuration_.SetStreamState(false); + return; + } + + codec_.maxFramerate = static_cast(parameters.framerate_fps); + codec_.maxBitrate = parameters.bitrate.GetSpatialLayerSum(0); + + configuration_.target_bps = parameters.bitrate.GetSpatialLayerSum(0); + configuration_.max_frame_rate = parameters.framerate_fps; + + if (configuration_.target_bps) { + configuration_.SetStreamState(true); + } else { + configuration_.SetStreamState(false); + } +} + +void NvidiaH264EncoderImpl::LayerConfig::SetStreamState(bool send_stream) { + if (send_stream && !sending) { + // Need a key frame if we have not sent this stream before. + key_frame_request = true; + } + sending = send_stream; +} + +} // namespace webrtc diff --git a/webrtc-sys/src/nvidia/h264_encoder_impl.h b/webrtc-sys/src/nvidia/h264_encoder_impl.h new file mode 100644 index 000000000..d1009fbb1 --- /dev/null +++ b/webrtc-sys/src/nvidia/h264_encoder_impl.h @@ -0,0 +1,98 @@ +#ifndef WEBRTC_NVIDIA_H264_ENCODER_IMPL_H_ +#define WEBRTC_NVIDIA_H264_ENCODER_IMPL_H_ + +#include + +#include +#include + +#include "NvEncoder/NvEncoder.h" +#include "NvEncoder/NvEncoderCuda.h" + +#include "absl/container/inlined_vector.h" +#include "api/transport/rtp/dependency_descriptor.h" +#include "api/video/i420_buffer.h" +#include "api/video/video_codec_constants.h" +#include "api/video_codecs/scalability_mode.h" +#include "api/video_codecs/video_encoder.h" +#include "common_video/h264/h264_bitstream_parser.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" + +namespace webrtc { + +class NvidiaH264EncoderImpl : public VideoEncoder { + public: + struct LayerConfig { + int simulcast_idx = 0; + int width = -1; + int height = -1; + bool sending = true; + bool key_frame_request = false; + float max_frame_rate = 0; + uint32_t target_bps = 0; + uint32_t max_bps = 0; + bool frame_dropping_on = false; + int key_frame_interval = 0; + int num_temporal_layers = 1; + + void SetStreamState(bool send_stream); + }; + + public: + NvidiaH264EncoderImpl(const webrtc::Environment& env, + CUcontext context, + CUmemorytype memory_type, + NV_ENC_BUFFER_FORMAT nv_format, + const SdpVideoFormat& format); + ~NvidiaH264EncoderImpl() override; + + int32_t InitEncode(const VideoCodec* codec_settings, + const Settings& settings) override; + + int32_t RegisterEncodeCompleteCallback( + EncodedImageCallback* callback) override; + + int32_t Release() override; + + int32_t Encode(const VideoFrame& frame, + const std::vector* frame_types) override; + + void SetRates(const RateControlParameters& rc_parameters) override; + + EncoderInfo GetEncoderInfo() const override; + + private: + int32_t ProcessEncodedFrame(std::vector& packet, + const ::webrtc::VideoFrame& inputFrame); + private: + EncodedImageCallback* encoded_image_callback_ = nullptr; + + std::unique_ptr encoder_; + CUcontext cu_context_; + CUmemorytype cu_memory_type_; + CUarray cu_scaled_array_; + NV_ENC_BUFFER_FORMAT nv_format_; + NV_ENC_INITIALIZE_PARAMS nv_initialize_params_; + NV_ENC_CONFIG nv_encode_config_; + GUID nv_profile_guid_; + NV_ENC_LEVEL nv_enc_level_; + + LayerConfig configuration_; + EncodedImage encoded_image_; + H264PacketizationMode packetization_mode_; + VideoCodec codec_; + void ReportInit(); + void ReportError(); + bool has_reported_init_ = false; + bool has_reported_error_ = false; + webrtc::H264BitstreamParser h264_bitstream_parser_; + const SdpVideoFormat format_; + H264Profile profile_ = H264Profile::kProfileConstrainedBaseline; + H264Level level_ = H264Level::kLevel1_b; +}; + +} // namespace webrtc + +#endif // WEBRTC_NVIDIA_H264_ENCODER_IMPL_H_ diff --git a/webrtc-sys/src/nvidia/implib/libcuda.so.init.c b/webrtc-sys/src/nvidia/implib/libcuda.so.init.c new file mode 100644 index 000000000..1e4b687d7 --- /dev/null +++ b/webrtc-sys/src/nvidia/implib/libcuda.so.init.c @@ -0,0 +1,903 @@ +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE // For RTLD_DEFAULT +#endif + +#define HAS_DLOPEN_CALLBACK 0 +#define HAS_DLSYM_CALLBACK 0 +#define NO_DLOPEN 0 +#define LAZY_LOAD 1 +#define THREAD_SAFE 1 + +#include +#include +#include +#include +#include + +#if THREAD_SAFE +#include +#endif + +// Sanity check for ARM to avoid puzzling runtime crashes +#ifdef __arm__ +# if defined __thumb__ && ! defined __THUMB_INTERWORK__ +# error "ARM trampolines need -mthumb-interwork to work in Thumb mode" +# endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define CHECK(cond, fmt, ...) do { \ + if(!(cond)) { \ + fprintf(stderr, "implib-gen: libcuda.so.1: " fmt "\n", ##__VA_ARGS__); \ + assert(0 && "Assertion in generated code"); \ + abort(); \ + } \ + } while(0) + +static void *lib_handle; +static int dlopened; + +#if ! NO_DLOPEN + +#if THREAD_SAFE + +// We need to consider two cases: +// - different threads calling intercepted APIs in parallel +// - same thread calling 2 intercepted APIs recursively +// due to dlopen calling library constructors +// (usually happens only under IMPLIB_EXPORT_SHIMS) + +// Current recursive mutex approach will deadlock +// if library constructor starts and joins a new thread +// which (directly or indirectly) calls another library function. +// Such situations should be very rare (although chances +// are higher when -DIMLIB_EXPORT_SHIMS are enabled). +// +// Similar issue is present in Glibc so hopefully it's +// not a big deal: // http://sourceware.org/bugzilla/show_bug.cgi?id=15686 +// (also google for "dlopen deadlock). + +static pthread_mutex_t mtx; +static int rec_count; + +static void init_lock(void) { + // We need recursive lock because dlopen will call library constructors + // which may call other intercepted APIs that will call load_library again. + // PTHREAD_RECURSIVE_MUTEX_INITIALIZER is not portable + // so we do it hard way. + + pthread_mutexattr_t attr; + CHECK(0 == pthread_mutexattr_init(&attr), "failed to init mutex"); + CHECK(0 == pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE), "failed to init mutex"); + + CHECK(0 == pthread_mutex_init(&mtx, &attr), "failed to init mutex"); +} + +static int lock(void) { + static pthread_once_t once = PTHREAD_ONCE_INIT; + CHECK(0 == pthread_once(&once, init_lock), "failed to init lock"); + + CHECK(0 == pthread_mutex_lock(&mtx), "failed to lock mutex"); + + return 0 == __sync_fetch_and_add(&rec_count, 1); +} + +static void unlock(void) { + __sync_fetch_and_add(&rec_count, -1); + CHECK(0 == pthread_mutex_unlock(&mtx), "failed to unlock mutex"); +} +#else +static int lock(void) { + return 1; +} +static void unlock(void) {} +#endif + +static int load_library(void) { + int publish = lock(); + + if (lib_handle) { + unlock(); + return publish; + } + +#if HAS_DLOPEN_CALLBACK + extern void *(const char *lib_name); + lib_handle = ("libcuda.so.1"); + CHECK(lib_handle, "failed to load library 'libcuda.so.1' via callback ''"); +#else + lib_handle = dlopen("libcuda.so.1", RTLD_LAZY | RTLD_GLOBAL); + CHECK(lib_handle, "failed to load library 'libcuda.so.1' via dlopen: %s", dlerror()); +#endif + + // With (non-default) IMPLIB_EXPORT_SHIMS we may call dlopen more than once + // so dlclose it if we are not the first ones + if (__sync_val_compare_and_swap(&dlopened, 0, 1)) { + dlclose(lib_handle); + } + + unlock(); + + return publish; +} + +// Run dtor as late as possible in case library functions are +// called in other global dtors +// FIXME: this may crash if one thread is calling into library +// while some other thread executes exit(). It's no clear +// how to fix this besides simply NOT dlclosing library at all. +static void __attribute__((destructor(101))) unload_lib(void) { + if (dlopened) { + dlclose(lib_handle); + lib_handle = 0; + dlopened = 0; + } +} +#endif + +#if ! NO_DLOPEN && ! LAZY_LOAD +static void __attribute__((constructor(101))) load_lib(void) { + load_library(); +} +#endif + +// TODO: convert to single 0-separated string +static const char *const sym_names[] = { + "cuArray3DCreate", + "cuArray3DCreate_v2", + "cuArray3DGetDescriptor", + "cuArray3DGetDescriptor_v2", + "cuArrayCreate", + "cuArrayCreate_v2", + "cuArrayDestroy", + "cuArrayGetDescriptor", + "cuArrayGetDescriptor_v2", + "cuArrayGetMemoryRequirements", + "cuArrayGetPlane", + "cuArrayGetSparseProperties", + "cuCheckpointProcessCheckpoint", + "cuCheckpointProcessGetRestoreThreadId", + "cuCheckpointProcessGetState", + "cuCheckpointProcessLock", + "cuCheckpointProcessRestore", + "cuCheckpointProcessUnlock", + "cuCoredumpGetAttribute", + "cuCoredumpGetAttributeGlobal", + "cuCoredumpSetAttribute", + "cuCoredumpSetAttributeGlobal", + "cuCtxAttach", + "cuCtxCreate", + "cuCtxCreate_v2", + "cuCtxCreate_v3", + "cuCtxCreate_v4", + "cuCtxDestroy", + "cuCtxDestroy_v2", + "cuCtxDetach", + "cuCtxDisablePeerAccess", + "cuCtxEnablePeerAccess", + "cuCtxFromGreenCtx", + "cuCtxGetApiVersion", + "cuCtxGetCacheConfig", + "cuCtxGetCurrent", + "cuCtxGetDevResource", + "cuCtxGetDevice", + "cuCtxGetExecAffinity", + "cuCtxGetFlags", + "cuCtxGetId", + "cuCtxGetLimit", + "cuCtxGetSharedMemConfig", + "cuCtxGetStreamPriorityRange", + "cuCtxPopCurrent", + "cuCtxPopCurrent_v2", + "cuCtxPushCurrent", + "cuCtxPushCurrent_v2", + "cuCtxRecordEvent", + "cuCtxResetPersistingL2Cache", + "cuCtxSetCacheConfig", + "cuCtxSetCurrent", + "cuCtxSetFlags", + "cuCtxSetLimit", + "cuCtxSetSharedMemConfig", + "cuCtxSynchronize", + "cuCtxWaitEvent", + "cuDestroyExternalMemory", + "cuDestroyExternalSemaphore", + "cuDevResourceGenerateDesc", + "cuDevSmResourceSplitByCount", + "cuDeviceCanAccessPeer", + "cuDeviceComputeCapability", + "cuDeviceGet", + "cuDeviceGetAttribute", + "cuDeviceGetByPCIBusId", + "cuDeviceGetCount", + "cuDeviceGetDefaultMemPool", + "cuDeviceGetDevResource", + "cuDeviceGetExecAffinitySupport", + "cuDeviceGetGraphMemAttribute", + "cuDeviceGetLuid", + "cuDeviceGetMemPool", + "cuDeviceGetName", + "cuDeviceGetNvSciSyncAttributes", + "cuDeviceGetP2PAttribute", + "cuDeviceGetPCIBusId", + "cuDeviceGetProperties", + "cuDeviceGetTexture1DLinearMaxWidth", + "cuDeviceGetUuid", + "cuDeviceGetUuid_v2", + "cuDeviceGraphMemTrim", + "cuDevicePrimaryCtxGetState", + "cuDevicePrimaryCtxRelease", + "cuDevicePrimaryCtxRelease_v2", + "cuDevicePrimaryCtxReset", + "cuDevicePrimaryCtxReset_v2", + "cuDevicePrimaryCtxRetain", + "cuDevicePrimaryCtxSetFlags", + "cuDevicePrimaryCtxSetFlags_v2", + "cuDeviceRegisterAsyncNotification", + "cuDeviceSetGraphMemAttribute", + "cuDeviceSetMemPool", + "cuDeviceTotalMem", + "cuDeviceTotalMem_v2", + "cuDeviceUnregisterAsyncNotification", + "cuDriverGetVersion", + "cuEGLApiInit", + "cuEGLStreamConsumerAcquireFrame", + "cuEGLStreamConsumerConnect", + "cuEGLStreamConsumerConnectWithFlags", + "cuEGLStreamConsumerDisconnect", + "cuEGLStreamConsumerReleaseFrame", + "cuEGLStreamProducerConnect", + "cuEGLStreamProducerDisconnect", + "cuEGLStreamProducerPresentFrame", + "cuEGLStreamProducerReturnFrame", + "cuEventCreate", + "cuEventDestroy", + "cuEventDestroy_v2", + "cuEventElapsedTime", + "cuEventElapsedTime_v2", + "cuEventQuery", + "cuEventRecord", + "cuEventRecordWithFlags", + "cuEventRecordWithFlags_ptsz", + "cuEventRecord_ptsz", + "cuEventSynchronize", + "cuExternalMemoryGetMappedBuffer", + "cuExternalMemoryGetMappedMipmappedArray", + "cuFlushGPUDirectRDMAWrites", + "cuFuncGetAttribute", + "cuFuncGetModule", + "cuFuncGetName", + "cuFuncGetParamInfo", + "cuFuncIsLoaded", + "cuFuncLoad", + "cuFuncSetAttribute", + "cuFuncSetBlockShape", + "cuFuncSetCacheConfig", + "cuFuncSetSharedMemConfig", + "cuFuncSetSharedSize", + "cuGLCtxCreate", + "cuGLCtxCreate_v2", + "cuGLGetDevices", + "cuGLGetDevices_v2", + "cuGLInit", + "cuGLMapBufferObject", + "cuGLMapBufferObjectAsync", + "cuGLMapBufferObjectAsync_v2", + "cuGLMapBufferObjectAsync_v2_ptsz", + "cuGLMapBufferObject_v2", + "cuGLMapBufferObject_v2_ptds", + "cuGLRegisterBufferObject", + "cuGLSetBufferObjectMapFlags", + "cuGLUnmapBufferObject", + "cuGLUnmapBufferObjectAsync", + "cuGLUnregisterBufferObject", + "cuGetErrorName", + "cuGetErrorString", + "cuGetExportTable", + "cuGetProcAddress", + "cuGetProcAddress_v2", + "cuGraphAddBatchMemOpNode", + "cuGraphAddChildGraphNode", + "cuGraphAddDependencies", + "cuGraphAddDependencies_v2", + "cuGraphAddEmptyNode", + "cuGraphAddEventRecordNode", + "cuGraphAddEventWaitNode", + "cuGraphAddExternalSemaphoresSignalNode", + "cuGraphAddExternalSemaphoresWaitNode", + "cuGraphAddHostNode", + "cuGraphAddKernelNode", + "cuGraphAddKernelNode_v2", + "cuGraphAddMemAllocNode", + "cuGraphAddMemFreeNode", + "cuGraphAddMemcpyNode", + "cuGraphAddMemsetNode", + "cuGraphAddNode", + "cuGraphAddNode_v2", + "cuGraphBatchMemOpNodeGetParams", + "cuGraphBatchMemOpNodeSetParams", + "cuGraphChildGraphNodeGetGraph", + "cuGraphClone", + "cuGraphConditionalHandleCreate", + "cuGraphCreate", + "cuGraphDebugDotPrint", + "cuGraphDestroy", + "cuGraphDestroyNode", + "cuGraphEventRecordNodeGetEvent", + "cuGraphEventRecordNodeSetEvent", + "cuGraphEventWaitNodeGetEvent", + "cuGraphEventWaitNodeSetEvent", + "cuGraphExecBatchMemOpNodeSetParams", + "cuGraphExecChildGraphNodeSetParams", + "cuGraphExecDestroy", + "cuGraphExecEventRecordNodeSetEvent", + "cuGraphExecEventWaitNodeSetEvent", + "cuGraphExecExternalSemaphoresSignalNodeSetParams", + "cuGraphExecExternalSemaphoresWaitNodeSetParams", + "cuGraphExecGetFlags", + "cuGraphExecHostNodeSetParams", + "cuGraphExecKernelNodeSetParams", + "cuGraphExecKernelNodeSetParams_v2", + "cuGraphExecMemcpyNodeSetParams", + "cuGraphExecMemsetNodeSetParams", + "cuGraphExecNodeSetParams", + "cuGraphExecUpdate", + "cuGraphExecUpdate_v2", + "cuGraphExternalSemaphoresSignalNodeGetParams", + "cuGraphExternalSemaphoresSignalNodeSetParams", + "cuGraphExternalSemaphoresWaitNodeGetParams", + "cuGraphExternalSemaphoresWaitNodeSetParams", + "cuGraphGetEdges", + "cuGraphGetEdges_v2", + "cuGraphGetNodes", + "cuGraphGetRootNodes", + "cuGraphHostNodeGetParams", + "cuGraphHostNodeSetParams", + "cuGraphInstantiate", + "cuGraphInstantiateWithFlags", + "cuGraphInstantiateWithParams", + "cuGraphInstantiateWithParams_ptsz", + "cuGraphInstantiate_v2", + "cuGraphKernelNodeCopyAttributes", + "cuGraphKernelNodeGetAttribute", + "cuGraphKernelNodeGetParams", + "cuGraphKernelNodeGetParams_v2", + "cuGraphKernelNodeSetAttribute", + "cuGraphKernelNodeSetParams", + "cuGraphKernelNodeSetParams_v2", + "cuGraphLaunch", + "cuGraphLaunch_ptsz", + "cuGraphMemAllocNodeGetParams", + "cuGraphMemFreeNodeGetParams", + "cuGraphMemcpyNodeGetParams", + "cuGraphMemcpyNodeSetParams", + "cuGraphMemsetNodeGetParams", + "cuGraphMemsetNodeSetParams", + "cuGraphNodeFindInClone", + "cuGraphNodeGetDependencies", + "cuGraphNodeGetDependencies_v2", + "cuGraphNodeGetDependentNodes", + "cuGraphNodeGetDependentNodes_v2", + "cuGraphNodeGetEnabled", + "cuGraphNodeGetType", + "cuGraphNodeSetEnabled", + "cuGraphNodeSetParams", + "cuGraphReleaseUserObject", + "cuGraphRemoveDependencies", + "cuGraphRemoveDependencies_v2", + "cuGraphRetainUserObject", + "cuGraphUpload", + "cuGraphUpload_ptsz", + "cuGraphicsEGLRegisterImage", + "cuGraphicsGLRegisterBuffer", + "cuGraphicsGLRegisterImage", + "cuGraphicsMapResources", + "cuGraphicsMapResources_ptsz", + "cuGraphicsResourceGetMappedEglFrame", + "cuGraphicsResourceGetMappedMipmappedArray", + "cuGraphicsResourceGetMappedPointer", + "cuGraphicsResourceGetMappedPointer_v2", + "cuGraphicsResourceSetMapFlags", + "cuGraphicsResourceSetMapFlags_v2", + "cuGraphicsSubResourceGetMappedArray", + "cuGraphicsUnmapResources", + "cuGraphicsUnmapResources_ptsz", + "cuGraphicsUnregisterResource", + "cuGraphicsVDPAURegisterOutputSurface", + "cuGraphicsVDPAURegisterVideoSurface", + "cuGreenCtxCreate", + "cuGreenCtxDestroy", + "cuGreenCtxGetDevResource", + "cuGreenCtxRecordEvent", + "cuGreenCtxStreamCreate", + "cuGreenCtxWaitEvent", + "cuImportExternalMemory", + "cuImportExternalSemaphore", + "cuInit", + "cuIpcCloseMemHandle", + "cuIpcGetEventHandle", + "cuIpcGetMemHandle", + "cuIpcOpenEventHandle", + "cuIpcOpenMemHandle", + "cuIpcOpenMemHandle_v2", + "cuKernelGetAttribute", + "cuKernelGetFunction", + "cuKernelGetLibrary", + "cuKernelGetName", + "cuKernelGetParamInfo", + "cuKernelSetAttribute", + "cuKernelSetCacheConfig", + "cuLaunch", + "cuLaunchCooperativeKernel", + "cuLaunchCooperativeKernelMultiDevice", + "cuLaunchCooperativeKernel_ptsz", + "cuLaunchGrid", + "cuLaunchGridAsync", + "cuLaunchHostFunc", + "cuLaunchHostFunc_ptsz", + "cuLaunchKernel", + "cuLaunchKernelEx", + "cuLaunchKernelEx_ptsz", + "cuLaunchKernel_ptsz", + "cuLibraryEnumerateKernels", + "cuLibraryGetGlobal", + "cuLibraryGetKernel", + "cuLibraryGetKernelCount", + "cuLibraryGetManaged", + "cuLibraryGetModule", + "cuLibraryGetUnifiedFunction", + "cuLibraryLoadData", + "cuLibraryLoadFromFile", + "cuLibraryUnload", + "cuLinkAddData", + "cuLinkAddData_v2", + "cuLinkAddFile", + "cuLinkAddFile_v2", + "cuLinkComplete", + "cuLinkCreate", + "cuLinkCreate_v2", + "cuLinkDestroy", + "cuMemAddressFree", + "cuMemAddressReserve", + "cuMemAdvise", + "cuMemAdvise_v2", + "cuMemAlloc", + "cuMemAllocAsync", + "cuMemAllocAsync_ptsz", + "cuMemAllocFromPoolAsync", + "cuMemAllocFromPoolAsync_ptsz", + "cuMemAllocHost", + "cuMemAllocHost_v2", + "cuMemAllocManaged", + "cuMemAllocPitch", + "cuMemAllocPitch_v2", + "cuMemAlloc_v2", + "cuMemBatchDecompressAsync", + "cuMemBatchDecompressAsync_ptsz", + "cuMemCreate", + "cuMemExportToShareableHandle", + "cuMemFree", + "cuMemFreeAsync", + "cuMemFreeAsync_ptsz", + "cuMemFreeHost", + "cuMemFree_v2", + "cuMemGetAccess", + "cuMemGetAddressRange", + "cuMemGetAddressRange_v2", + "cuMemGetAllocationGranularity", + "cuMemGetAllocationPropertiesFromHandle", + "cuMemGetAttribute", + "cuMemGetAttribute_v2", + "cuMemGetHandleForAddressRange", + "cuMemGetInfo", + "cuMemGetInfo_v2", + "cuMemHostAlloc", + "cuMemHostGetDevicePointer", + "cuMemHostGetDevicePointer_v2", + "cuMemHostGetFlags", + "cuMemHostRegister", + "cuMemHostRegister_v2", + "cuMemHostUnregister", + "cuMemImportFromShareableHandle", + "cuMemMap", + "cuMemMapArrayAsync", + "cuMemMapArrayAsync_ptsz", + "cuMemPoolCreate", + "cuMemPoolDestroy", + "cuMemPoolExportPointer", + "cuMemPoolExportToShareableHandle", + "cuMemPoolGetAccess", + "cuMemPoolGetAttribute", + "cuMemPoolImportFromShareableHandle", + "cuMemPoolImportPointer", + "cuMemPoolSetAccess", + "cuMemPoolSetAttribute", + "cuMemPoolTrimTo", + "cuMemPrefetchAsync", + "cuMemPrefetchAsync_ptsz", + "cuMemPrefetchAsync_v2", + "cuMemPrefetchAsync_v2_ptsz", + "cuMemRangeGetAttribute", + "cuMemRangeGetAttributes", + "cuMemRelease", + "cuMemRetainAllocationHandle", + "cuMemSetAccess", + "cuMemUnmap", + "cuMemcpy", + "cuMemcpy2D", + "cuMemcpy2DAsync", + "cuMemcpy2DAsync_v2", + "cuMemcpy2DAsync_v2_ptsz", + "cuMemcpy2DUnaligned", + "cuMemcpy2DUnaligned_v2", + "cuMemcpy2DUnaligned_v2_ptds", + "cuMemcpy2D_v2", + "cuMemcpy2D_v2_ptds", + "cuMemcpy3D", + "cuMemcpy3DAsync", + "cuMemcpy3DAsync_v2", + "cuMemcpy3DAsync_v2_ptsz", + "cuMemcpy3DBatchAsync", + "cuMemcpy3DBatchAsync_ptsz", + "cuMemcpy3DPeer", + "cuMemcpy3DPeerAsync", + "cuMemcpy3DPeerAsync_ptsz", + "cuMemcpy3DPeer_ptds", + "cuMemcpy3D_v2", + "cuMemcpy3D_v2_ptds", + "cuMemcpyAsync", + "cuMemcpyAsync_ptsz", + "cuMemcpyAtoA", + "cuMemcpyAtoA_v2", + "cuMemcpyAtoA_v2_ptds", + "cuMemcpyAtoD", + "cuMemcpyAtoD_v2", + "cuMemcpyAtoD_v2_ptds", + "cuMemcpyAtoH", + "cuMemcpyAtoHAsync", + "cuMemcpyAtoHAsync_v2", + "cuMemcpyAtoHAsync_v2_ptsz", + "cuMemcpyAtoH_v2", + "cuMemcpyAtoH_v2_ptds", + "cuMemcpyBatchAsync", + "cuMemcpyBatchAsync_ptsz", + "cuMemcpyDtoA", + "cuMemcpyDtoA_v2", + "cuMemcpyDtoA_v2_ptds", + "cuMemcpyDtoD", + "cuMemcpyDtoDAsync", + "cuMemcpyDtoDAsync_v2", + "cuMemcpyDtoDAsync_v2_ptsz", + "cuMemcpyDtoD_v2", + "cuMemcpyDtoD_v2_ptds", + "cuMemcpyDtoH", + "cuMemcpyDtoHAsync", + "cuMemcpyDtoHAsync_v2", + "cuMemcpyDtoHAsync_v2_ptsz", + "cuMemcpyDtoH_v2", + "cuMemcpyDtoH_v2_ptds", + "cuMemcpyHtoA", + "cuMemcpyHtoAAsync", + "cuMemcpyHtoAAsync_v2", + "cuMemcpyHtoAAsync_v2_ptsz", + "cuMemcpyHtoA_v2", + "cuMemcpyHtoA_v2_ptds", + "cuMemcpyHtoD", + "cuMemcpyHtoDAsync", + "cuMemcpyHtoDAsync_v2", + "cuMemcpyHtoDAsync_v2_ptsz", + "cuMemcpyHtoD_v2", + "cuMemcpyHtoD_v2_ptds", + "cuMemcpyPeer", + "cuMemcpyPeerAsync", + "cuMemcpyPeerAsync_ptsz", + "cuMemcpyPeer_ptds", + "cuMemcpy_ptds", + "cuMemsetD16", + "cuMemsetD16Async", + "cuMemsetD16Async_ptsz", + "cuMemsetD16_v2", + "cuMemsetD16_v2_ptds", + "cuMemsetD2D16", + "cuMemsetD2D16Async", + "cuMemsetD2D16Async_ptsz", + "cuMemsetD2D16_v2", + "cuMemsetD2D16_v2_ptds", + "cuMemsetD2D32", + "cuMemsetD2D32Async", + "cuMemsetD2D32Async_ptsz", + "cuMemsetD2D32_v2", + "cuMemsetD2D32_v2_ptds", + "cuMemsetD2D8", + "cuMemsetD2D8Async", + "cuMemsetD2D8Async_ptsz", + "cuMemsetD2D8_v2", + "cuMemsetD2D8_v2_ptds", + "cuMemsetD32", + "cuMemsetD32Async", + "cuMemsetD32Async_ptsz", + "cuMemsetD32_v2", + "cuMemsetD32_v2_ptds", + "cuMemsetD8", + "cuMemsetD8Async", + "cuMemsetD8Async_ptsz", + "cuMemsetD8_v2", + "cuMemsetD8_v2_ptds", + "cuMipmappedArrayCreate", + "cuMipmappedArrayDestroy", + "cuMipmappedArrayGetLevel", + "cuMipmappedArrayGetMemoryRequirements", + "cuMipmappedArrayGetSparseProperties", + "cuModuleEnumerateFunctions", + "cuModuleGetFunction", + "cuModuleGetFunctionCount", + "cuModuleGetGlobal", + "cuModuleGetGlobal_v2", + "cuModuleGetLoadingMode", + "cuModuleGetSurfRef", + "cuModuleGetTexRef", + "cuModuleLoad", + "cuModuleLoadData", + "cuModuleLoadDataEx", + "cuModuleLoadFatBinary", + "cuModuleUnload", + "cuMulticastAddDevice", + "cuMulticastBindAddr", + "cuMulticastBindMem", + "cuMulticastCreate", + "cuMulticastGetGranularity", + "cuMulticastUnbind", + "cuOccupancyAvailableDynamicSMemPerBlock", + "cuOccupancyMaxActiveBlocksPerMultiprocessor", + "cuOccupancyMaxActiveBlocksPerMultiprocessorWithFlags", + "cuOccupancyMaxActiveClusters", + "cuOccupancyMaxPotentialBlockSize", + "cuOccupancyMaxPotentialBlockSizeWithFlags", + "cuOccupancyMaxPotentialClusterSize", + "cuParamSetSize", + "cuParamSetTexRef", + "cuParamSetf", + "cuParamSeti", + "cuParamSetv", + "cuPointerGetAttribute", + "cuPointerGetAttributes", + "cuPointerSetAttribute", + "cuProfilerInitialize", + "cuProfilerStart", + "cuProfilerStop", + "cuSignalExternalSemaphoresAsync", + "cuSignalExternalSemaphoresAsync_ptsz", + "cuStreamAddCallback", + "cuStreamAddCallback_ptsz", + "cuStreamAttachMemAsync", + "cuStreamAttachMemAsync_ptsz", + "cuStreamBatchMemOp", + "cuStreamBatchMemOp_ptsz", + "cuStreamBatchMemOp_v2", + "cuStreamBatchMemOp_v2_ptsz", + "cuStreamBeginCapture", + "cuStreamBeginCaptureToGraph", + "cuStreamBeginCaptureToGraph_ptsz", + "cuStreamBeginCapture_ptsz", + "cuStreamBeginCapture_v2", + "cuStreamBeginCapture_v2_ptsz", + "cuStreamCopyAttributes", + "cuStreamCopyAttributes_ptsz", + "cuStreamCreate", + "cuStreamCreateWithPriority", + "cuStreamDestroy", + "cuStreamDestroy_v2", + "cuStreamEndCapture", + "cuStreamEndCapture_ptsz", + "cuStreamGetAttribute", + "cuStreamGetAttribute_ptsz", + "cuStreamGetCaptureInfo", + "cuStreamGetCaptureInfo_ptsz", + "cuStreamGetCaptureInfo_v2", + "cuStreamGetCaptureInfo_v2_ptsz", + "cuStreamGetCaptureInfo_v3", + "cuStreamGetCaptureInfo_v3_ptsz", + "cuStreamGetCtx", + "cuStreamGetCtx_ptsz", + "cuStreamGetCtx_v2", + "cuStreamGetCtx_v2_ptsz", + "cuStreamGetDevice", + "cuStreamGetDevice_ptsz", + "cuStreamGetFlags", + "cuStreamGetFlags_ptsz", + "cuStreamGetGreenCtx", + "cuStreamGetId", + "cuStreamGetId_ptsz", + "cuStreamGetPriority", + "cuStreamGetPriority_ptsz", + "cuStreamIsCapturing", + "cuStreamIsCapturing_ptsz", + "cuStreamQuery", + "cuStreamQuery_ptsz", + "cuStreamSetAttribute", + "cuStreamSetAttribute_ptsz", + "cuStreamSynchronize", + "cuStreamSynchronize_ptsz", + "cuStreamUpdateCaptureDependencies", + "cuStreamUpdateCaptureDependencies_ptsz", + "cuStreamUpdateCaptureDependencies_v2", + "cuStreamUpdateCaptureDependencies_v2_ptsz", + "cuStreamWaitEvent", + "cuStreamWaitEvent_ptsz", + "cuStreamWaitValue32", + "cuStreamWaitValue32_ptsz", + "cuStreamWaitValue32_v2", + "cuStreamWaitValue32_v2_ptsz", + "cuStreamWaitValue64", + "cuStreamWaitValue64_ptsz", + "cuStreamWaitValue64_v2", + "cuStreamWaitValue64_v2_ptsz", + "cuStreamWriteValue32", + "cuStreamWriteValue32_ptsz", + "cuStreamWriteValue32_v2", + "cuStreamWriteValue32_v2_ptsz", + "cuStreamWriteValue64", + "cuStreamWriteValue64_ptsz", + "cuStreamWriteValue64_v2", + "cuStreamWriteValue64_v2_ptsz", + "cuSurfObjectCreate", + "cuSurfObjectDestroy", + "cuSurfObjectGetResourceDesc", + "cuSurfRefGetArray", + "cuSurfRefSetArray", + "cuTensorMapEncodeIm2col", + "cuTensorMapEncodeIm2colWide", + "cuTensorMapEncodeTiled", + "cuTensorMapReplaceAddress", + "cuTexObjectCreate", + "cuTexObjectDestroy", + "cuTexObjectGetResourceDesc", + "cuTexObjectGetResourceViewDesc", + "cuTexObjectGetTextureDesc", + "cuTexRefCreate", + "cuTexRefDestroy", + "cuTexRefGetAddress", + "cuTexRefGetAddressMode", + "cuTexRefGetAddress_v2", + "cuTexRefGetArray", + "cuTexRefGetBorderColor", + "cuTexRefGetFilterMode", + "cuTexRefGetFlags", + "cuTexRefGetFormat", + "cuTexRefGetMaxAnisotropy", + "cuTexRefGetMipmapFilterMode", + "cuTexRefGetMipmapLevelBias", + "cuTexRefGetMipmapLevelClamp", + "cuTexRefGetMipmappedArray", + "cuTexRefSetAddress", + "cuTexRefSetAddress2D", + "cuTexRefSetAddress2D_v2", + "cuTexRefSetAddress2D_v3", + "cuTexRefSetAddressMode", + "cuTexRefSetAddress_v2", + "cuTexRefSetArray", + "cuTexRefSetBorderColor", + "cuTexRefSetFilterMode", + "cuTexRefSetFlags", + "cuTexRefSetFormat", + "cuTexRefSetMaxAnisotropy", + "cuTexRefSetMipmapFilterMode", + "cuTexRefSetMipmapLevelBias", + "cuTexRefSetMipmapLevelClamp", + "cuTexRefSetMipmappedArray", + "cuThreadExchangeStreamCaptureMode", + "cuUserObjectCreate", + "cuUserObjectRelease", + "cuUserObjectRetain", + "cuVDPAUCtxCreate", + "cuVDPAUCtxCreate_v2", + "cuVDPAUGetDevice", + "cuWaitExternalSemaphoresAsync", + "cuWaitExternalSemaphoresAsync_ptsz", + "cudbgApiAttach", + "cudbgApiDetach", + "cudbgApiInit", + "cudbgGetAPI", + "cudbgGetAPIVersion", + "cudbgMain", + "cudbgReportDriverApiError", + "cudbgReportDriverInternalError", + 0 +}; + +#define SYM_COUNT (sizeof(sym_names)/sizeof(sym_names[0]) - 1) + +extern void *_libcuda_so_tramp_table[]; + +// Can be sped up by manually parsing library symtab... +void *_libcuda_so_tramp_resolve(size_t i) { + assert(i < SYM_COUNT); + + int publish = 1; + + void *h = 0; +#if NO_DLOPEN + // Library with implementations must have already been loaded. + if (lib_handle) { + // User has specified loaded library + h = lib_handle; + } else { + // User hasn't provided us the loaded library so search the global namespace. +# ifndef IMPLIB_EXPORT_SHIMS + // If shim symbols are hidden we should search + // for first available definition of symbol in library list + h = RTLD_DEFAULT; +# else + // Otherwise look for next available definition + h = RTLD_NEXT; +# endif + } +#else + publish = load_library(); + h = lib_handle; + CHECK(h, "failed to resolve symbol '%s', library failed to load", sym_names[i]); +#endif + + void *addr; +#if HAS_DLSYM_CALLBACK + extern void *(void *handle, const char *sym_name); + addr = (h, sym_names[i]); + CHECK(addr, "failed to resolve symbol '%s' via callback ", sym_names[i]); +#else + // Dlsym is thread-safe so don't need to protect it. + addr = dlsym(h, sym_names[i]); + CHECK(addr, "failed to resolve symbol '%s' via dlsym: %s", sym_names[i], dlerror()); +#endif + + if (publish) { + // Use atomic to please Tsan and ensure that preceeding writes + // in library ctors have been delivered before publishing address + (void)__sync_val_compare_and_swap(&_libcuda_so_tramp_table[i], 0, addr); + } + + return addr; +} + +// Below APIs are not thread-safe +// and it's not clear how make them such +// (we can not know if some other thread is +// currently executing library code). + +// Helper for user to resolve all symbols +void _libcuda_so_tramp_resolve_all(void) { + size_t i; + for(i = 0; i < SYM_COUNT; ++i) + _libcuda_so_tramp_resolve(i); +} + +// Allows user to specify manually loaded implementation library. +void _libcuda_so_tramp_set_handle(void *handle) { + // TODO: call unload_lib ? + lib_handle = handle; + dlopened = 0; +} + +// Resets all resolved symbols. This is needed in case +// client code wants to reload interposed library multiple times. +void _libcuda_so_tramp_reset(void) { + // TODO: call unload_lib ? + memset(_libcuda_so_tramp_table, 0, SYM_COUNT * sizeof(_libcuda_so_tramp_table[0])); + lib_handle = 0; + dlopened = 0; +} + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/webrtc-sys/src/nvidia/implib/libcuda.so.tramp.S b/webrtc-sys/src/nvidia/implib/libcuda.so.tramp.S new file mode 100644 index 000000000..103d78fed --- /dev/null +++ b/webrtc-sys/src/nvidia/implib/libcuda.so.tramp.S @@ -0,0 +1,22564 @@ +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .section .note.GNU-stack,"",@progbits + + .data + + .globl _libcuda_so_tramp_table + .hidden _libcuda_so_tramp_table + .align 8 +_libcuda_so_tramp_table: + .zero 5280 + + .text + + .globl _libcuda_so_tramp_resolve + .hidden _libcuda_so_tramp_resolve + + .globl _libcuda_so_save_regs_and_resolve + .hidden _libcuda_so_save_regs_and_resolve + .type _libcuda_so_save_regs_and_resolve, %function +_libcuda_so_save_regs_and_resolve: + .cfi_startproc + +#define PUSH_REG(reg) pushq %reg ; .cfi_adjust_cfa_offset 8; .cfi_rel_offset reg, 0 +#define POP_REG(reg) popq %reg ; .cfi_adjust_cfa_offset -8; .cfi_restore reg + +#define DEC_STACK(d) subq $d, %rsp; .cfi_adjust_cfa_offset d +#define INC_STACK(d) addq $d, %rsp; .cfi_adjust_cfa_offset -d + +#define PUSH_MMX_REG(reg) DEC_STACK(8); movq %reg, (%rsp); .cfi_rel_offset reg, 0 +#define POP_MMX_REG(reg) movq (%rsp), %reg; .cfi_restore reg; INC_STACK(8) + +#define PUSH_XMM_REG(reg) DEC_STACK(16); movdqa %reg, (%rsp); .cfi_rel_offset reg, 0 +#define POP_XMM_REG(reg) movdqa (%rsp), %reg; .cfi_restore reg; INC_STACK(16) + +// TODO: cfi_offset/cfi_restore +#define PUSH_YMM_REG(reg) DEC_STACK(32); vmovdqu %reg, (%rsp) +#define POP_YMM_REG(reg) vmovdqu (%rsp), %reg; INC_STACK(32) + +// TODO: cfi_offset/cfi_restore +#define PUSH_ZMM_REG(reg) DEC_STACK(64); vmovdqu32 %reg, (%rsp) +#define POP_ZMM_REG(reg) vmovdqu32 (%rsp), %reg; INC_STACK(64) + + // Slow path which calls dlsym, taken only on first call. + // All registers are stored to handle arbitrary calling conventions + // (except x87 FPU registers which do not have to be preserved). + // For Dwarf directives, read https://www.imperialviolet.org/2017/01/18/cfi.html. + + .cfi_def_cfa_offset 8 // Return address + + PUSH_REG(rdi) // 16 + mov 0x10(%rsp), %rdi + PUSH_REG(rbx) + PUSH_REG(rbx) // 16 + PUSH_REG(rcx) + PUSH_REG(rdx) // 16 + PUSH_REG(rbp) + PUSH_REG(rsi) // 16 + PUSH_REG(r8) + PUSH_REG(r9) // 16 + PUSH_REG(r10) + PUSH_REG(r11) // 16 + PUSH_REG(r12) + PUSH_REG(r13) // 16 + PUSH_REG(r14) + PUSH_REG(r15) // 16 + + // Maybe use cpuid instead of macro to detect current vector size... +#ifdef __AVX512F__ + PUSH_ZMM_REG(zmm0) + PUSH_ZMM_REG(zmm1) + PUSH_ZMM_REG(zmm2) + PUSH_ZMM_REG(zmm3) + PUSH_ZMM_REG(zmm4) + PUSH_ZMM_REG(zmm5) + PUSH_ZMM_REG(zmm6) + PUSH_ZMM_REG(zmm7) +#elif defined __AVX__ + PUSH_YMM_REG(ymm0) + PUSH_YMM_REG(ymm1) + PUSH_YMM_REG(ymm2) + PUSH_YMM_REG(ymm3) + PUSH_YMM_REG(ymm4) + PUSH_YMM_REG(ymm5) + PUSH_YMM_REG(ymm6) + PUSH_YMM_REG(ymm7) +#elif defined __SSE__ + PUSH_XMM_REG(xmm0) + PUSH_XMM_REG(xmm1) + PUSH_XMM_REG(xmm2) + PUSH_XMM_REG(xmm3) + PUSH_XMM_REG(xmm4) + PUSH_XMM_REG(xmm5) + PUSH_XMM_REG(xmm6) + PUSH_XMM_REG(xmm7) +#endif + + // MMX registers are not used to pass arguments so we do not save them + + // Stack is just 8-byte aligned but callee will re-align to 16 + call _libcuda_so_tramp_resolve + +#ifdef __AVX512F__ + POP_ZMM_REG(zmm7) + POP_ZMM_REG(zmm6) + POP_ZMM_REG(zmm5) + POP_ZMM_REG(zmm4) + POP_ZMM_REG(zmm3) + POP_ZMM_REG(zmm2) + POP_ZMM_REG(zmm1) + POP_ZMM_REG(zmm0) // 16 +#elif defined __AVX__ + POP_YMM_REG(ymm7) + POP_YMM_REG(ymm6) + POP_YMM_REG(ymm5) + POP_YMM_REG(ymm4) + POP_YMM_REG(ymm3) + POP_YMM_REG(ymm2) + POP_YMM_REG(ymm1) + POP_YMM_REG(ymm0) // 16 +#elif defined __SSE__ + POP_XMM_REG(xmm7) + POP_XMM_REG(xmm6) + POP_XMM_REG(xmm5) + POP_XMM_REG(xmm4) + POP_XMM_REG(xmm3) + POP_XMM_REG(xmm2) + POP_XMM_REG(xmm1) + POP_XMM_REG(xmm0) // 16 +#endif + + POP_REG(r15) + POP_REG(r14) // 16 + POP_REG(r13) + POP_REG(r12) // 16 + POP_REG(r11) + POP_REG(r10) // 16 + POP_REG(r9) + POP_REG(r8) // 16 + POP_REG(rsi) + POP_REG(rbp) // 16 + POP_REG(rdx) + POP_REG(rcx) // 16 + POP_REG(rbx) + POP_REG(rbx) // 16 + POP_REG(rdi) + + ret + + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuArray3DCreate + .p2align 4 + .type cuArray3DCreate, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuArray3DCreate +#endif +cuArray3DCreate: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+0(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+0(%rip) +2: + pushq $0 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuArray3DCreate_v2 + .p2align 4 + .type cuArray3DCreate_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuArray3DCreate_v2 +#endif +cuArray3DCreate_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+8(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+8(%rip) +2: + pushq $1 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuArray3DGetDescriptor + .p2align 4 + .type cuArray3DGetDescriptor, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuArray3DGetDescriptor +#endif +cuArray3DGetDescriptor: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+16(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+16(%rip) +2: + pushq $2 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuArray3DGetDescriptor_v2 + .p2align 4 + .type cuArray3DGetDescriptor_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuArray3DGetDescriptor_v2 +#endif +cuArray3DGetDescriptor_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+24(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+24(%rip) +2: + pushq $3 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuArrayCreate + .p2align 4 + .type cuArrayCreate, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuArrayCreate +#endif +cuArrayCreate: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+32(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+32(%rip) +2: + pushq $4 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuArrayCreate_v2 + .p2align 4 + .type cuArrayCreate_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuArrayCreate_v2 +#endif +cuArrayCreate_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+40(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+40(%rip) +2: + pushq $5 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuArrayDestroy + .p2align 4 + .type cuArrayDestroy, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuArrayDestroy +#endif +cuArrayDestroy: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+48(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+48(%rip) +2: + pushq $6 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuArrayGetDescriptor + .p2align 4 + .type cuArrayGetDescriptor, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuArrayGetDescriptor +#endif +cuArrayGetDescriptor: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+56(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+56(%rip) +2: + pushq $7 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuArrayGetDescriptor_v2 + .p2align 4 + .type cuArrayGetDescriptor_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuArrayGetDescriptor_v2 +#endif +cuArrayGetDescriptor_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+64(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+64(%rip) +2: + pushq $8 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuArrayGetMemoryRequirements + .p2align 4 + .type cuArrayGetMemoryRequirements, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuArrayGetMemoryRequirements +#endif +cuArrayGetMemoryRequirements: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+72(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+72(%rip) +2: + pushq $9 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuArrayGetPlane + .p2align 4 + .type cuArrayGetPlane, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuArrayGetPlane +#endif +cuArrayGetPlane: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+80(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+80(%rip) +2: + pushq $10 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuArrayGetSparseProperties + .p2align 4 + .type cuArrayGetSparseProperties, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuArrayGetSparseProperties +#endif +cuArrayGetSparseProperties: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+88(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+88(%rip) +2: + pushq $11 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCheckpointProcessCheckpoint + .p2align 4 + .type cuCheckpointProcessCheckpoint, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCheckpointProcessCheckpoint +#endif +cuCheckpointProcessCheckpoint: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+96(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+96(%rip) +2: + pushq $12 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCheckpointProcessGetRestoreThreadId + .p2align 4 + .type cuCheckpointProcessGetRestoreThreadId, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCheckpointProcessGetRestoreThreadId +#endif +cuCheckpointProcessGetRestoreThreadId: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+104(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+104(%rip) +2: + pushq $13 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCheckpointProcessGetState + .p2align 4 + .type cuCheckpointProcessGetState, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCheckpointProcessGetState +#endif +cuCheckpointProcessGetState: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+112(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+112(%rip) +2: + pushq $14 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCheckpointProcessLock + .p2align 4 + .type cuCheckpointProcessLock, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCheckpointProcessLock +#endif +cuCheckpointProcessLock: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+120(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+120(%rip) +2: + pushq $15 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCheckpointProcessRestore + .p2align 4 + .type cuCheckpointProcessRestore, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCheckpointProcessRestore +#endif +cuCheckpointProcessRestore: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+128(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+128(%rip) +2: + pushq $16 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCheckpointProcessUnlock + .p2align 4 + .type cuCheckpointProcessUnlock, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCheckpointProcessUnlock +#endif +cuCheckpointProcessUnlock: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+136(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+136(%rip) +2: + pushq $17 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCoredumpGetAttribute + .p2align 4 + .type cuCoredumpGetAttribute, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCoredumpGetAttribute +#endif +cuCoredumpGetAttribute: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+144(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+144(%rip) +2: + pushq $18 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCoredumpGetAttributeGlobal + .p2align 4 + .type cuCoredumpGetAttributeGlobal, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCoredumpGetAttributeGlobal +#endif +cuCoredumpGetAttributeGlobal: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+152(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+152(%rip) +2: + pushq $19 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCoredumpSetAttribute + .p2align 4 + .type cuCoredumpSetAttribute, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCoredumpSetAttribute +#endif +cuCoredumpSetAttribute: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+160(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+160(%rip) +2: + pushq $20 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCoredumpSetAttributeGlobal + .p2align 4 + .type cuCoredumpSetAttributeGlobal, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCoredumpSetAttributeGlobal +#endif +cuCoredumpSetAttributeGlobal: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+168(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+168(%rip) +2: + pushq $21 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCtxAttach + .p2align 4 + .type cuCtxAttach, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCtxAttach +#endif +cuCtxAttach: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+176(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+176(%rip) +2: + pushq $22 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCtxCreate + .p2align 4 + .type cuCtxCreate, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCtxCreate +#endif +cuCtxCreate: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+184(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+184(%rip) +2: + pushq $23 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCtxCreate_v2 + .p2align 4 + .type cuCtxCreate_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCtxCreate_v2 +#endif +cuCtxCreate_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+192(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+192(%rip) +2: + pushq $24 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCtxCreate_v3 + .p2align 4 + .type cuCtxCreate_v3, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCtxCreate_v3 +#endif +cuCtxCreate_v3: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+200(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+200(%rip) +2: + pushq $25 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCtxCreate_v4 + .p2align 4 + .type cuCtxCreate_v4, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCtxCreate_v4 +#endif +cuCtxCreate_v4: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+208(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+208(%rip) +2: + pushq $26 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCtxDestroy + .p2align 4 + .type cuCtxDestroy, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCtxDestroy +#endif +cuCtxDestroy: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+216(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+216(%rip) +2: + pushq $27 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCtxDestroy_v2 + .p2align 4 + .type cuCtxDestroy_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCtxDestroy_v2 +#endif +cuCtxDestroy_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+224(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+224(%rip) +2: + pushq $28 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCtxDetach + .p2align 4 + .type cuCtxDetach, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCtxDetach +#endif +cuCtxDetach: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+232(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+232(%rip) +2: + pushq $29 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCtxDisablePeerAccess + .p2align 4 + .type cuCtxDisablePeerAccess, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCtxDisablePeerAccess +#endif +cuCtxDisablePeerAccess: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+240(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+240(%rip) +2: + pushq $30 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCtxEnablePeerAccess + .p2align 4 + .type cuCtxEnablePeerAccess, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCtxEnablePeerAccess +#endif +cuCtxEnablePeerAccess: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+248(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+248(%rip) +2: + pushq $31 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCtxFromGreenCtx + .p2align 4 + .type cuCtxFromGreenCtx, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCtxFromGreenCtx +#endif +cuCtxFromGreenCtx: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+256(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+256(%rip) +2: + pushq $32 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCtxGetApiVersion + .p2align 4 + .type cuCtxGetApiVersion, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCtxGetApiVersion +#endif +cuCtxGetApiVersion: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+264(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+264(%rip) +2: + pushq $33 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCtxGetCacheConfig + .p2align 4 + .type cuCtxGetCacheConfig, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCtxGetCacheConfig +#endif +cuCtxGetCacheConfig: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+272(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+272(%rip) +2: + pushq $34 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCtxGetCurrent + .p2align 4 + .type cuCtxGetCurrent, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCtxGetCurrent +#endif +cuCtxGetCurrent: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+280(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+280(%rip) +2: + pushq $35 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCtxGetDevResource + .p2align 4 + .type cuCtxGetDevResource, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCtxGetDevResource +#endif +cuCtxGetDevResource: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+288(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+288(%rip) +2: + pushq $36 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCtxGetDevice + .p2align 4 + .type cuCtxGetDevice, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCtxGetDevice +#endif +cuCtxGetDevice: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+296(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+296(%rip) +2: + pushq $37 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCtxGetExecAffinity + .p2align 4 + .type cuCtxGetExecAffinity, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCtxGetExecAffinity +#endif +cuCtxGetExecAffinity: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+304(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+304(%rip) +2: + pushq $38 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCtxGetFlags + .p2align 4 + .type cuCtxGetFlags, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCtxGetFlags +#endif +cuCtxGetFlags: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+312(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+312(%rip) +2: + pushq $39 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCtxGetId + .p2align 4 + .type cuCtxGetId, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCtxGetId +#endif +cuCtxGetId: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+320(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+320(%rip) +2: + pushq $40 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCtxGetLimit + .p2align 4 + .type cuCtxGetLimit, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCtxGetLimit +#endif +cuCtxGetLimit: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+328(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+328(%rip) +2: + pushq $41 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCtxGetSharedMemConfig + .p2align 4 + .type cuCtxGetSharedMemConfig, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCtxGetSharedMemConfig +#endif +cuCtxGetSharedMemConfig: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+336(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+336(%rip) +2: + pushq $42 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCtxGetStreamPriorityRange + .p2align 4 + .type cuCtxGetStreamPriorityRange, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCtxGetStreamPriorityRange +#endif +cuCtxGetStreamPriorityRange: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+344(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+344(%rip) +2: + pushq $43 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCtxPopCurrent + .p2align 4 + .type cuCtxPopCurrent, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCtxPopCurrent +#endif +cuCtxPopCurrent: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+352(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+352(%rip) +2: + pushq $44 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCtxPopCurrent_v2 + .p2align 4 + .type cuCtxPopCurrent_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCtxPopCurrent_v2 +#endif +cuCtxPopCurrent_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+360(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+360(%rip) +2: + pushq $45 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCtxPushCurrent + .p2align 4 + .type cuCtxPushCurrent, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCtxPushCurrent +#endif +cuCtxPushCurrent: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+368(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+368(%rip) +2: + pushq $46 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCtxPushCurrent_v2 + .p2align 4 + .type cuCtxPushCurrent_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCtxPushCurrent_v2 +#endif +cuCtxPushCurrent_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+376(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+376(%rip) +2: + pushq $47 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCtxRecordEvent + .p2align 4 + .type cuCtxRecordEvent, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCtxRecordEvent +#endif +cuCtxRecordEvent: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+384(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+384(%rip) +2: + pushq $48 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCtxResetPersistingL2Cache + .p2align 4 + .type cuCtxResetPersistingL2Cache, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCtxResetPersistingL2Cache +#endif +cuCtxResetPersistingL2Cache: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+392(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+392(%rip) +2: + pushq $49 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCtxSetCacheConfig + .p2align 4 + .type cuCtxSetCacheConfig, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCtxSetCacheConfig +#endif +cuCtxSetCacheConfig: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+400(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+400(%rip) +2: + pushq $50 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCtxSetCurrent + .p2align 4 + .type cuCtxSetCurrent, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCtxSetCurrent +#endif +cuCtxSetCurrent: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+408(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+408(%rip) +2: + pushq $51 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCtxSetFlags + .p2align 4 + .type cuCtxSetFlags, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCtxSetFlags +#endif +cuCtxSetFlags: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+416(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+416(%rip) +2: + pushq $52 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCtxSetLimit + .p2align 4 + .type cuCtxSetLimit, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCtxSetLimit +#endif +cuCtxSetLimit: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+424(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+424(%rip) +2: + pushq $53 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCtxSetSharedMemConfig + .p2align 4 + .type cuCtxSetSharedMemConfig, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCtxSetSharedMemConfig +#endif +cuCtxSetSharedMemConfig: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+432(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+432(%rip) +2: + pushq $54 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCtxSynchronize + .p2align 4 + .type cuCtxSynchronize, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCtxSynchronize +#endif +cuCtxSynchronize: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+440(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+440(%rip) +2: + pushq $55 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuCtxWaitEvent + .p2align 4 + .type cuCtxWaitEvent, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuCtxWaitEvent +#endif +cuCtxWaitEvent: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+448(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+448(%rip) +2: + pushq $56 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuDestroyExternalMemory + .p2align 4 + .type cuDestroyExternalMemory, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuDestroyExternalMemory +#endif +cuDestroyExternalMemory: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+456(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+456(%rip) +2: + pushq $57 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuDestroyExternalSemaphore + .p2align 4 + .type cuDestroyExternalSemaphore, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuDestroyExternalSemaphore +#endif +cuDestroyExternalSemaphore: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+464(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+464(%rip) +2: + pushq $58 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuDevResourceGenerateDesc + .p2align 4 + .type cuDevResourceGenerateDesc, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuDevResourceGenerateDesc +#endif +cuDevResourceGenerateDesc: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+472(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+472(%rip) +2: + pushq $59 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuDevSmResourceSplitByCount + .p2align 4 + .type cuDevSmResourceSplitByCount, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuDevSmResourceSplitByCount +#endif +cuDevSmResourceSplitByCount: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+480(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+480(%rip) +2: + pushq $60 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuDeviceCanAccessPeer + .p2align 4 + .type cuDeviceCanAccessPeer, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuDeviceCanAccessPeer +#endif +cuDeviceCanAccessPeer: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+488(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+488(%rip) +2: + pushq $61 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuDeviceComputeCapability + .p2align 4 + .type cuDeviceComputeCapability, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuDeviceComputeCapability +#endif +cuDeviceComputeCapability: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+496(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+496(%rip) +2: + pushq $62 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuDeviceGet + .p2align 4 + .type cuDeviceGet, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuDeviceGet +#endif +cuDeviceGet: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+504(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+504(%rip) +2: + pushq $63 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuDeviceGetAttribute + .p2align 4 + .type cuDeviceGetAttribute, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuDeviceGetAttribute +#endif +cuDeviceGetAttribute: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+512(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+512(%rip) +2: + pushq $64 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuDeviceGetByPCIBusId + .p2align 4 + .type cuDeviceGetByPCIBusId, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuDeviceGetByPCIBusId +#endif +cuDeviceGetByPCIBusId: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+520(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+520(%rip) +2: + pushq $65 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuDeviceGetCount + .p2align 4 + .type cuDeviceGetCount, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuDeviceGetCount +#endif +cuDeviceGetCount: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+528(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+528(%rip) +2: + pushq $66 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuDeviceGetDefaultMemPool + .p2align 4 + .type cuDeviceGetDefaultMemPool, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuDeviceGetDefaultMemPool +#endif +cuDeviceGetDefaultMemPool: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+536(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+536(%rip) +2: + pushq $67 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuDeviceGetDevResource + .p2align 4 + .type cuDeviceGetDevResource, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuDeviceGetDevResource +#endif +cuDeviceGetDevResource: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+544(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+544(%rip) +2: + pushq $68 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuDeviceGetExecAffinitySupport + .p2align 4 + .type cuDeviceGetExecAffinitySupport, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuDeviceGetExecAffinitySupport +#endif +cuDeviceGetExecAffinitySupport: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+552(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+552(%rip) +2: + pushq $69 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuDeviceGetGraphMemAttribute + .p2align 4 + .type cuDeviceGetGraphMemAttribute, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuDeviceGetGraphMemAttribute +#endif +cuDeviceGetGraphMemAttribute: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+560(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+560(%rip) +2: + pushq $70 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuDeviceGetLuid + .p2align 4 + .type cuDeviceGetLuid, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuDeviceGetLuid +#endif +cuDeviceGetLuid: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+568(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+568(%rip) +2: + pushq $71 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuDeviceGetMemPool + .p2align 4 + .type cuDeviceGetMemPool, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuDeviceGetMemPool +#endif +cuDeviceGetMemPool: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+576(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+576(%rip) +2: + pushq $72 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuDeviceGetName + .p2align 4 + .type cuDeviceGetName, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuDeviceGetName +#endif +cuDeviceGetName: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+584(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+584(%rip) +2: + pushq $73 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuDeviceGetNvSciSyncAttributes + .p2align 4 + .type cuDeviceGetNvSciSyncAttributes, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuDeviceGetNvSciSyncAttributes +#endif +cuDeviceGetNvSciSyncAttributes: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+592(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+592(%rip) +2: + pushq $74 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuDeviceGetP2PAttribute + .p2align 4 + .type cuDeviceGetP2PAttribute, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuDeviceGetP2PAttribute +#endif +cuDeviceGetP2PAttribute: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+600(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+600(%rip) +2: + pushq $75 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuDeviceGetPCIBusId + .p2align 4 + .type cuDeviceGetPCIBusId, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuDeviceGetPCIBusId +#endif +cuDeviceGetPCIBusId: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+608(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+608(%rip) +2: + pushq $76 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuDeviceGetProperties + .p2align 4 + .type cuDeviceGetProperties, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuDeviceGetProperties +#endif +cuDeviceGetProperties: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+616(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+616(%rip) +2: + pushq $77 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuDeviceGetTexture1DLinearMaxWidth + .p2align 4 + .type cuDeviceGetTexture1DLinearMaxWidth, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuDeviceGetTexture1DLinearMaxWidth +#endif +cuDeviceGetTexture1DLinearMaxWidth: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+624(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+624(%rip) +2: + pushq $78 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuDeviceGetUuid + .p2align 4 + .type cuDeviceGetUuid, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuDeviceGetUuid +#endif +cuDeviceGetUuid: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+632(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+632(%rip) +2: + pushq $79 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuDeviceGetUuid_v2 + .p2align 4 + .type cuDeviceGetUuid_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuDeviceGetUuid_v2 +#endif +cuDeviceGetUuid_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+640(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+640(%rip) +2: + pushq $80 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuDeviceGraphMemTrim + .p2align 4 + .type cuDeviceGraphMemTrim, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuDeviceGraphMemTrim +#endif +cuDeviceGraphMemTrim: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+648(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+648(%rip) +2: + pushq $81 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuDevicePrimaryCtxGetState + .p2align 4 + .type cuDevicePrimaryCtxGetState, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuDevicePrimaryCtxGetState +#endif +cuDevicePrimaryCtxGetState: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+656(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+656(%rip) +2: + pushq $82 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuDevicePrimaryCtxRelease + .p2align 4 + .type cuDevicePrimaryCtxRelease, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuDevicePrimaryCtxRelease +#endif +cuDevicePrimaryCtxRelease: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+664(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+664(%rip) +2: + pushq $83 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuDevicePrimaryCtxRelease_v2 + .p2align 4 + .type cuDevicePrimaryCtxRelease_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuDevicePrimaryCtxRelease_v2 +#endif +cuDevicePrimaryCtxRelease_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+672(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+672(%rip) +2: + pushq $84 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuDevicePrimaryCtxReset + .p2align 4 + .type cuDevicePrimaryCtxReset, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuDevicePrimaryCtxReset +#endif +cuDevicePrimaryCtxReset: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+680(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+680(%rip) +2: + pushq $85 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuDevicePrimaryCtxReset_v2 + .p2align 4 + .type cuDevicePrimaryCtxReset_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuDevicePrimaryCtxReset_v2 +#endif +cuDevicePrimaryCtxReset_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+688(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+688(%rip) +2: + pushq $86 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuDevicePrimaryCtxRetain + .p2align 4 + .type cuDevicePrimaryCtxRetain, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuDevicePrimaryCtxRetain +#endif +cuDevicePrimaryCtxRetain: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+696(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+696(%rip) +2: + pushq $87 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuDevicePrimaryCtxSetFlags + .p2align 4 + .type cuDevicePrimaryCtxSetFlags, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuDevicePrimaryCtxSetFlags +#endif +cuDevicePrimaryCtxSetFlags: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+704(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+704(%rip) +2: + pushq $88 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuDevicePrimaryCtxSetFlags_v2 + .p2align 4 + .type cuDevicePrimaryCtxSetFlags_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuDevicePrimaryCtxSetFlags_v2 +#endif +cuDevicePrimaryCtxSetFlags_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+712(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+712(%rip) +2: + pushq $89 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuDeviceRegisterAsyncNotification + .p2align 4 + .type cuDeviceRegisterAsyncNotification, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuDeviceRegisterAsyncNotification +#endif +cuDeviceRegisterAsyncNotification: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+720(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+720(%rip) +2: + pushq $90 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuDeviceSetGraphMemAttribute + .p2align 4 + .type cuDeviceSetGraphMemAttribute, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuDeviceSetGraphMemAttribute +#endif +cuDeviceSetGraphMemAttribute: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+728(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+728(%rip) +2: + pushq $91 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuDeviceSetMemPool + .p2align 4 + .type cuDeviceSetMemPool, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuDeviceSetMemPool +#endif +cuDeviceSetMemPool: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+736(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+736(%rip) +2: + pushq $92 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuDeviceTotalMem + .p2align 4 + .type cuDeviceTotalMem, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuDeviceTotalMem +#endif +cuDeviceTotalMem: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+744(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+744(%rip) +2: + pushq $93 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuDeviceTotalMem_v2 + .p2align 4 + .type cuDeviceTotalMem_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuDeviceTotalMem_v2 +#endif +cuDeviceTotalMem_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+752(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+752(%rip) +2: + pushq $94 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuDeviceUnregisterAsyncNotification + .p2align 4 + .type cuDeviceUnregisterAsyncNotification, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuDeviceUnregisterAsyncNotification +#endif +cuDeviceUnregisterAsyncNotification: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+760(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+760(%rip) +2: + pushq $95 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuDriverGetVersion + .p2align 4 + .type cuDriverGetVersion, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuDriverGetVersion +#endif +cuDriverGetVersion: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+768(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+768(%rip) +2: + pushq $96 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuEGLApiInit + .p2align 4 + .type cuEGLApiInit, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuEGLApiInit +#endif +cuEGLApiInit: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+776(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+776(%rip) +2: + pushq $97 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuEGLStreamConsumerAcquireFrame + .p2align 4 + .type cuEGLStreamConsumerAcquireFrame, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuEGLStreamConsumerAcquireFrame +#endif +cuEGLStreamConsumerAcquireFrame: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+784(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+784(%rip) +2: + pushq $98 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuEGLStreamConsumerConnect + .p2align 4 + .type cuEGLStreamConsumerConnect, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuEGLStreamConsumerConnect +#endif +cuEGLStreamConsumerConnect: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+792(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+792(%rip) +2: + pushq $99 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuEGLStreamConsumerConnectWithFlags + .p2align 4 + .type cuEGLStreamConsumerConnectWithFlags, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuEGLStreamConsumerConnectWithFlags +#endif +cuEGLStreamConsumerConnectWithFlags: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+800(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+800(%rip) +2: + pushq $100 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuEGLStreamConsumerDisconnect + .p2align 4 + .type cuEGLStreamConsumerDisconnect, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuEGLStreamConsumerDisconnect +#endif +cuEGLStreamConsumerDisconnect: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+808(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+808(%rip) +2: + pushq $101 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuEGLStreamConsumerReleaseFrame + .p2align 4 + .type cuEGLStreamConsumerReleaseFrame, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuEGLStreamConsumerReleaseFrame +#endif +cuEGLStreamConsumerReleaseFrame: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+816(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+816(%rip) +2: + pushq $102 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuEGLStreamProducerConnect + .p2align 4 + .type cuEGLStreamProducerConnect, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuEGLStreamProducerConnect +#endif +cuEGLStreamProducerConnect: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+824(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+824(%rip) +2: + pushq $103 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuEGLStreamProducerDisconnect + .p2align 4 + .type cuEGLStreamProducerDisconnect, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuEGLStreamProducerDisconnect +#endif +cuEGLStreamProducerDisconnect: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+832(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+832(%rip) +2: + pushq $104 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuEGLStreamProducerPresentFrame + .p2align 4 + .type cuEGLStreamProducerPresentFrame, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuEGLStreamProducerPresentFrame +#endif +cuEGLStreamProducerPresentFrame: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+840(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+840(%rip) +2: + pushq $105 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuEGLStreamProducerReturnFrame + .p2align 4 + .type cuEGLStreamProducerReturnFrame, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuEGLStreamProducerReturnFrame +#endif +cuEGLStreamProducerReturnFrame: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+848(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+848(%rip) +2: + pushq $106 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuEventCreate + .p2align 4 + .type cuEventCreate, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuEventCreate +#endif +cuEventCreate: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+856(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+856(%rip) +2: + pushq $107 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuEventDestroy + .p2align 4 + .type cuEventDestroy, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuEventDestroy +#endif +cuEventDestroy: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+864(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+864(%rip) +2: + pushq $108 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuEventDestroy_v2 + .p2align 4 + .type cuEventDestroy_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuEventDestroy_v2 +#endif +cuEventDestroy_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+872(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+872(%rip) +2: + pushq $109 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuEventElapsedTime + .p2align 4 + .type cuEventElapsedTime, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuEventElapsedTime +#endif +cuEventElapsedTime: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+880(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+880(%rip) +2: + pushq $110 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuEventElapsedTime_v2 + .p2align 4 + .type cuEventElapsedTime_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuEventElapsedTime_v2 +#endif +cuEventElapsedTime_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+888(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+888(%rip) +2: + pushq $111 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuEventQuery + .p2align 4 + .type cuEventQuery, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuEventQuery +#endif +cuEventQuery: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+896(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+896(%rip) +2: + pushq $112 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuEventRecord + .p2align 4 + .type cuEventRecord, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuEventRecord +#endif +cuEventRecord: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+904(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+904(%rip) +2: + pushq $113 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuEventRecordWithFlags + .p2align 4 + .type cuEventRecordWithFlags, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuEventRecordWithFlags +#endif +cuEventRecordWithFlags: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+912(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+912(%rip) +2: + pushq $114 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuEventRecordWithFlags_ptsz + .p2align 4 + .type cuEventRecordWithFlags_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuEventRecordWithFlags_ptsz +#endif +cuEventRecordWithFlags_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+920(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+920(%rip) +2: + pushq $115 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuEventRecord_ptsz + .p2align 4 + .type cuEventRecord_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuEventRecord_ptsz +#endif +cuEventRecord_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+928(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+928(%rip) +2: + pushq $116 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuEventSynchronize + .p2align 4 + .type cuEventSynchronize, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuEventSynchronize +#endif +cuEventSynchronize: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+936(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+936(%rip) +2: + pushq $117 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuExternalMemoryGetMappedBuffer + .p2align 4 + .type cuExternalMemoryGetMappedBuffer, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuExternalMemoryGetMappedBuffer +#endif +cuExternalMemoryGetMappedBuffer: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+944(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+944(%rip) +2: + pushq $118 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuExternalMemoryGetMappedMipmappedArray + .p2align 4 + .type cuExternalMemoryGetMappedMipmappedArray, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuExternalMemoryGetMappedMipmappedArray +#endif +cuExternalMemoryGetMappedMipmappedArray: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+952(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+952(%rip) +2: + pushq $119 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuFlushGPUDirectRDMAWrites + .p2align 4 + .type cuFlushGPUDirectRDMAWrites, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuFlushGPUDirectRDMAWrites +#endif +cuFlushGPUDirectRDMAWrites: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+960(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+960(%rip) +2: + pushq $120 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuFuncGetAttribute + .p2align 4 + .type cuFuncGetAttribute, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuFuncGetAttribute +#endif +cuFuncGetAttribute: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+968(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+968(%rip) +2: + pushq $121 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuFuncGetModule + .p2align 4 + .type cuFuncGetModule, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuFuncGetModule +#endif +cuFuncGetModule: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+976(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+976(%rip) +2: + pushq $122 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuFuncGetName + .p2align 4 + .type cuFuncGetName, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuFuncGetName +#endif +cuFuncGetName: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+984(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+984(%rip) +2: + pushq $123 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuFuncGetParamInfo + .p2align 4 + .type cuFuncGetParamInfo, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuFuncGetParamInfo +#endif +cuFuncGetParamInfo: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+992(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+992(%rip) +2: + pushq $124 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuFuncIsLoaded + .p2align 4 + .type cuFuncIsLoaded, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuFuncIsLoaded +#endif +cuFuncIsLoaded: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1000(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1000(%rip) +2: + pushq $125 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuFuncLoad + .p2align 4 + .type cuFuncLoad, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuFuncLoad +#endif +cuFuncLoad: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1008(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1008(%rip) +2: + pushq $126 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuFuncSetAttribute + .p2align 4 + .type cuFuncSetAttribute, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuFuncSetAttribute +#endif +cuFuncSetAttribute: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1016(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1016(%rip) +2: + pushq $127 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuFuncSetBlockShape + .p2align 4 + .type cuFuncSetBlockShape, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuFuncSetBlockShape +#endif +cuFuncSetBlockShape: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1024(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1024(%rip) +2: + pushq $128 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuFuncSetCacheConfig + .p2align 4 + .type cuFuncSetCacheConfig, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuFuncSetCacheConfig +#endif +cuFuncSetCacheConfig: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1032(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1032(%rip) +2: + pushq $129 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuFuncSetSharedMemConfig + .p2align 4 + .type cuFuncSetSharedMemConfig, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuFuncSetSharedMemConfig +#endif +cuFuncSetSharedMemConfig: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1040(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1040(%rip) +2: + pushq $130 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuFuncSetSharedSize + .p2align 4 + .type cuFuncSetSharedSize, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuFuncSetSharedSize +#endif +cuFuncSetSharedSize: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1048(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1048(%rip) +2: + pushq $131 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGLCtxCreate + .p2align 4 + .type cuGLCtxCreate, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGLCtxCreate +#endif +cuGLCtxCreate: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1056(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1056(%rip) +2: + pushq $132 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGLCtxCreate_v2 + .p2align 4 + .type cuGLCtxCreate_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGLCtxCreate_v2 +#endif +cuGLCtxCreate_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1064(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1064(%rip) +2: + pushq $133 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGLGetDevices + .p2align 4 + .type cuGLGetDevices, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGLGetDevices +#endif +cuGLGetDevices: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1072(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1072(%rip) +2: + pushq $134 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGLGetDevices_v2 + .p2align 4 + .type cuGLGetDevices_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGLGetDevices_v2 +#endif +cuGLGetDevices_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1080(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1080(%rip) +2: + pushq $135 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGLInit + .p2align 4 + .type cuGLInit, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGLInit +#endif +cuGLInit: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1088(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1088(%rip) +2: + pushq $136 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGLMapBufferObject + .p2align 4 + .type cuGLMapBufferObject, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGLMapBufferObject +#endif +cuGLMapBufferObject: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1096(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1096(%rip) +2: + pushq $137 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGLMapBufferObjectAsync + .p2align 4 + .type cuGLMapBufferObjectAsync, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGLMapBufferObjectAsync +#endif +cuGLMapBufferObjectAsync: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1104(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1104(%rip) +2: + pushq $138 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGLMapBufferObjectAsync_v2 + .p2align 4 + .type cuGLMapBufferObjectAsync_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGLMapBufferObjectAsync_v2 +#endif +cuGLMapBufferObjectAsync_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1112(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1112(%rip) +2: + pushq $139 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGLMapBufferObjectAsync_v2_ptsz + .p2align 4 + .type cuGLMapBufferObjectAsync_v2_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGLMapBufferObjectAsync_v2_ptsz +#endif +cuGLMapBufferObjectAsync_v2_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1120(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1120(%rip) +2: + pushq $140 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGLMapBufferObject_v2 + .p2align 4 + .type cuGLMapBufferObject_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGLMapBufferObject_v2 +#endif +cuGLMapBufferObject_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1128(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1128(%rip) +2: + pushq $141 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGLMapBufferObject_v2_ptds + .p2align 4 + .type cuGLMapBufferObject_v2_ptds, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGLMapBufferObject_v2_ptds +#endif +cuGLMapBufferObject_v2_ptds: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1136(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1136(%rip) +2: + pushq $142 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGLRegisterBufferObject + .p2align 4 + .type cuGLRegisterBufferObject, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGLRegisterBufferObject +#endif +cuGLRegisterBufferObject: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1144(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1144(%rip) +2: + pushq $143 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGLSetBufferObjectMapFlags + .p2align 4 + .type cuGLSetBufferObjectMapFlags, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGLSetBufferObjectMapFlags +#endif +cuGLSetBufferObjectMapFlags: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1152(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1152(%rip) +2: + pushq $144 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGLUnmapBufferObject + .p2align 4 + .type cuGLUnmapBufferObject, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGLUnmapBufferObject +#endif +cuGLUnmapBufferObject: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1160(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1160(%rip) +2: + pushq $145 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGLUnmapBufferObjectAsync + .p2align 4 + .type cuGLUnmapBufferObjectAsync, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGLUnmapBufferObjectAsync +#endif +cuGLUnmapBufferObjectAsync: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1168(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1168(%rip) +2: + pushq $146 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGLUnregisterBufferObject + .p2align 4 + .type cuGLUnregisterBufferObject, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGLUnregisterBufferObject +#endif +cuGLUnregisterBufferObject: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1176(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1176(%rip) +2: + pushq $147 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGetErrorName + .p2align 4 + .type cuGetErrorName, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGetErrorName +#endif +cuGetErrorName: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1184(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1184(%rip) +2: + pushq $148 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGetErrorString + .p2align 4 + .type cuGetErrorString, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGetErrorString +#endif +cuGetErrorString: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1192(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1192(%rip) +2: + pushq $149 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGetExportTable + .p2align 4 + .type cuGetExportTable, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGetExportTable +#endif +cuGetExportTable: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1200(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1200(%rip) +2: + pushq $150 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGetProcAddress + .p2align 4 + .type cuGetProcAddress, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGetProcAddress +#endif +cuGetProcAddress: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1208(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1208(%rip) +2: + pushq $151 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGetProcAddress_v2 + .p2align 4 + .type cuGetProcAddress_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGetProcAddress_v2 +#endif +cuGetProcAddress_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1216(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1216(%rip) +2: + pushq $152 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphAddBatchMemOpNode + .p2align 4 + .type cuGraphAddBatchMemOpNode, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphAddBatchMemOpNode +#endif +cuGraphAddBatchMemOpNode: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1224(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1224(%rip) +2: + pushq $153 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphAddChildGraphNode + .p2align 4 + .type cuGraphAddChildGraphNode, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphAddChildGraphNode +#endif +cuGraphAddChildGraphNode: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1232(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1232(%rip) +2: + pushq $154 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphAddDependencies + .p2align 4 + .type cuGraphAddDependencies, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphAddDependencies +#endif +cuGraphAddDependencies: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1240(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1240(%rip) +2: + pushq $155 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphAddDependencies_v2 + .p2align 4 + .type cuGraphAddDependencies_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphAddDependencies_v2 +#endif +cuGraphAddDependencies_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1248(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1248(%rip) +2: + pushq $156 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphAddEmptyNode + .p2align 4 + .type cuGraphAddEmptyNode, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphAddEmptyNode +#endif +cuGraphAddEmptyNode: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1256(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1256(%rip) +2: + pushq $157 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphAddEventRecordNode + .p2align 4 + .type cuGraphAddEventRecordNode, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphAddEventRecordNode +#endif +cuGraphAddEventRecordNode: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1264(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1264(%rip) +2: + pushq $158 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphAddEventWaitNode + .p2align 4 + .type cuGraphAddEventWaitNode, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphAddEventWaitNode +#endif +cuGraphAddEventWaitNode: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1272(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1272(%rip) +2: + pushq $159 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphAddExternalSemaphoresSignalNode + .p2align 4 + .type cuGraphAddExternalSemaphoresSignalNode, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphAddExternalSemaphoresSignalNode +#endif +cuGraphAddExternalSemaphoresSignalNode: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1280(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1280(%rip) +2: + pushq $160 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphAddExternalSemaphoresWaitNode + .p2align 4 + .type cuGraphAddExternalSemaphoresWaitNode, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphAddExternalSemaphoresWaitNode +#endif +cuGraphAddExternalSemaphoresWaitNode: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1288(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1288(%rip) +2: + pushq $161 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphAddHostNode + .p2align 4 + .type cuGraphAddHostNode, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphAddHostNode +#endif +cuGraphAddHostNode: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1296(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1296(%rip) +2: + pushq $162 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphAddKernelNode + .p2align 4 + .type cuGraphAddKernelNode, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphAddKernelNode +#endif +cuGraphAddKernelNode: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1304(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1304(%rip) +2: + pushq $163 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphAddKernelNode_v2 + .p2align 4 + .type cuGraphAddKernelNode_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphAddKernelNode_v2 +#endif +cuGraphAddKernelNode_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1312(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1312(%rip) +2: + pushq $164 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphAddMemAllocNode + .p2align 4 + .type cuGraphAddMemAllocNode, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphAddMemAllocNode +#endif +cuGraphAddMemAllocNode: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1320(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1320(%rip) +2: + pushq $165 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphAddMemFreeNode + .p2align 4 + .type cuGraphAddMemFreeNode, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphAddMemFreeNode +#endif +cuGraphAddMemFreeNode: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1328(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1328(%rip) +2: + pushq $166 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphAddMemcpyNode + .p2align 4 + .type cuGraphAddMemcpyNode, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphAddMemcpyNode +#endif +cuGraphAddMemcpyNode: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1336(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1336(%rip) +2: + pushq $167 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphAddMemsetNode + .p2align 4 + .type cuGraphAddMemsetNode, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphAddMemsetNode +#endif +cuGraphAddMemsetNode: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1344(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1344(%rip) +2: + pushq $168 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphAddNode + .p2align 4 + .type cuGraphAddNode, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphAddNode +#endif +cuGraphAddNode: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1352(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1352(%rip) +2: + pushq $169 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphAddNode_v2 + .p2align 4 + .type cuGraphAddNode_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphAddNode_v2 +#endif +cuGraphAddNode_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1360(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1360(%rip) +2: + pushq $170 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphBatchMemOpNodeGetParams + .p2align 4 + .type cuGraphBatchMemOpNodeGetParams, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphBatchMemOpNodeGetParams +#endif +cuGraphBatchMemOpNodeGetParams: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1368(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1368(%rip) +2: + pushq $171 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphBatchMemOpNodeSetParams + .p2align 4 + .type cuGraphBatchMemOpNodeSetParams, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphBatchMemOpNodeSetParams +#endif +cuGraphBatchMemOpNodeSetParams: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1376(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1376(%rip) +2: + pushq $172 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphChildGraphNodeGetGraph + .p2align 4 + .type cuGraphChildGraphNodeGetGraph, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphChildGraphNodeGetGraph +#endif +cuGraphChildGraphNodeGetGraph: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1384(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1384(%rip) +2: + pushq $173 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphClone + .p2align 4 + .type cuGraphClone, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphClone +#endif +cuGraphClone: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1392(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1392(%rip) +2: + pushq $174 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphConditionalHandleCreate + .p2align 4 + .type cuGraphConditionalHandleCreate, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphConditionalHandleCreate +#endif +cuGraphConditionalHandleCreate: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1400(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1400(%rip) +2: + pushq $175 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphCreate + .p2align 4 + .type cuGraphCreate, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphCreate +#endif +cuGraphCreate: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1408(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1408(%rip) +2: + pushq $176 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphDebugDotPrint + .p2align 4 + .type cuGraphDebugDotPrint, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphDebugDotPrint +#endif +cuGraphDebugDotPrint: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1416(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1416(%rip) +2: + pushq $177 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphDestroy + .p2align 4 + .type cuGraphDestroy, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphDestroy +#endif +cuGraphDestroy: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1424(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1424(%rip) +2: + pushq $178 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphDestroyNode + .p2align 4 + .type cuGraphDestroyNode, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphDestroyNode +#endif +cuGraphDestroyNode: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1432(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1432(%rip) +2: + pushq $179 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphEventRecordNodeGetEvent + .p2align 4 + .type cuGraphEventRecordNodeGetEvent, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphEventRecordNodeGetEvent +#endif +cuGraphEventRecordNodeGetEvent: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1440(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1440(%rip) +2: + pushq $180 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphEventRecordNodeSetEvent + .p2align 4 + .type cuGraphEventRecordNodeSetEvent, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphEventRecordNodeSetEvent +#endif +cuGraphEventRecordNodeSetEvent: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1448(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1448(%rip) +2: + pushq $181 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphEventWaitNodeGetEvent + .p2align 4 + .type cuGraphEventWaitNodeGetEvent, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphEventWaitNodeGetEvent +#endif +cuGraphEventWaitNodeGetEvent: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1456(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1456(%rip) +2: + pushq $182 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphEventWaitNodeSetEvent + .p2align 4 + .type cuGraphEventWaitNodeSetEvent, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphEventWaitNodeSetEvent +#endif +cuGraphEventWaitNodeSetEvent: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1464(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1464(%rip) +2: + pushq $183 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphExecBatchMemOpNodeSetParams + .p2align 4 + .type cuGraphExecBatchMemOpNodeSetParams, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphExecBatchMemOpNodeSetParams +#endif +cuGraphExecBatchMemOpNodeSetParams: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1472(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1472(%rip) +2: + pushq $184 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphExecChildGraphNodeSetParams + .p2align 4 + .type cuGraphExecChildGraphNodeSetParams, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphExecChildGraphNodeSetParams +#endif +cuGraphExecChildGraphNodeSetParams: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1480(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1480(%rip) +2: + pushq $185 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphExecDestroy + .p2align 4 + .type cuGraphExecDestroy, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphExecDestroy +#endif +cuGraphExecDestroy: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1488(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1488(%rip) +2: + pushq $186 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphExecEventRecordNodeSetEvent + .p2align 4 + .type cuGraphExecEventRecordNodeSetEvent, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphExecEventRecordNodeSetEvent +#endif +cuGraphExecEventRecordNodeSetEvent: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1496(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1496(%rip) +2: + pushq $187 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphExecEventWaitNodeSetEvent + .p2align 4 + .type cuGraphExecEventWaitNodeSetEvent, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphExecEventWaitNodeSetEvent +#endif +cuGraphExecEventWaitNodeSetEvent: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1504(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1504(%rip) +2: + pushq $188 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphExecExternalSemaphoresSignalNodeSetParams + .p2align 4 + .type cuGraphExecExternalSemaphoresSignalNodeSetParams, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphExecExternalSemaphoresSignalNodeSetParams +#endif +cuGraphExecExternalSemaphoresSignalNodeSetParams: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1512(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1512(%rip) +2: + pushq $189 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphExecExternalSemaphoresWaitNodeSetParams + .p2align 4 + .type cuGraphExecExternalSemaphoresWaitNodeSetParams, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphExecExternalSemaphoresWaitNodeSetParams +#endif +cuGraphExecExternalSemaphoresWaitNodeSetParams: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1520(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1520(%rip) +2: + pushq $190 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphExecGetFlags + .p2align 4 + .type cuGraphExecGetFlags, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphExecGetFlags +#endif +cuGraphExecGetFlags: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1528(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1528(%rip) +2: + pushq $191 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphExecHostNodeSetParams + .p2align 4 + .type cuGraphExecHostNodeSetParams, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphExecHostNodeSetParams +#endif +cuGraphExecHostNodeSetParams: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1536(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1536(%rip) +2: + pushq $192 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphExecKernelNodeSetParams + .p2align 4 + .type cuGraphExecKernelNodeSetParams, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphExecKernelNodeSetParams +#endif +cuGraphExecKernelNodeSetParams: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1544(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1544(%rip) +2: + pushq $193 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphExecKernelNodeSetParams_v2 + .p2align 4 + .type cuGraphExecKernelNodeSetParams_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphExecKernelNodeSetParams_v2 +#endif +cuGraphExecKernelNodeSetParams_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1552(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1552(%rip) +2: + pushq $194 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphExecMemcpyNodeSetParams + .p2align 4 + .type cuGraphExecMemcpyNodeSetParams, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphExecMemcpyNodeSetParams +#endif +cuGraphExecMemcpyNodeSetParams: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1560(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1560(%rip) +2: + pushq $195 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphExecMemsetNodeSetParams + .p2align 4 + .type cuGraphExecMemsetNodeSetParams, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphExecMemsetNodeSetParams +#endif +cuGraphExecMemsetNodeSetParams: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1568(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1568(%rip) +2: + pushq $196 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphExecNodeSetParams + .p2align 4 + .type cuGraphExecNodeSetParams, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphExecNodeSetParams +#endif +cuGraphExecNodeSetParams: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1576(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1576(%rip) +2: + pushq $197 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphExecUpdate + .p2align 4 + .type cuGraphExecUpdate, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphExecUpdate +#endif +cuGraphExecUpdate: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1584(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1584(%rip) +2: + pushq $198 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphExecUpdate_v2 + .p2align 4 + .type cuGraphExecUpdate_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphExecUpdate_v2 +#endif +cuGraphExecUpdate_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1592(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1592(%rip) +2: + pushq $199 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphExternalSemaphoresSignalNodeGetParams + .p2align 4 + .type cuGraphExternalSemaphoresSignalNodeGetParams, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphExternalSemaphoresSignalNodeGetParams +#endif +cuGraphExternalSemaphoresSignalNodeGetParams: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1600(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1600(%rip) +2: + pushq $200 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphExternalSemaphoresSignalNodeSetParams + .p2align 4 + .type cuGraphExternalSemaphoresSignalNodeSetParams, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphExternalSemaphoresSignalNodeSetParams +#endif +cuGraphExternalSemaphoresSignalNodeSetParams: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1608(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1608(%rip) +2: + pushq $201 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphExternalSemaphoresWaitNodeGetParams + .p2align 4 + .type cuGraphExternalSemaphoresWaitNodeGetParams, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphExternalSemaphoresWaitNodeGetParams +#endif +cuGraphExternalSemaphoresWaitNodeGetParams: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1616(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1616(%rip) +2: + pushq $202 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphExternalSemaphoresWaitNodeSetParams + .p2align 4 + .type cuGraphExternalSemaphoresWaitNodeSetParams, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphExternalSemaphoresWaitNodeSetParams +#endif +cuGraphExternalSemaphoresWaitNodeSetParams: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1624(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1624(%rip) +2: + pushq $203 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphGetEdges + .p2align 4 + .type cuGraphGetEdges, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphGetEdges +#endif +cuGraphGetEdges: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1632(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1632(%rip) +2: + pushq $204 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphGetEdges_v2 + .p2align 4 + .type cuGraphGetEdges_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphGetEdges_v2 +#endif +cuGraphGetEdges_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1640(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1640(%rip) +2: + pushq $205 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphGetNodes + .p2align 4 + .type cuGraphGetNodes, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphGetNodes +#endif +cuGraphGetNodes: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1648(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1648(%rip) +2: + pushq $206 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphGetRootNodes + .p2align 4 + .type cuGraphGetRootNodes, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphGetRootNodes +#endif +cuGraphGetRootNodes: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1656(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1656(%rip) +2: + pushq $207 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphHostNodeGetParams + .p2align 4 + .type cuGraphHostNodeGetParams, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphHostNodeGetParams +#endif +cuGraphHostNodeGetParams: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1664(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1664(%rip) +2: + pushq $208 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphHostNodeSetParams + .p2align 4 + .type cuGraphHostNodeSetParams, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphHostNodeSetParams +#endif +cuGraphHostNodeSetParams: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1672(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1672(%rip) +2: + pushq $209 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphInstantiate + .p2align 4 + .type cuGraphInstantiate, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphInstantiate +#endif +cuGraphInstantiate: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1680(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1680(%rip) +2: + pushq $210 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphInstantiateWithFlags + .p2align 4 + .type cuGraphInstantiateWithFlags, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphInstantiateWithFlags +#endif +cuGraphInstantiateWithFlags: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1688(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1688(%rip) +2: + pushq $211 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphInstantiateWithParams + .p2align 4 + .type cuGraphInstantiateWithParams, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphInstantiateWithParams +#endif +cuGraphInstantiateWithParams: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1696(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1696(%rip) +2: + pushq $212 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphInstantiateWithParams_ptsz + .p2align 4 + .type cuGraphInstantiateWithParams_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphInstantiateWithParams_ptsz +#endif +cuGraphInstantiateWithParams_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1704(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1704(%rip) +2: + pushq $213 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphInstantiate_v2 + .p2align 4 + .type cuGraphInstantiate_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphInstantiate_v2 +#endif +cuGraphInstantiate_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1712(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1712(%rip) +2: + pushq $214 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphKernelNodeCopyAttributes + .p2align 4 + .type cuGraphKernelNodeCopyAttributes, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphKernelNodeCopyAttributes +#endif +cuGraphKernelNodeCopyAttributes: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1720(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1720(%rip) +2: + pushq $215 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphKernelNodeGetAttribute + .p2align 4 + .type cuGraphKernelNodeGetAttribute, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphKernelNodeGetAttribute +#endif +cuGraphKernelNodeGetAttribute: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1728(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1728(%rip) +2: + pushq $216 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphKernelNodeGetParams + .p2align 4 + .type cuGraphKernelNodeGetParams, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphKernelNodeGetParams +#endif +cuGraphKernelNodeGetParams: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1736(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1736(%rip) +2: + pushq $217 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphKernelNodeGetParams_v2 + .p2align 4 + .type cuGraphKernelNodeGetParams_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphKernelNodeGetParams_v2 +#endif +cuGraphKernelNodeGetParams_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1744(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1744(%rip) +2: + pushq $218 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphKernelNodeSetAttribute + .p2align 4 + .type cuGraphKernelNodeSetAttribute, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphKernelNodeSetAttribute +#endif +cuGraphKernelNodeSetAttribute: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1752(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1752(%rip) +2: + pushq $219 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphKernelNodeSetParams + .p2align 4 + .type cuGraphKernelNodeSetParams, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphKernelNodeSetParams +#endif +cuGraphKernelNodeSetParams: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1760(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1760(%rip) +2: + pushq $220 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphKernelNodeSetParams_v2 + .p2align 4 + .type cuGraphKernelNodeSetParams_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphKernelNodeSetParams_v2 +#endif +cuGraphKernelNodeSetParams_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1768(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1768(%rip) +2: + pushq $221 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphLaunch + .p2align 4 + .type cuGraphLaunch, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphLaunch +#endif +cuGraphLaunch: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1776(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1776(%rip) +2: + pushq $222 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphLaunch_ptsz + .p2align 4 + .type cuGraphLaunch_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphLaunch_ptsz +#endif +cuGraphLaunch_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1784(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1784(%rip) +2: + pushq $223 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphMemAllocNodeGetParams + .p2align 4 + .type cuGraphMemAllocNodeGetParams, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphMemAllocNodeGetParams +#endif +cuGraphMemAllocNodeGetParams: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1792(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1792(%rip) +2: + pushq $224 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphMemFreeNodeGetParams + .p2align 4 + .type cuGraphMemFreeNodeGetParams, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphMemFreeNodeGetParams +#endif +cuGraphMemFreeNodeGetParams: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1800(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1800(%rip) +2: + pushq $225 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphMemcpyNodeGetParams + .p2align 4 + .type cuGraphMemcpyNodeGetParams, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphMemcpyNodeGetParams +#endif +cuGraphMemcpyNodeGetParams: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1808(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1808(%rip) +2: + pushq $226 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphMemcpyNodeSetParams + .p2align 4 + .type cuGraphMemcpyNodeSetParams, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphMemcpyNodeSetParams +#endif +cuGraphMemcpyNodeSetParams: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1816(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1816(%rip) +2: + pushq $227 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphMemsetNodeGetParams + .p2align 4 + .type cuGraphMemsetNodeGetParams, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphMemsetNodeGetParams +#endif +cuGraphMemsetNodeGetParams: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1824(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1824(%rip) +2: + pushq $228 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphMemsetNodeSetParams + .p2align 4 + .type cuGraphMemsetNodeSetParams, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphMemsetNodeSetParams +#endif +cuGraphMemsetNodeSetParams: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1832(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1832(%rip) +2: + pushq $229 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphNodeFindInClone + .p2align 4 + .type cuGraphNodeFindInClone, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphNodeFindInClone +#endif +cuGraphNodeFindInClone: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1840(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1840(%rip) +2: + pushq $230 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphNodeGetDependencies + .p2align 4 + .type cuGraphNodeGetDependencies, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphNodeGetDependencies +#endif +cuGraphNodeGetDependencies: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1848(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1848(%rip) +2: + pushq $231 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphNodeGetDependencies_v2 + .p2align 4 + .type cuGraphNodeGetDependencies_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphNodeGetDependencies_v2 +#endif +cuGraphNodeGetDependencies_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1856(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1856(%rip) +2: + pushq $232 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphNodeGetDependentNodes + .p2align 4 + .type cuGraphNodeGetDependentNodes, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphNodeGetDependentNodes +#endif +cuGraphNodeGetDependentNodes: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1864(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1864(%rip) +2: + pushq $233 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphNodeGetDependentNodes_v2 + .p2align 4 + .type cuGraphNodeGetDependentNodes_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphNodeGetDependentNodes_v2 +#endif +cuGraphNodeGetDependentNodes_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1872(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1872(%rip) +2: + pushq $234 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphNodeGetEnabled + .p2align 4 + .type cuGraphNodeGetEnabled, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphNodeGetEnabled +#endif +cuGraphNodeGetEnabled: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1880(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1880(%rip) +2: + pushq $235 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphNodeGetType + .p2align 4 + .type cuGraphNodeGetType, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphNodeGetType +#endif +cuGraphNodeGetType: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1888(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1888(%rip) +2: + pushq $236 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphNodeSetEnabled + .p2align 4 + .type cuGraphNodeSetEnabled, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphNodeSetEnabled +#endif +cuGraphNodeSetEnabled: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1896(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1896(%rip) +2: + pushq $237 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphNodeSetParams + .p2align 4 + .type cuGraphNodeSetParams, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphNodeSetParams +#endif +cuGraphNodeSetParams: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1904(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1904(%rip) +2: + pushq $238 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphReleaseUserObject + .p2align 4 + .type cuGraphReleaseUserObject, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphReleaseUserObject +#endif +cuGraphReleaseUserObject: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1912(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1912(%rip) +2: + pushq $239 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphRemoveDependencies + .p2align 4 + .type cuGraphRemoveDependencies, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphRemoveDependencies +#endif +cuGraphRemoveDependencies: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1920(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1920(%rip) +2: + pushq $240 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphRemoveDependencies_v2 + .p2align 4 + .type cuGraphRemoveDependencies_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphRemoveDependencies_v2 +#endif +cuGraphRemoveDependencies_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1928(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1928(%rip) +2: + pushq $241 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphRetainUserObject + .p2align 4 + .type cuGraphRetainUserObject, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphRetainUserObject +#endif +cuGraphRetainUserObject: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1936(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1936(%rip) +2: + pushq $242 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphUpload + .p2align 4 + .type cuGraphUpload, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphUpload +#endif +cuGraphUpload: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1944(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1944(%rip) +2: + pushq $243 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphUpload_ptsz + .p2align 4 + .type cuGraphUpload_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphUpload_ptsz +#endif +cuGraphUpload_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1952(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1952(%rip) +2: + pushq $244 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphicsEGLRegisterImage + .p2align 4 + .type cuGraphicsEGLRegisterImage, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphicsEGLRegisterImage +#endif +cuGraphicsEGLRegisterImage: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1960(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1960(%rip) +2: + pushq $245 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphicsGLRegisterBuffer + .p2align 4 + .type cuGraphicsGLRegisterBuffer, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphicsGLRegisterBuffer +#endif +cuGraphicsGLRegisterBuffer: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1968(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1968(%rip) +2: + pushq $246 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphicsGLRegisterImage + .p2align 4 + .type cuGraphicsGLRegisterImage, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphicsGLRegisterImage +#endif +cuGraphicsGLRegisterImage: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1976(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1976(%rip) +2: + pushq $247 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphicsMapResources + .p2align 4 + .type cuGraphicsMapResources, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphicsMapResources +#endif +cuGraphicsMapResources: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1984(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1984(%rip) +2: + pushq $248 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphicsMapResources_ptsz + .p2align 4 + .type cuGraphicsMapResources_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphicsMapResources_ptsz +#endif +cuGraphicsMapResources_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+1992(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+1992(%rip) +2: + pushq $249 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphicsResourceGetMappedEglFrame + .p2align 4 + .type cuGraphicsResourceGetMappedEglFrame, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphicsResourceGetMappedEglFrame +#endif +cuGraphicsResourceGetMappedEglFrame: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2000(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2000(%rip) +2: + pushq $250 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphicsResourceGetMappedMipmappedArray + .p2align 4 + .type cuGraphicsResourceGetMappedMipmappedArray, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphicsResourceGetMappedMipmappedArray +#endif +cuGraphicsResourceGetMappedMipmappedArray: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2008(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2008(%rip) +2: + pushq $251 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphicsResourceGetMappedPointer + .p2align 4 + .type cuGraphicsResourceGetMappedPointer, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphicsResourceGetMappedPointer +#endif +cuGraphicsResourceGetMappedPointer: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2016(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2016(%rip) +2: + pushq $252 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphicsResourceGetMappedPointer_v2 + .p2align 4 + .type cuGraphicsResourceGetMappedPointer_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphicsResourceGetMappedPointer_v2 +#endif +cuGraphicsResourceGetMappedPointer_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2024(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2024(%rip) +2: + pushq $253 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphicsResourceSetMapFlags + .p2align 4 + .type cuGraphicsResourceSetMapFlags, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphicsResourceSetMapFlags +#endif +cuGraphicsResourceSetMapFlags: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2032(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2032(%rip) +2: + pushq $254 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphicsResourceSetMapFlags_v2 + .p2align 4 + .type cuGraphicsResourceSetMapFlags_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphicsResourceSetMapFlags_v2 +#endif +cuGraphicsResourceSetMapFlags_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2040(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2040(%rip) +2: + pushq $255 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphicsSubResourceGetMappedArray + .p2align 4 + .type cuGraphicsSubResourceGetMappedArray, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphicsSubResourceGetMappedArray +#endif +cuGraphicsSubResourceGetMappedArray: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2048(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2048(%rip) +2: + pushq $256 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphicsUnmapResources + .p2align 4 + .type cuGraphicsUnmapResources, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphicsUnmapResources +#endif +cuGraphicsUnmapResources: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2056(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2056(%rip) +2: + pushq $257 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphicsUnmapResources_ptsz + .p2align 4 + .type cuGraphicsUnmapResources_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphicsUnmapResources_ptsz +#endif +cuGraphicsUnmapResources_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2064(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2064(%rip) +2: + pushq $258 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphicsUnregisterResource + .p2align 4 + .type cuGraphicsUnregisterResource, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphicsUnregisterResource +#endif +cuGraphicsUnregisterResource: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2072(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2072(%rip) +2: + pushq $259 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphicsVDPAURegisterOutputSurface + .p2align 4 + .type cuGraphicsVDPAURegisterOutputSurface, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphicsVDPAURegisterOutputSurface +#endif +cuGraphicsVDPAURegisterOutputSurface: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2080(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2080(%rip) +2: + pushq $260 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGraphicsVDPAURegisterVideoSurface + .p2align 4 + .type cuGraphicsVDPAURegisterVideoSurface, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGraphicsVDPAURegisterVideoSurface +#endif +cuGraphicsVDPAURegisterVideoSurface: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2088(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2088(%rip) +2: + pushq $261 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGreenCtxCreate + .p2align 4 + .type cuGreenCtxCreate, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGreenCtxCreate +#endif +cuGreenCtxCreate: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2096(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2096(%rip) +2: + pushq $262 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGreenCtxDestroy + .p2align 4 + .type cuGreenCtxDestroy, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGreenCtxDestroy +#endif +cuGreenCtxDestroy: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2104(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2104(%rip) +2: + pushq $263 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGreenCtxGetDevResource + .p2align 4 + .type cuGreenCtxGetDevResource, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGreenCtxGetDevResource +#endif +cuGreenCtxGetDevResource: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2112(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2112(%rip) +2: + pushq $264 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGreenCtxRecordEvent + .p2align 4 + .type cuGreenCtxRecordEvent, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGreenCtxRecordEvent +#endif +cuGreenCtxRecordEvent: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2120(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2120(%rip) +2: + pushq $265 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGreenCtxStreamCreate + .p2align 4 + .type cuGreenCtxStreamCreate, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGreenCtxStreamCreate +#endif +cuGreenCtxStreamCreate: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2128(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2128(%rip) +2: + pushq $266 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuGreenCtxWaitEvent + .p2align 4 + .type cuGreenCtxWaitEvent, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuGreenCtxWaitEvent +#endif +cuGreenCtxWaitEvent: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2136(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2136(%rip) +2: + pushq $267 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuImportExternalMemory + .p2align 4 + .type cuImportExternalMemory, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuImportExternalMemory +#endif +cuImportExternalMemory: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2144(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2144(%rip) +2: + pushq $268 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuImportExternalSemaphore + .p2align 4 + .type cuImportExternalSemaphore, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuImportExternalSemaphore +#endif +cuImportExternalSemaphore: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2152(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2152(%rip) +2: + pushq $269 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuInit + .p2align 4 + .type cuInit, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuInit +#endif +cuInit: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2160(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2160(%rip) +2: + pushq $270 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuIpcCloseMemHandle + .p2align 4 + .type cuIpcCloseMemHandle, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuIpcCloseMemHandle +#endif +cuIpcCloseMemHandle: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2168(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2168(%rip) +2: + pushq $271 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuIpcGetEventHandle + .p2align 4 + .type cuIpcGetEventHandle, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuIpcGetEventHandle +#endif +cuIpcGetEventHandle: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2176(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2176(%rip) +2: + pushq $272 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuIpcGetMemHandle + .p2align 4 + .type cuIpcGetMemHandle, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuIpcGetMemHandle +#endif +cuIpcGetMemHandle: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2184(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2184(%rip) +2: + pushq $273 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuIpcOpenEventHandle + .p2align 4 + .type cuIpcOpenEventHandle, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuIpcOpenEventHandle +#endif +cuIpcOpenEventHandle: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2192(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2192(%rip) +2: + pushq $274 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuIpcOpenMemHandle + .p2align 4 + .type cuIpcOpenMemHandle, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuIpcOpenMemHandle +#endif +cuIpcOpenMemHandle: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2200(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2200(%rip) +2: + pushq $275 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuIpcOpenMemHandle_v2 + .p2align 4 + .type cuIpcOpenMemHandle_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuIpcOpenMemHandle_v2 +#endif +cuIpcOpenMemHandle_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2208(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2208(%rip) +2: + pushq $276 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuKernelGetAttribute + .p2align 4 + .type cuKernelGetAttribute, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuKernelGetAttribute +#endif +cuKernelGetAttribute: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2216(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2216(%rip) +2: + pushq $277 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuKernelGetFunction + .p2align 4 + .type cuKernelGetFunction, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuKernelGetFunction +#endif +cuKernelGetFunction: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2224(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2224(%rip) +2: + pushq $278 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuKernelGetLibrary + .p2align 4 + .type cuKernelGetLibrary, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuKernelGetLibrary +#endif +cuKernelGetLibrary: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2232(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2232(%rip) +2: + pushq $279 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuKernelGetName + .p2align 4 + .type cuKernelGetName, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuKernelGetName +#endif +cuKernelGetName: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2240(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2240(%rip) +2: + pushq $280 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuKernelGetParamInfo + .p2align 4 + .type cuKernelGetParamInfo, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuKernelGetParamInfo +#endif +cuKernelGetParamInfo: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2248(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2248(%rip) +2: + pushq $281 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuKernelSetAttribute + .p2align 4 + .type cuKernelSetAttribute, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuKernelSetAttribute +#endif +cuKernelSetAttribute: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2256(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2256(%rip) +2: + pushq $282 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuKernelSetCacheConfig + .p2align 4 + .type cuKernelSetCacheConfig, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuKernelSetCacheConfig +#endif +cuKernelSetCacheConfig: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2264(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2264(%rip) +2: + pushq $283 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuLaunch + .p2align 4 + .type cuLaunch, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuLaunch +#endif +cuLaunch: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2272(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2272(%rip) +2: + pushq $284 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuLaunchCooperativeKernel + .p2align 4 + .type cuLaunchCooperativeKernel, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuLaunchCooperativeKernel +#endif +cuLaunchCooperativeKernel: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2280(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2280(%rip) +2: + pushq $285 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuLaunchCooperativeKernelMultiDevice + .p2align 4 + .type cuLaunchCooperativeKernelMultiDevice, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuLaunchCooperativeKernelMultiDevice +#endif +cuLaunchCooperativeKernelMultiDevice: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2288(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2288(%rip) +2: + pushq $286 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuLaunchCooperativeKernel_ptsz + .p2align 4 + .type cuLaunchCooperativeKernel_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuLaunchCooperativeKernel_ptsz +#endif +cuLaunchCooperativeKernel_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2296(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2296(%rip) +2: + pushq $287 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuLaunchGrid + .p2align 4 + .type cuLaunchGrid, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuLaunchGrid +#endif +cuLaunchGrid: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2304(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2304(%rip) +2: + pushq $288 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuLaunchGridAsync + .p2align 4 + .type cuLaunchGridAsync, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuLaunchGridAsync +#endif +cuLaunchGridAsync: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2312(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2312(%rip) +2: + pushq $289 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuLaunchHostFunc + .p2align 4 + .type cuLaunchHostFunc, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuLaunchHostFunc +#endif +cuLaunchHostFunc: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2320(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2320(%rip) +2: + pushq $290 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuLaunchHostFunc_ptsz + .p2align 4 + .type cuLaunchHostFunc_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuLaunchHostFunc_ptsz +#endif +cuLaunchHostFunc_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2328(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2328(%rip) +2: + pushq $291 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuLaunchKernel + .p2align 4 + .type cuLaunchKernel, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuLaunchKernel +#endif +cuLaunchKernel: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2336(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2336(%rip) +2: + pushq $292 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuLaunchKernelEx + .p2align 4 + .type cuLaunchKernelEx, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuLaunchKernelEx +#endif +cuLaunchKernelEx: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2344(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2344(%rip) +2: + pushq $293 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuLaunchKernelEx_ptsz + .p2align 4 + .type cuLaunchKernelEx_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuLaunchKernelEx_ptsz +#endif +cuLaunchKernelEx_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2352(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2352(%rip) +2: + pushq $294 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuLaunchKernel_ptsz + .p2align 4 + .type cuLaunchKernel_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuLaunchKernel_ptsz +#endif +cuLaunchKernel_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2360(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2360(%rip) +2: + pushq $295 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuLibraryEnumerateKernels + .p2align 4 + .type cuLibraryEnumerateKernels, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuLibraryEnumerateKernels +#endif +cuLibraryEnumerateKernels: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2368(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2368(%rip) +2: + pushq $296 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuLibraryGetGlobal + .p2align 4 + .type cuLibraryGetGlobal, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuLibraryGetGlobal +#endif +cuLibraryGetGlobal: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2376(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2376(%rip) +2: + pushq $297 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuLibraryGetKernel + .p2align 4 + .type cuLibraryGetKernel, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuLibraryGetKernel +#endif +cuLibraryGetKernel: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2384(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2384(%rip) +2: + pushq $298 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuLibraryGetKernelCount + .p2align 4 + .type cuLibraryGetKernelCount, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuLibraryGetKernelCount +#endif +cuLibraryGetKernelCount: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2392(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2392(%rip) +2: + pushq $299 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuLibraryGetManaged + .p2align 4 + .type cuLibraryGetManaged, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuLibraryGetManaged +#endif +cuLibraryGetManaged: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2400(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2400(%rip) +2: + pushq $300 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuLibraryGetModule + .p2align 4 + .type cuLibraryGetModule, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuLibraryGetModule +#endif +cuLibraryGetModule: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2408(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2408(%rip) +2: + pushq $301 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuLibraryGetUnifiedFunction + .p2align 4 + .type cuLibraryGetUnifiedFunction, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuLibraryGetUnifiedFunction +#endif +cuLibraryGetUnifiedFunction: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2416(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2416(%rip) +2: + pushq $302 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuLibraryLoadData + .p2align 4 + .type cuLibraryLoadData, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuLibraryLoadData +#endif +cuLibraryLoadData: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2424(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2424(%rip) +2: + pushq $303 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuLibraryLoadFromFile + .p2align 4 + .type cuLibraryLoadFromFile, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuLibraryLoadFromFile +#endif +cuLibraryLoadFromFile: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2432(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2432(%rip) +2: + pushq $304 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuLibraryUnload + .p2align 4 + .type cuLibraryUnload, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuLibraryUnload +#endif +cuLibraryUnload: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2440(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2440(%rip) +2: + pushq $305 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuLinkAddData + .p2align 4 + .type cuLinkAddData, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuLinkAddData +#endif +cuLinkAddData: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2448(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2448(%rip) +2: + pushq $306 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuLinkAddData_v2 + .p2align 4 + .type cuLinkAddData_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuLinkAddData_v2 +#endif +cuLinkAddData_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2456(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2456(%rip) +2: + pushq $307 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuLinkAddFile + .p2align 4 + .type cuLinkAddFile, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuLinkAddFile +#endif +cuLinkAddFile: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2464(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2464(%rip) +2: + pushq $308 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuLinkAddFile_v2 + .p2align 4 + .type cuLinkAddFile_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuLinkAddFile_v2 +#endif +cuLinkAddFile_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2472(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2472(%rip) +2: + pushq $309 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuLinkComplete + .p2align 4 + .type cuLinkComplete, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuLinkComplete +#endif +cuLinkComplete: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2480(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2480(%rip) +2: + pushq $310 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuLinkCreate + .p2align 4 + .type cuLinkCreate, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuLinkCreate +#endif +cuLinkCreate: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2488(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2488(%rip) +2: + pushq $311 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuLinkCreate_v2 + .p2align 4 + .type cuLinkCreate_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuLinkCreate_v2 +#endif +cuLinkCreate_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2496(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2496(%rip) +2: + pushq $312 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuLinkDestroy + .p2align 4 + .type cuLinkDestroy, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuLinkDestroy +#endif +cuLinkDestroy: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2504(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2504(%rip) +2: + pushq $313 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemAddressFree + .p2align 4 + .type cuMemAddressFree, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemAddressFree +#endif +cuMemAddressFree: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2512(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2512(%rip) +2: + pushq $314 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemAddressReserve + .p2align 4 + .type cuMemAddressReserve, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemAddressReserve +#endif +cuMemAddressReserve: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2520(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2520(%rip) +2: + pushq $315 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemAdvise + .p2align 4 + .type cuMemAdvise, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemAdvise +#endif +cuMemAdvise: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2528(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2528(%rip) +2: + pushq $316 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemAdvise_v2 + .p2align 4 + .type cuMemAdvise_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemAdvise_v2 +#endif +cuMemAdvise_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2536(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2536(%rip) +2: + pushq $317 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemAlloc + .p2align 4 + .type cuMemAlloc, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemAlloc +#endif +cuMemAlloc: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2544(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2544(%rip) +2: + pushq $318 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemAllocAsync + .p2align 4 + .type cuMemAllocAsync, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemAllocAsync +#endif +cuMemAllocAsync: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2552(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2552(%rip) +2: + pushq $319 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemAllocAsync_ptsz + .p2align 4 + .type cuMemAllocAsync_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemAllocAsync_ptsz +#endif +cuMemAllocAsync_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2560(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2560(%rip) +2: + pushq $320 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemAllocFromPoolAsync + .p2align 4 + .type cuMemAllocFromPoolAsync, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemAllocFromPoolAsync +#endif +cuMemAllocFromPoolAsync: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2568(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2568(%rip) +2: + pushq $321 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemAllocFromPoolAsync_ptsz + .p2align 4 + .type cuMemAllocFromPoolAsync_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemAllocFromPoolAsync_ptsz +#endif +cuMemAllocFromPoolAsync_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2576(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2576(%rip) +2: + pushq $322 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemAllocHost + .p2align 4 + .type cuMemAllocHost, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemAllocHost +#endif +cuMemAllocHost: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2584(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2584(%rip) +2: + pushq $323 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemAllocHost_v2 + .p2align 4 + .type cuMemAllocHost_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemAllocHost_v2 +#endif +cuMemAllocHost_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2592(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2592(%rip) +2: + pushq $324 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemAllocManaged + .p2align 4 + .type cuMemAllocManaged, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemAllocManaged +#endif +cuMemAllocManaged: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2600(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2600(%rip) +2: + pushq $325 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemAllocPitch + .p2align 4 + .type cuMemAllocPitch, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemAllocPitch +#endif +cuMemAllocPitch: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2608(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2608(%rip) +2: + pushq $326 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemAllocPitch_v2 + .p2align 4 + .type cuMemAllocPitch_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemAllocPitch_v2 +#endif +cuMemAllocPitch_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2616(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2616(%rip) +2: + pushq $327 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemAlloc_v2 + .p2align 4 + .type cuMemAlloc_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemAlloc_v2 +#endif +cuMemAlloc_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2624(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2624(%rip) +2: + pushq $328 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemBatchDecompressAsync + .p2align 4 + .type cuMemBatchDecompressAsync, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemBatchDecompressAsync +#endif +cuMemBatchDecompressAsync: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2632(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2632(%rip) +2: + pushq $329 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemBatchDecompressAsync_ptsz + .p2align 4 + .type cuMemBatchDecompressAsync_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemBatchDecompressAsync_ptsz +#endif +cuMemBatchDecompressAsync_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2640(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2640(%rip) +2: + pushq $330 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemCreate + .p2align 4 + .type cuMemCreate, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemCreate +#endif +cuMemCreate: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2648(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2648(%rip) +2: + pushq $331 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemExportToShareableHandle + .p2align 4 + .type cuMemExportToShareableHandle, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemExportToShareableHandle +#endif +cuMemExportToShareableHandle: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2656(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2656(%rip) +2: + pushq $332 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemFree + .p2align 4 + .type cuMemFree, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemFree +#endif +cuMemFree: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2664(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2664(%rip) +2: + pushq $333 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemFreeAsync + .p2align 4 + .type cuMemFreeAsync, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemFreeAsync +#endif +cuMemFreeAsync: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2672(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2672(%rip) +2: + pushq $334 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemFreeAsync_ptsz + .p2align 4 + .type cuMemFreeAsync_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemFreeAsync_ptsz +#endif +cuMemFreeAsync_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2680(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2680(%rip) +2: + pushq $335 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemFreeHost + .p2align 4 + .type cuMemFreeHost, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemFreeHost +#endif +cuMemFreeHost: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2688(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2688(%rip) +2: + pushq $336 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemFree_v2 + .p2align 4 + .type cuMemFree_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemFree_v2 +#endif +cuMemFree_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2696(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2696(%rip) +2: + pushq $337 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemGetAccess + .p2align 4 + .type cuMemGetAccess, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemGetAccess +#endif +cuMemGetAccess: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2704(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2704(%rip) +2: + pushq $338 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemGetAddressRange + .p2align 4 + .type cuMemGetAddressRange, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemGetAddressRange +#endif +cuMemGetAddressRange: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2712(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2712(%rip) +2: + pushq $339 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemGetAddressRange_v2 + .p2align 4 + .type cuMemGetAddressRange_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemGetAddressRange_v2 +#endif +cuMemGetAddressRange_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2720(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2720(%rip) +2: + pushq $340 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemGetAllocationGranularity + .p2align 4 + .type cuMemGetAllocationGranularity, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemGetAllocationGranularity +#endif +cuMemGetAllocationGranularity: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2728(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2728(%rip) +2: + pushq $341 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemGetAllocationPropertiesFromHandle + .p2align 4 + .type cuMemGetAllocationPropertiesFromHandle, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemGetAllocationPropertiesFromHandle +#endif +cuMemGetAllocationPropertiesFromHandle: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2736(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2736(%rip) +2: + pushq $342 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemGetAttribute + .p2align 4 + .type cuMemGetAttribute, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemGetAttribute +#endif +cuMemGetAttribute: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2744(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2744(%rip) +2: + pushq $343 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemGetAttribute_v2 + .p2align 4 + .type cuMemGetAttribute_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemGetAttribute_v2 +#endif +cuMemGetAttribute_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2752(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2752(%rip) +2: + pushq $344 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemGetHandleForAddressRange + .p2align 4 + .type cuMemGetHandleForAddressRange, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemGetHandleForAddressRange +#endif +cuMemGetHandleForAddressRange: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2760(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2760(%rip) +2: + pushq $345 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemGetInfo + .p2align 4 + .type cuMemGetInfo, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemGetInfo +#endif +cuMemGetInfo: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2768(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2768(%rip) +2: + pushq $346 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemGetInfo_v2 + .p2align 4 + .type cuMemGetInfo_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemGetInfo_v2 +#endif +cuMemGetInfo_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2776(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2776(%rip) +2: + pushq $347 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemHostAlloc + .p2align 4 + .type cuMemHostAlloc, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemHostAlloc +#endif +cuMemHostAlloc: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2784(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2784(%rip) +2: + pushq $348 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemHostGetDevicePointer + .p2align 4 + .type cuMemHostGetDevicePointer, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemHostGetDevicePointer +#endif +cuMemHostGetDevicePointer: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2792(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2792(%rip) +2: + pushq $349 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemHostGetDevicePointer_v2 + .p2align 4 + .type cuMemHostGetDevicePointer_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemHostGetDevicePointer_v2 +#endif +cuMemHostGetDevicePointer_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2800(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2800(%rip) +2: + pushq $350 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemHostGetFlags + .p2align 4 + .type cuMemHostGetFlags, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemHostGetFlags +#endif +cuMemHostGetFlags: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2808(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2808(%rip) +2: + pushq $351 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemHostRegister + .p2align 4 + .type cuMemHostRegister, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemHostRegister +#endif +cuMemHostRegister: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2816(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2816(%rip) +2: + pushq $352 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemHostRegister_v2 + .p2align 4 + .type cuMemHostRegister_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemHostRegister_v2 +#endif +cuMemHostRegister_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2824(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2824(%rip) +2: + pushq $353 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemHostUnregister + .p2align 4 + .type cuMemHostUnregister, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemHostUnregister +#endif +cuMemHostUnregister: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2832(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2832(%rip) +2: + pushq $354 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemImportFromShareableHandle + .p2align 4 + .type cuMemImportFromShareableHandle, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemImportFromShareableHandle +#endif +cuMemImportFromShareableHandle: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2840(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2840(%rip) +2: + pushq $355 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemMap + .p2align 4 + .type cuMemMap, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemMap +#endif +cuMemMap: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2848(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2848(%rip) +2: + pushq $356 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemMapArrayAsync + .p2align 4 + .type cuMemMapArrayAsync, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemMapArrayAsync +#endif +cuMemMapArrayAsync: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2856(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2856(%rip) +2: + pushq $357 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemMapArrayAsync_ptsz + .p2align 4 + .type cuMemMapArrayAsync_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemMapArrayAsync_ptsz +#endif +cuMemMapArrayAsync_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2864(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2864(%rip) +2: + pushq $358 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemPoolCreate + .p2align 4 + .type cuMemPoolCreate, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemPoolCreate +#endif +cuMemPoolCreate: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2872(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2872(%rip) +2: + pushq $359 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemPoolDestroy + .p2align 4 + .type cuMemPoolDestroy, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemPoolDestroy +#endif +cuMemPoolDestroy: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2880(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2880(%rip) +2: + pushq $360 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemPoolExportPointer + .p2align 4 + .type cuMemPoolExportPointer, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemPoolExportPointer +#endif +cuMemPoolExportPointer: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2888(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2888(%rip) +2: + pushq $361 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemPoolExportToShareableHandle + .p2align 4 + .type cuMemPoolExportToShareableHandle, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemPoolExportToShareableHandle +#endif +cuMemPoolExportToShareableHandle: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2896(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2896(%rip) +2: + pushq $362 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemPoolGetAccess + .p2align 4 + .type cuMemPoolGetAccess, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemPoolGetAccess +#endif +cuMemPoolGetAccess: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2904(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2904(%rip) +2: + pushq $363 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemPoolGetAttribute + .p2align 4 + .type cuMemPoolGetAttribute, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemPoolGetAttribute +#endif +cuMemPoolGetAttribute: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2912(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2912(%rip) +2: + pushq $364 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemPoolImportFromShareableHandle + .p2align 4 + .type cuMemPoolImportFromShareableHandle, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemPoolImportFromShareableHandle +#endif +cuMemPoolImportFromShareableHandle: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2920(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2920(%rip) +2: + pushq $365 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemPoolImportPointer + .p2align 4 + .type cuMemPoolImportPointer, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemPoolImportPointer +#endif +cuMemPoolImportPointer: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2928(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2928(%rip) +2: + pushq $366 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemPoolSetAccess + .p2align 4 + .type cuMemPoolSetAccess, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemPoolSetAccess +#endif +cuMemPoolSetAccess: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2936(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2936(%rip) +2: + pushq $367 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemPoolSetAttribute + .p2align 4 + .type cuMemPoolSetAttribute, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemPoolSetAttribute +#endif +cuMemPoolSetAttribute: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2944(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2944(%rip) +2: + pushq $368 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemPoolTrimTo + .p2align 4 + .type cuMemPoolTrimTo, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemPoolTrimTo +#endif +cuMemPoolTrimTo: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2952(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2952(%rip) +2: + pushq $369 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemPrefetchAsync + .p2align 4 + .type cuMemPrefetchAsync, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemPrefetchAsync +#endif +cuMemPrefetchAsync: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2960(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2960(%rip) +2: + pushq $370 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemPrefetchAsync_ptsz + .p2align 4 + .type cuMemPrefetchAsync_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemPrefetchAsync_ptsz +#endif +cuMemPrefetchAsync_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2968(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2968(%rip) +2: + pushq $371 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemPrefetchAsync_v2 + .p2align 4 + .type cuMemPrefetchAsync_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemPrefetchAsync_v2 +#endif +cuMemPrefetchAsync_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2976(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2976(%rip) +2: + pushq $372 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemPrefetchAsync_v2_ptsz + .p2align 4 + .type cuMemPrefetchAsync_v2_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemPrefetchAsync_v2_ptsz +#endif +cuMemPrefetchAsync_v2_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2984(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2984(%rip) +2: + pushq $373 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemRangeGetAttribute + .p2align 4 + .type cuMemRangeGetAttribute, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemRangeGetAttribute +#endif +cuMemRangeGetAttribute: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+2992(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+2992(%rip) +2: + pushq $374 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemRangeGetAttributes + .p2align 4 + .type cuMemRangeGetAttributes, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemRangeGetAttributes +#endif +cuMemRangeGetAttributes: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3000(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3000(%rip) +2: + pushq $375 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemRelease + .p2align 4 + .type cuMemRelease, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemRelease +#endif +cuMemRelease: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3008(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3008(%rip) +2: + pushq $376 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemRetainAllocationHandle + .p2align 4 + .type cuMemRetainAllocationHandle, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemRetainAllocationHandle +#endif +cuMemRetainAllocationHandle: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3016(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3016(%rip) +2: + pushq $377 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemSetAccess + .p2align 4 + .type cuMemSetAccess, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemSetAccess +#endif +cuMemSetAccess: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3024(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3024(%rip) +2: + pushq $378 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemUnmap + .p2align 4 + .type cuMemUnmap, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemUnmap +#endif +cuMemUnmap: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3032(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3032(%rip) +2: + pushq $379 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpy + .p2align 4 + .type cuMemcpy, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpy +#endif +cuMemcpy: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3040(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3040(%rip) +2: + pushq $380 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpy2D + .p2align 4 + .type cuMemcpy2D, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpy2D +#endif +cuMemcpy2D: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3048(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3048(%rip) +2: + pushq $381 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpy2DAsync + .p2align 4 + .type cuMemcpy2DAsync, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpy2DAsync +#endif +cuMemcpy2DAsync: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3056(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3056(%rip) +2: + pushq $382 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpy2DAsync_v2 + .p2align 4 + .type cuMemcpy2DAsync_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpy2DAsync_v2 +#endif +cuMemcpy2DAsync_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3064(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3064(%rip) +2: + pushq $383 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpy2DAsync_v2_ptsz + .p2align 4 + .type cuMemcpy2DAsync_v2_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpy2DAsync_v2_ptsz +#endif +cuMemcpy2DAsync_v2_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3072(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3072(%rip) +2: + pushq $384 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpy2DUnaligned + .p2align 4 + .type cuMemcpy2DUnaligned, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpy2DUnaligned +#endif +cuMemcpy2DUnaligned: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3080(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3080(%rip) +2: + pushq $385 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpy2DUnaligned_v2 + .p2align 4 + .type cuMemcpy2DUnaligned_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpy2DUnaligned_v2 +#endif +cuMemcpy2DUnaligned_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3088(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3088(%rip) +2: + pushq $386 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpy2DUnaligned_v2_ptds + .p2align 4 + .type cuMemcpy2DUnaligned_v2_ptds, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpy2DUnaligned_v2_ptds +#endif +cuMemcpy2DUnaligned_v2_ptds: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3096(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3096(%rip) +2: + pushq $387 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpy2D_v2 + .p2align 4 + .type cuMemcpy2D_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpy2D_v2 +#endif +cuMemcpy2D_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3104(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3104(%rip) +2: + pushq $388 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpy2D_v2_ptds + .p2align 4 + .type cuMemcpy2D_v2_ptds, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpy2D_v2_ptds +#endif +cuMemcpy2D_v2_ptds: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3112(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3112(%rip) +2: + pushq $389 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpy3D + .p2align 4 + .type cuMemcpy3D, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpy3D +#endif +cuMemcpy3D: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3120(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3120(%rip) +2: + pushq $390 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpy3DAsync + .p2align 4 + .type cuMemcpy3DAsync, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpy3DAsync +#endif +cuMemcpy3DAsync: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3128(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3128(%rip) +2: + pushq $391 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpy3DAsync_v2 + .p2align 4 + .type cuMemcpy3DAsync_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpy3DAsync_v2 +#endif +cuMemcpy3DAsync_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3136(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3136(%rip) +2: + pushq $392 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpy3DAsync_v2_ptsz + .p2align 4 + .type cuMemcpy3DAsync_v2_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpy3DAsync_v2_ptsz +#endif +cuMemcpy3DAsync_v2_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3144(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3144(%rip) +2: + pushq $393 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpy3DBatchAsync + .p2align 4 + .type cuMemcpy3DBatchAsync, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpy3DBatchAsync +#endif +cuMemcpy3DBatchAsync: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3152(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3152(%rip) +2: + pushq $394 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpy3DBatchAsync_ptsz + .p2align 4 + .type cuMemcpy3DBatchAsync_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpy3DBatchAsync_ptsz +#endif +cuMemcpy3DBatchAsync_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3160(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3160(%rip) +2: + pushq $395 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpy3DPeer + .p2align 4 + .type cuMemcpy3DPeer, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpy3DPeer +#endif +cuMemcpy3DPeer: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3168(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3168(%rip) +2: + pushq $396 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpy3DPeerAsync + .p2align 4 + .type cuMemcpy3DPeerAsync, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpy3DPeerAsync +#endif +cuMemcpy3DPeerAsync: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3176(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3176(%rip) +2: + pushq $397 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpy3DPeerAsync_ptsz + .p2align 4 + .type cuMemcpy3DPeerAsync_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpy3DPeerAsync_ptsz +#endif +cuMemcpy3DPeerAsync_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3184(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3184(%rip) +2: + pushq $398 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpy3DPeer_ptds + .p2align 4 + .type cuMemcpy3DPeer_ptds, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpy3DPeer_ptds +#endif +cuMemcpy3DPeer_ptds: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3192(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3192(%rip) +2: + pushq $399 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpy3D_v2 + .p2align 4 + .type cuMemcpy3D_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpy3D_v2 +#endif +cuMemcpy3D_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3200(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3200(%rip) +2: + pushq $400 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpy3D_v2_ptds + .p2align 4 + .type cuMemcpy3D_v2_ptds, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpy3D_v2_ptds +#endif +cuMemcpy3D_v2_ptds: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3208(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3208(%rip) +2: + pushq $401 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyAsync + .p2align 4 + .type cuMemcpyAsync, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyAsync +#endif +cuMemcpyAsync: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3216(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3216(%rip) +2: + pushq $402 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyAsync_ptsz + .p2align 4 + .type cuMemcpyAsync_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyAsync_ptsz +#endif +cuMemcpyAsync_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3224(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3224(%rip) +2: + pushq $403 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyAtoA + .p2align 4 + .type cuMemcpyAtoA, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyAtoA +#endif +cuMemcpyAtoA: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3232(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3232(%rip) +2: + pushq $404 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyAtoA_v2 + .p2align 4 + .type cuMemcpyAtoA_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyAtoA_v2 +#endif +cuMemcpyAtoA_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3240(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3240(%rip) +2: + pushq $405 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyAtoA_v2_ptds + .p2align 4 + .type cuMemcpyAtoA_v2_ptds, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyAtoA_v2_ptds +#endif +cuMemcpyAtoA_v2_ptds: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3248(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3248(%rip) +2: + pushq $406 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyAtoD + .p2align 4 + .type cuMemcpyAtoD, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyAtoD +#endif +cuMemcpyAtoD: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3256(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3256(%rip) +2: + pushq $407 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyAtoD_v2 + .p2align 4 + .type cuMemcpyAtoD_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyAtoD_v2 +#endif +cuMemcpyAtoD_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3264(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3264(%rip) +2: + pushq $408 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyAtoD_v2_ptds + .p2align 4 + .type cuMemcpyAtoD_v2_ptds, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyAtoD_v2_ptds +#endif +cuMemcpyAtoD_v2_ptds: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3272(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3272(%rip) +2: + pushq $409 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyAtoH + .p2align 4 + .type cuMemcpyAtoH, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyAtoH +#endif +cuMemcpyAtoH: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3280(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3280(%rip) +2: + pushq $410 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyAtoHAsync + .p2align 4 + .type cuMemcpyAtoHAsync, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyAtoHAsync +#endif +cuMemcpyAtoHAsync: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3288(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3288(%rip) +2: + pushq $411 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyAtoHAsync_v2 + .p2align 4 + .type cuMemcpyAtoHAsync_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyAtoHAsync_v2 +#endif +cuMemcpyAtoHAsync_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3296(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3296(%rip) +2: + pushq $412 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyAtoHAsync_v2_ptsz + .p2align 4 + .type cuMemcpyAtoHAsync_v2_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyAtoHAsync_v2_ptsz +#endif +cuMemcpyAtoHAsync_v2_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3304(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3304(%rip) +2: + pushq $413 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyAtoH_v2 + .p2align 4 + .type cuMemcpyAtoH_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyAtoH_v2 +#endif +cuMemcpyAtoH_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3312(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3312(%rip) +2: + pushq $414 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyAtoH_v2_ptds + .p2align 4 + .type cuMemcpyAtoH_v2_ptds, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyAtoH_v2_ptds +#endif +cuMemcpyAtoH_v2_ptds: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3320(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3320(%rip) +2: + pushq $415 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyBatchAsync + .p2align 4 + .type cuMemcpyBatchAsync, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyBatchAsync +#endif +cuMemcpyBatchAsync: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3328(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3328(%rip) +2: + pushq $416 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyBatchAsync_ptsz + .p2align 4 + .type cuMemcpyBatchAsync_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyBatchAsync_ptsz +#endif +cuMemcpyBatchAsync_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3336(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3336(%rip) +2: + pushq $417 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyDtoA + .p2align 4 + .type cuMemcpyDtoA, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyDtoA +#endif +cuMemcpyDtoA: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3344(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3344(%rip) +2: + pushq $418 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyDtoA_v2 + .p2align 4 + .type cuMemcpyDtoA_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyDtoA_v2 +#endif +cuMemcpyDtoA_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3352(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3352(%rip) +2: + pushq $419 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyDtoA_v2_ptds + .p2align 4 + .type cuMemcpyDtoA_v2_ptds, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyDtoA_v2_ptds +#endif +cuMemcpyDtoA_v2_ptds: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3360(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3360(%rip) +2: + pushq $420 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyDtoD + .p2align 4 + .type cuMemcpyDtoD, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyDtoD +#endif +cuMemcpyDtoD: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3368(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3368(%rip) +2: + pushq $421 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyDtoDAsync + .p2align 4 + .type cuMemcpyDtoDAsync, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyDtoDAsync +#endif +cuMemcpyDtoDAsync: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3376(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3376(%rip) +2: + pushq $422 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyDtoDAsync_v2 + .p2align 4 + .type cuMemcpyDtoDAsync_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyDtoDAsync_v2 +#endif +cuMemcpyDtoDAsync_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3384(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3384(%rip) +2: + pushq $423 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyDtoDAsync_v2_ptsz + .p2align 4 + .type cuMemcpyDtoDAsync_v2_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyDtoDAsync_v2_ptsz +#endif +cuMemcpyDtoDAsync_v2_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3392(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3392(%rip) +2: + pushq $424 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyDtoD_v2 + .p2align 4 + .type cuMemcpyDtoD_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyDtoD_v2 +#endif +cuMemcpyDtoD_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3400(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3400(%rip) +2: + pushq $425 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyDtoD_v2_ptds + .p2align 4 + .type cuMemcpyDtoD_v2_ptds, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyDtoD_v2_ptds +#endif +cuMemcpyDtoD_v2_ptds: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3408(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3408(%rip) +2: + pushq $426 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyDtoH + .p2align 4 + .type cuMemcpyDtoH, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyDtoH +#endif +cuMemcpyDtoH: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3416(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3416(%rip) +2: + pushq $427 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyDtoHAsync + .p2align 4 + .type cuMemcpyDtoHAsync, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyDtoHAsync +#endif +cuMemcpyDtoHAsync: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3424(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3424(%rip) +2: + pushq $428 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyDtoHAsync_v2 + .p2align 4 + .type cuMemcpyDtoHAsync_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyDtoHAsync_v2 +#endif +cuMemcpyDtoHAsync_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3432(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3432(%rip) +2: + pushq $429 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyDtoHAsync_v2_ptsz + .p2align 4 + .type cuMemcpyDtoHAsync_v2_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyDtoHAsync_v2_ptsz +#endif +cuMemcpyDtoHAsync_v2_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3440(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3440(%rip) +2: + pushq $430 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyDtoH_v2 + .p2align 4 + .type cuMemcpyDtoH_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyDtoH_v2 +#endif +cuMemcpyDtoH_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3448(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3448(%rip) +2: + pushq $431 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyDtoH_v2_ptds + .p2align 4 + .type cuMemcpyDtoH_v2_ptds, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyDtoH_v2_ptds +#endif +cuMemcpyDtoH_v2_ptds: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3456(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3456(%rip) +2: + pushq $432 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyHtoA + .p2align 4 + .type cuMemcpyHtoA, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyHtoA +#endif +cuMemcpyHtoA: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3464(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3464(%rip) +2: + pushq $433 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyHtoAAsync + .p2align 4 + .type cuMemcpyHtoAAsync, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyHtoAAsync +#endif +cuMemcpyHtoAAsync: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3472(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3472(%rip) +2: + pushq $434 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyHtoAAsync_v2 + .p2align 4 + .type cuMemcpyHtoAAsync_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyHtoAAsync_v2 +#endif +cuMemcpyHtoAAsync_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3480(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3480(%rip) +2: + pushq $435 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyHtoAAsync_v2_ptsz + .p2align 4 + .type cuMemcpyHtoAAsync_v2_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyHtoAAsync_v2_ptsz +#endif +cuMemcpyHtoAAsync_v2_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3488(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3488(%rip) +2: + pushq $436 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyHtoA_v2 + .p2align 4 + .type cuMemcpyHtoA_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyHtoA_v2 +#endif +cuMemcpyHtoA_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3496(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3496(%rip) +2: + pushq $437 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyHtoA_v2_ptds + .p2align 4 + .type cuMemcpyHtoA_v2_ptds, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyHtoA_v2_ptds +#endif +cuMemcpyHtoA_v2_ptds: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3504(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3504(%rip) +2: + pushq $438 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyHtoD + .p2align 4 + .type cuMemcpyHtoD, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyHtoD +#endif +cuMemcpyHtoD: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3512(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3512(%rip) +2: + pushq $439 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyHtoDAsync + .p2align 4 + .type cuMemcpyHtoDAsync, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyHtoDAsync +#endif +cuMemcpyHtoDAsync: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3520(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3520(%rip) +2: + pushq $440 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyHtoDAsync_v2 + .p2align 4 + .type cuMemcpyHtoDAsync_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyHtoDAsync_v2 +#endif +cuMemcpyHtoDAsync_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3528(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3528(%rip) +2: + pushq $441 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyHtoDAsync_v2_ptsz + .p2align 4 + .type cuMemcpyHtoDAsync_v2_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyHtoDAsync_v2_ptsz +#endif +cuMemcpyHtoDAsync_v2_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3536(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3536(%rip) +2: + pushq $442 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyHtoD_v2 + .p2align 4 + .type cuMemcpyHtoD_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyHtoD_v2 +#endif +cuMemcpyHtoD_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3544(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3544(%rip) +2: + pushq $443 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyHtoD_v2_ptds + .p2align 4 + .type cuMemcpyHtoD_v2_ptds, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyHtoD_v2_ptds +#endif +cuMemcpyHtoD_v2_ptds: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3552(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3552(%rip) +2: + pushq $444 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyPeer + .p2align 4 + .type cuMemcpyPeer, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyPeer +#endif +cuMemcpyPeer: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3560(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3560(%rip) +2: + pushq $445 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyPeerAsync + .p2align 4 + .type cuMemcpyPeerAsync, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyPeerAsync +#endif +cuMemcpyPeerAsync: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3568(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3568(%rip) +2: + pushq $446 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyPeerAsync_ptsz + .p2align 4 + .type cuMemcpyPeerAsync_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyPeerAsync_ptsz +#endif +cuMemcpyPeerAsync_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3576(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3576(%rip) +2: + pushq $447 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpyPeer_ptds + .p2align 4 + .type cuMemcpyPeer_ptds, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpyPeer_ptds +#endif +cuMemcpyPeer_ptds: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3584(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3584(%rip) +2: + pushq $448 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemcpy_ptds + .p2align 4 + .type cuMemcpy_ptds, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemcpy_ptds +#endif +cuMemcpy_ptds: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3592(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3592(%rip) +2: + pushq $449 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemsetD16 + .p2align 4 + .type cuMemsetD16, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemsetD16 +#endif +cuMemsetD16: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3600(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3600(%rip) +2: + pushq $450 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemsetD16Async + .p2align 4 + .type cuMemsetD16Async, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemsetD16Async +#endif +cuMemsetD16Async: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3608(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3608(%rip) +2: + pushq $451 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemsetD16Async_ptsz + .p2align 4 + .type cuMemsetD16Async_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemsetD16Async_ptsz +#endif +cuMemsetD16Async_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3616(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3616(%rip) +2: + pushq $452 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemsetD16_v2 + .p2align 4 + .type cuMemsetD16_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemsetD16_v2 +#endif +cuMemsetD16_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3624(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3624(%rip) +2: + pushq $453 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemsetD16_v2_ptds + .p2align 4 + .type cuMemsetD16_v2_ptds, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemsetD16_v2_ptds +#endif +cuMemsetD16_v2_ptds: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3632(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3632(%rip) +2: + pushq $454 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemsetD2D16 + .p2align 4 + .type cuMemsetD2D16, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemsetD2D16 +#endif +cuMemsetD2D16: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3640(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3640(%rip) +2: + pushq $455 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemsetD2D16Async + .p2align 4 + .type cuMemsetD2D16Async, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemsetD2D16Async +#endif +cuMemsetD2D16Async: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3648(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3648(%rip) +2: + pushq $456 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemsetD2D16Async_ptsz + .p2align 4 + .type cuMemsetD2D16Async_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemsetD2D16Async_ptsz +#endif +cuMemsetD2D16Async_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3656(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3656(%rip) +2: + pushq $457 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemsetD2D16_v2 + .p2align 4 + .type cuMemsetD2D16_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemsetD2D16_v2 +#endif +cuMemsetD2D16_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3664(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3664(%rip) +2: + pushq $458 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemsetD2D16_v2_ptds + .p2align 4 + .type cuMemsetD2D16_v2_ptds, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemsetD2D16_v2_ptds +#endif +cuMemsetD2D16_v2_ptds: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3672(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3672(%rip) +2: + pushq $459 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemsetD2D32 + .p2align 4 + .type cuMemsetD2D32, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemsetD2D32 +#endif +cuMemsetD2D32: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3680(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3680(%rip) +2: + pushq $460 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemsetD2D32Async + .p2align 4 + .type cuMemsetD2D32Async, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemsetD2D32Async +#endif +cuMemsetD2D32Async: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3688(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3688(%rip) +2: + pushq $461 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemsetD2D32Async_ptsz + .p2align 4 + .type cuMemsetD2D32Async_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemsetD2D32Async_ptsz +#endif +cuMemsetD2D32Async_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3696(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3696(%rip) +2: + pushq $462 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemsetD2D32_v2 + .p2align 4 + .type cuMemsetD2D32_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemsetD2D32_v2 +#endif +cuMemsetD2D32_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3704(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3704(%rip) +2: + pushq $463 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemsetD2D32_v2_ptds + .p2align 4 + .type cuMemsetD2D32_v2_ptds, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemsetD2D32_v2_ptds +#endif +cuMemsetD2D32_v2_ptds: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3712(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3712(%rip) +2: + pushq $464 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemsetD2D8 + .p2align 4 + .type cuMemsetD2D8, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemsetD2D8 +#endif +cuMemsetD2D8: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3720(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3720(%rip) +2: + pushq $465 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemsetD2D8Async + .p2align 4 + .type cuMemsetD2D8Async, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemsetD2D8Async +#endif +cuMemsetD2D8Async: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3728(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3728(%rip) +2: + pushq $466 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemsetD2D8Async_ptsz + .p2align 4 + .type cuMemsetD2D8Async_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemsetD2D8Async_ptsz +#endif +cuMemsetD2D8Async_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3736(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3736(%rip) +2: + pushq $467 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemsetD2D8_v2 + .p2align 4 + .type cuMemsetD2D8_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemsetD2D8_v2 +#endif +cuMemsetD2D8_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3744(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3744(%rip) +2: + pushq $468 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemsetD2D8_v2_ptds + .p2align 4 + .type cuMemsetD2D8_v2_ptds, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemsetD2D8_v2_ptds +#endif +cuMemsetD2D8_v2_ptds: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3752(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3752(%rip) +2: + pushq $469 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemsetD32 + .p2align 4 + .type cuMemsetD32, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemsetD32 +#endif +cuMemsetD32: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3760(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3760(%rip) +2: + pushq $470 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemsetD32Async + .p2align 4 + .type cuMemsetD32Async, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemsetD32Async +#endif +cuMemsetD32Async: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3768(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3768(%rip) +2: + pushq $471 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemsetD32Async_ptsz + .p2align 4 + .type cuMemsetD32Async_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemsetD32Async_ptsz +#endif +cuMemsetD32Async_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3776(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3776(%rip) +2: + pushq $472 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemsetD32_v2 + .p2align 4 + .type cuMemsetD32_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemsetD32_v2 +#endif +cuMemsetD32_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3784(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3784(%rip) +2: + pushq $473 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemsetD32_v2_ptds + .p2align 4 + .type cuMemsetD32_v2_ptds, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemsetD32_v2_ptds +#endif +cuMemsetD32_v2_ptds: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3792(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3792(%rip) +2: + pushq $474 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemsetD8 + .p2align 4 + .type cuMemsetD8, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemsetD8 +#endif +cuMemsetD8: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3800(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3800(%rip) +2: + pushq $475 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemsetD8Async + .p2align 4 + .type cuMemsetD8Async, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemsetD8Async +#endif +cuMemsetD8Async: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3808(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3808(%rip) +2: + pushq $476 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemsetD8Async_ptsz + .p2align 4 + .type cuMemsetD8Async_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemsetD8Async_ptsz +#endif +cuMemsetD8Async_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3816(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3816(%rip) +2: + pushq $477 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemsetD8_v2 + .p2align 4 + .type cuMemsetD8_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemsetD8_v2 +#endif +cuMemsetD8_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3824(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3824(%rip) +2: + pushq $478 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMemsetD8_v2_ptds + .p2align 4 + .type cuMemsetD8_v2_ptds, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMemsetD8_v2_ptds +#endif +cuMemsetD8_v2_ptds: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3832(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3832(%rip) +2: + pushq $479 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMipmappedArrayCreate + .p2align 4 + .type cuMipmappedArrayCreate, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMipmappedArrayCreate +#endif +cuMipmappedArrayCreate: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3840(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3840(%rip) +2: + pushq $480 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMipmappedArrayDestroy + .p2align 4 + .type cuMipmappedArrayDestroy, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMipmappedArrayDestroy +#endif +cuMipmappedArrayDestroy: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3848(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3848(%rip) +2: + pushq $481 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMipmappedArrayGetLevel + .p2align 4 + .type cuMipmappedArrayGetLevel, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMipmappedArrayGetLevel +#endif +cuMipmappedArrayGetLevel: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3856(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3856(%rip) +2: + pushq $482 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMipmappedArrayGetMemoryRequirements + .p2align 4 + .type cuMipmappedArrayGetMemoryRequirements, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMipmappedArrayGetMemoryRequirements +#endif +cuMipmappedArrayGetMemoryRequirements: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3864(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3864(%rip) +2: + pushq $483 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMipmappedArrayGetSparseProperties + .p2align 4 + .type cuMipmappedArrayGetSparseProperties, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMipmappedArrayGetSparseProperties +#endif +cuMipmappedArrayGetSparseProperties: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3872(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3872(%rip) +2: + pushq $484 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuModuleEnumerateFunctions + .p2align 4 + .type cuModuleEnumerateFunctions, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuModuleEnumerateFunctions +#endif +cuModuleEnumerateFunctions: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3880(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3880(%rip) +2: + pushq $485 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuModuleGetFunction + .p2align 4 + .type cuModuleGetFunction, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuModuleGetFunction +#endif +cuModuleGetFunction: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3888(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3888(%rip) +2: + pushq $486 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuModuleGetFunctionCount + .p2align 4 + .type cuModuleGetFunctionCount, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuModuleGetFunctionCount +#endif +cuModuleGetFunctionCount: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3896(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3896(%rip) +2: + pushq $487 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuModuleGetGlobal + .p2align 4 + .type cuModuleGetGlobal, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuModuleGetGlobal +#endif +cuModuleGetGlobal: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3904(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3904(%rip) +2: + pushq $488 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuModuleGetGlobal_v2 + .p2align 4 + .type cuModuleGetGlobal_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuModuleGetGlobal_v2 +#endif +cuModuleGetGlobal_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3912(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3912(%rip) +2: + pushq $489 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuModuleGetLoadingMode + .p2align 4 + .type cuModuleGetLoadingMode, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuModuleGetLoadingMode +#endif +cuModuleGetLoadingMode: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3920(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3920(%rip) +2: + pushq $490 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuModuleGetSurfRef + .p2align 4 + .type cuModuleGetSurfRef, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuModuleGetSurfRef +#endif +cuModuleGetSurfRef: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3928(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3928(%rip) +2: + pushq $491 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuModuleGetTexRef + .p2align 4 + .type cuModuleGetTexRef, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuModuleGetTexRef +#endif +cuModuleGetTexRef: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3936(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3936(%rip) +2: + pushq $492 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuModuleLoad + .p2align 4 + .type cuModuleLoad, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuModuleLoad +#endif +cuModuleLoad: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3944(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3944(%rip) +2: + pushq $493 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuModuleLoadData + .p2align 4 + .type cuModuleLoadData, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuModuleLoadData +#endif +cuModuleLoadData: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3952(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3952(%rip) +2: + pushq $494 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuModuleLoadDataEx + .p2align 4 + .type cuModuleLoadDataEx, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuModuleLoadDataEx +#endif +cuModuleLoadDataEx: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3960(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3960(%rip) +2: + pushq $495 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuModuleLoadFatBinary + .p2align 4 + .type cuModuleLoadFatBinary, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuModuleLoadFatBinary +#endif +cuModuleLoadFatBinary: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3968(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3968(%rip) +2: + pushq $496 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuModuleUnload + .p2align 4 + .type cuModuleUnload, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuModuleUnload +#endif +cuModuleUnload: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3976(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3976(%rip) +2: + pushq $497 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMulticastAddDevice + .p2align 4 + .type cuMulticastAddDevice, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMulticastAddDevice +#endif +cuMulticastAddDevice: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3984(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3984(%rip) +2: + pushq $498 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMulticastBindAddr + .p2align 4 + .type cuMulticastBindAddr, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMulticastBindAddr +#endif +cuMulticastBindAddr: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+3992(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+3992(%rip) +2: + pushq $499 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMulticastBindMem + .p2align 4 + .type cuMulticastBindMem, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMulticastBindMem +#endif +cuMulticastBindMem: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4000(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4000(%rip) +2: + pushq $500 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMulticastCreate + .p2align 4 + .type cuMulticastCreate, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMulticastCreate +#endif +cuMulticastCreate: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4008(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4008(%rip) +2: + pushq $501 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMulticastGetGranularity + .p2align 4 + .type cuMulticastGetGranularity, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMulticastGetGranularity +#endif +cuMulticastGetGranularity: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4016(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4016(%rip) +2: + pushq $502 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuMulticastUnbind + .p2align 4 + .type cuMulticastUnbind, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuMulticastUnbind +#endif +cuMulticastUnbind: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4024(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4024(%rip) +2: + pushq $503 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuOccupancyAvailableDynamicSMemPerBlock + .p2align 4 + .type cuOccupancyAvailableDynamicSMemPerBlock, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuOccupancyAvailableDynamicSMemPerBlock +#endif +cuOccupancyAvailableDynamicSMemPerBlock: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4032(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4032(%rip) +2: + pushq $504 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuOccupancyMaxActiveBlocksPerMultiprocessor + .p2align 4 + .type cuOccupancyMaxActiveBlocksPerMultiprocessor, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuOccupancyMaxActiveBlocksPerMultiprocessor +#endif +cuOccupancyMaxActiveBlocksPerMultiprocessor: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4040(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4040(%rip) +2: + pushq $505 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuOccupancyMaxActiveBlocksPerMultiprocessorWithFlags + .p2align 4 + .type cuOccupancyMaxActiveBlocksPerMultiprocessorWithFlags, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuOccupancyMaxActiveBlocksPerMultiprocessorWithFlags +#endif +cuOccupancyMaxActiveBlocksPerMultiprocessorWithFlags: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4048(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4048(%rip) +2: + pushq $506 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuOccupancyMaxActiveClusters + .p2align 4 + .type cuOccupancyMaxActiveClusters, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuOccupancyMaxActiveClusters +#endif +cuOccupancyMaxActiveClusters: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4056(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4056(%rip) +2: + pushq $507 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuOccupancyMaxPotentialBlockSize + .p2align 4 + .type cuOccupancyMaxPotentialBlockSize, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuOccupancyMaxPotentialBlockSize +#endif +cuOccupancyMaxPotentialBlockSize: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4064(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4064(%rip) +2: + pushq $508 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuOccupancyMaxPotentialBlockSizeWithFlags + .p2align 4 + .type cuOccupancyMaxPotentialBlockSizeWithFlags, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuOccupancyMaxPotentialBlockSizeWithFlags +#endif +cuOccupancyMaxPotentialBlockSizeWithFlags: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4072(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4072(%rip) +2: + pushq $509 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuOccupancyMaxPotentialClusterSize + .p2align 4 + .type cuOccupancyMaxPotentialClusterSize, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuOccupancyMaxPotentialClusterSize +#endif +cuOccupancyMaxPotentialClusterSize: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4080(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4080(%rip) +2: + pushq $510 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuParamSetSize + .p2align 4 + .type cuParamSetSize, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuParamSetSize +#endif +cuParamSetSize: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4088(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4088(%rip) +2: + pushq $511 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuParamSetTexRef + .p2align 4 + .type cuParamSetTexRef, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuParamSetTexRef +#endif +cuParamSetTexRef: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4096(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4096(%rip) +2: + pushq $512 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuParamSetf + .p2align 4 + .type cuParamSetf, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuParamSetf +#endif +cuParamSetf: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4104(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4104(%rip) +2: + pushq $513 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuParamSeti + .p2align 4 + .type cuParamSeti, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuParamSeti +#endif +cuParamSeti: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4112(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4112(%rip) +2: + pushq $514 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuParamSetv + .p2align 4 + .type cuParamSetv, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuParamSetv +#endif +cuParamSetv: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4120(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4120(%rip) +2: + pushq $515 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuPointerGetAttribute + .p2align 4 + .type cuPointerGetAttribute, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuPointerGetAttribute +#endif +cuPointerGetAttribute: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4128(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4128(%rip) +2: + pushq $516 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuPointerGetAttributes + .p2align 4 + .type cuPointerGetAttributes, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuPointerGetAttributes +#endif +cuPointerGetAttributes: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4136(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4136(%rip) +2: + pushq $517 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuPointerSetAttribute + .p2align 4 + .type cuPointerSetAttribute, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuPointerSetAttribute +#endif +cuPointerSetAttribute: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4144(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4144(%rip) +2: + pushq $518 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuProfilerInitialize + .p2align 4 + .type cuProfilerInitialize, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuProfilerInitialize +#endif +cuProfilerInitialize: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4152(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4152(%rip) +2: + pushq $519 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuProfilerStart + .p2align 4 + .type cuProfilerStart, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuProfilerStart +#endif +cuProfilerStart: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4160(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4160(%rip) +2: + pushq $520 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuProfilerStop + .p2align 4 + .type cuProfilerStop, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuProfilerStop +#endif +cuProfilerStop: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4168(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4168(%rip) +2: + pushq $521 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuSignalExternalSemaphoresAsync + .p2align 4 + .type cuSignalExternalSemaphoresAsync, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuSignalExternalSemaphoresAsync +#endif +cuSignalExternalSemaphoresAsync: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4176(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4176(%rip) +2: + pushq $522 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuSignalExternalSemaphoresAsync_ptsz + .p2align 4 + .type cuSignalExternalSemaphoresAsync_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuSignalExternalSemaphoresAsync_ptsz +#endif +cuSignalExternalSemaphoresAsync_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4184(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4184(%rip) +2: + pushq $523 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamAddCallback + .p2align 4 + .type cuStreamAddCallback, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamAddCallback +#endif +cuStreamAddCallback: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4192(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4192(%rip) +2: + pushq $524 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamAddCallback_ptsz + .p2align 4 + .type cuStreamAddCallback_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamAddCallback_ptsz +#endif +cuStreamAddCallback_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4200(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4200(%rip) +2: + pushq $525 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamAttachMemAsync + .p2align 4 + .type cuStreamAttachMemAsync, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamAttachMemAsync +#endif +cuStreamAttachMemAsync: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4208(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4208(%rip) +2: + pushq $526 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamAttachMemAsync_ptsz + .p2align 4 + .type cuStreamAttachMemAsync_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamAttachMemAsync_ptsz +#endif +cuStreamAttachMemAsync_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4216(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4216(%rip) +2: + pushq $527 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamBatchMemOp + .p2align 4 + .type cuStreamBatchMemOp, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamBatchMemOp +#endif +cuStreamBatchMemOp: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4224(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4224(%rip) +2: + pushq $528 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamBatchMemOp_ptsz + .p2align 4 + .type cuStreamBatchMemOp_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamBatchMemOp_ptsz +#endif +cuStreamBatchMemOp_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4232(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4232(%rip) +2: + pushq $529 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamBatchMemOp_v2 + .p2align 4 + .type cuStreamBatchMemOp_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamBatchMemOp_v2 +#endif +cuStreamBatchMemOp_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4240(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4240(%rip) +2: + pushq $530 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamBatchMemOp_v2_ptsz + .p2align 4 + .type cuStreamBatchMemOp_v2_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamBatchMemOp_v2_ptsz +#endif +cuStreamBatchMemOp_v2_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4248(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4248(%rip) +2: + pushq $531 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamBeginCapture + .p2align 4 + .type cuStreamBeginCapture, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamBeginCapture +#endif +cuStreamBeginCapture: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4256(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4256(%rip) +2: + pushq $532 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamBeginCaptureToGraph + .p2align 4 + .type cuStreamBeginCaptureToGraph, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamBeginCaptureToGraph +#endif +cuStreamBeginCaptureToGraph: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4264(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4264(%rip) +2: + pushq $533 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamBeginCaptureToGraph_ptsz + .p2align 4 + .type cuStreamBeginCaptureToGraph_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamBeginCaptureToGraph_ptsz +#endif +cuStreamBeginCaptureToGraph_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4272(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4272(%rip) +2: + pushq $534 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamBeginCapture_ptsz + .p2align 4 + .type cuStreamBeginCapture_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamBeginCapture_ptsz +#endif +cuStreamBeginCapture_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4280(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4280(%rip) +2: + pushq $535 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamBeginCapture_v2 + .p2align 4 + .type cuStreamBeginCapture_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamBeginCapture_v2 +#endif +cuStreamBeginCapture_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4288(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4288(%rip) +2: + pushq $536 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamBeginCapture_v2_ptsz + .p2align 4 + .type cuStreamBeginCapture_v2_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamBeginCapture_v2_ptsz +#endif +cuStreamBeginCapture_v2_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4296(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4296(%rip) +2: + pushq $537 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamCopyAttributes + .p2align 4 + .type cuStreamCopyAttributes, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamCopyAttributes +#endif +cuStreamCopyAttributes: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4304(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4304(%rip) +2: + pushq $538 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamCopyAttributes_ptsz + .p2align 4 + .type cuStreamCopyAttributes_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamCopyAttributes_ptsz +#endif +cuStreamCopyAttributes_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4312(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4312(%rip) +2: + pushq $539 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamCreate + .p2align 4 + .type cuStreamCreate, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamCreate +#endif +cuStreamCreate: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4320(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4320(%rip) +2: + pushq $540 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamCreateWithPriority + .p2align 4 + .type cuStreamCreateWithPriority, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamCreateWithPriority +#endif +cuStreamCreateWithPriority: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4328(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4328(%rip) +2: + pushq $541 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamDestroy + .p2align 4 + .type cuStreamDestroy, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamDestroy +#endif +cuStreamDestroy: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4336(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4336(%rip) +2: + pushq $542 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamDestroy_v2 + .p2align 4 + .type cuStreamDestroy_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamDestroy_v2 +#endif +cuStreamDestroy_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4344(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4344(%rip) +2: + pushq $543 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamEndCapture + .p2align 4 + .type cuStreamEndCapture, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamEndCapture +#endif +cuStreamEndCapture: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4352(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4352(%rip) +2: + pushq $544 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamEndCapture_ptsz + .p2align 4 + .type cuStreamEndCapture_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamEndCapture_ptsz +#endif +cuStreamEndCapture_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4360(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4360(%rip) +2: + pushq $545 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamGetAttribute + .p2align 4 + .type cuStreamGetAttribute, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamGetAttribute +#endif +cuStreamGetAttribute: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4368(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4368(%rip) +2: + pushq $546 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamGetAttribute_ptsz + .p2align 4 + .type cuStreamGetAttribute_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamGetAttribute_ptsz +#endif +cuStreamGetAttribute_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4376(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4376(%rip) +2: + pushq $547 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamGetCaptureInfo + .p2align 4 + .type cuStreamGetCaptureInfo, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamGetCaptureInfo +#endif +cuStreamGetCaptureInfo: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4384(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4384(%rip) +2: + pushq $548 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamGetCaptureInfo_ptsz + .p2align 4 + .type cuStreamGetCaptureInfo_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamGetCaptureInfo_ptsz +#endif +cuStreamGetCaptureInfo_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4392(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4392(%rip) +2: + pushq $549 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamGetCaptureInfo_v2 + .p2align 4 + .type cuStreamGetCaptureInfo_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamGetCaptureInfo_v2 +#endif +cuStreamGetCaptureInfo_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4400(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4400(%rip) +2: + pushq $550 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamGetCaptureInfo_v2_ptsz + .p2align 4 + .type cuStreamGetCaptureInfo_v2_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamGetCaptureInfo_v2_ptsz +#endif +cuStreamGetCaptureInfo_v2_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4408(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4408(%rip) +2: + pushq $551 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamGetCaptureInfo_v3 + .p2align 4 + .type cuStreamGetCaptureInfo_v3, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamGetCaptureInfo_v3 +#endif +cuStreamGetCaptureInfo_v3: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4416(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4416(%rip) +2: + pushq $552 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamGetCaptureInfo_v3_ptsz + .p2align 4 + .type cuStreamGetCaptureInfo_v3_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamGetCaptureInfo_v3_ptsz +#endif +cuStreamGetCaptureInfo_v3_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4424(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4424(%rip) +2: + pushq $553 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamGetCtx + .p2align 4 + .type cuStreamGetCtx, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamGetCtx +#endif +cuStreamGetCtx: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4432(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4432(%rip) +2: + pushq $554 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamGetCtx_ptsz + .p2align 4 + .type cuStreamGetCtx_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamGetCtx_ptsz +#endif +cuStreamGetCtx_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4440(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4440(%rip) +2: + pushq $555 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamGetCtx_v2 + .p2align 4 + .type cuStreamGetCtx_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamGetCtx_v2 +#endif +cuStreamGetCtx_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4448(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4448(%rip) +2: + pushq $556 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamGetCtx_v2_ptsz + .p2align 4 + .type cuStreamGetCtx_v2_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamGetCtx_v2_ptsz +#endif +cuStreamGetCtx_v2_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4456(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4456(%rip) +2: + pushq $557 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamGetDevice + .p2align 4 + .type cuStreamGetDevice, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamGetDevice +#endif +cuStreamGetDevice: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4464(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4464(%rip) +2: + pushq $558 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamGetDevice_ptsz + .p2align 4 + .type cuStreamGetDevice_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamGetDevice_ptsz +#endif +cuStreamGetDevice_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4472(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4472(%rip) +2: + pushq $559 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamGetFlags + .p2align 4 + .type cuStreamGetFlags, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamGetFlags +#endif +cuStreamGetFlags: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4480(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4480(%rip) +2: + pushq $560 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamGetFlags_ptsz + .p2align 4 + .type cuStreamGetFlags_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamGetFlags_ptsz +#endif +cuStreamGetFlags_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4488(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4488(%rip) +2: + pushq $561 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamGetGreenCtx + .p2align 4 + .type cuStreamGetGreenCtx, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamGetGreenCtx +#endif +cuStreamGetGreenCtx: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4496(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4496(%rip) +2: + pushq $562 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamGetId + .p2align 4 + .type cuStreamGetId, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamGetId +#endif +cuStreamGetId: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4504(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4504(%rip) +2: + pushq $563 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamGetId_ptsz + .p2align 4 + .type cuStreamGetId_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamGetId_ptsz +#endif +cuStreamGetId_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4512(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4512(%rip) +2: + pushq $564 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamGetPriority + .p2align 4 + .type cuStreamGetPriority, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamGetPriority +#endif +cuStreamGetPriority: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4520(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4520(%rip) +2: + pushq $565 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamGetPriority_ptsz + .p2align 4 + .type cuStreamGetPriority_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamGetPriority_ptsz +#endif +cuStreamGetPriority_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4528(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4528(%rip) +2: + pushq $566 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamIsCapturing + .p2align 4 + .type cuStreamIsCapturing, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamIsCapturing +#endif +cuStreamIsCapturing: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4536(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4536(%rip) +2: + pushq $567 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamIsCapturing_ptsz + .p2align 4 + .type cuStreamIsCapturing_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamIsCapturing_ptsz +#endif +cuStreamIsCapturing_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4544(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4544(%rip) +2: + pushq $568 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamQuery + .p2align 4 + .type cuStreamQuery, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamQuery +#endif +cuStreamQuery: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4552(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4552(%rip) +2: + pushq $569 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamQuery_ptsz + .p2align 4 + .type cuStreamQuery_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamQuery_ptsz +#endif +cuStreamQuery_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4560(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4560(%rip) +2: + pushq $570 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamSetAttribute + .p2align 4 + .type cuStreamSetAttribute, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamSetAttribute +#endif +cuStreamSetAttribute: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4568(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4568(%rip) +2: + pushq $571 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamSetAttribute_ptsz + .p2align 4 + .type cuStreamSetAttribute_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamSetAttribute_ptsz +#endif +cuStreamSetAttribute_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4576(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4576(%rip) +2: + pushq $572 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamSynchronize + .p2align 4 + .type cuStreamSynchronize, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamSynchronize +#endif +cuStreamSynchronize: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4584(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4584(%rip) +2: + pushq $573 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamSynchronize_ptsz + .p2align 4 + .type cuStreamSynchronize_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamSynchronize_ptsz +#endif +cuStreamSynchronize_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4592(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4592(%rip) +2: + pushq $574 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamUpdateCaptureDependencies + .p2align 4 + .type cuStreamUpdateCaptureDependencies, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamUpdateCaptureDependencies +#endif +cuStreamUpdateCaptureDependencies: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4600(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4600(%rip) +2: + pushq $575 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamUpdateCaptureDependencies_ptsz + .p2align 4 + .type cuStreamUpdateCaptureDependencies_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamUpdateCaptureDependencies_ptsz +#endif +cuStreamUpdateCaptureDependencies_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4608(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4608(%rip) +2: + pushq $576 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamUpdateCaptureDependencies_v2 + .p2align 4 + .type cuStreamUpdateCaptureDependencies_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamUpdateCaptureDependencies_v2 +#endif +cuStreamUpdateCaptureDependencies_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4616(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4616(%rip) +2: + pushq $577 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamUpdateCaptureDependencies_v2_ptsz + .p2align 4 + .type cuStreamUpdateCaptureDependencies_v2_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamUpdateCaptureDependencies_v2_ptsz +#endif +cuStreamUpdateCaptureDependencies_v2_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4624(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4624(%rip) +2: + pushq $578 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamWaitEvent + .p2align 4 + .type cuStreamWaitEvent, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamWaitEvent +#endif +cuStreamWaitEvent: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4632(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4632(%rip) +2: + pushq $579 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamWaitEvent_ptsz + .p2align 4 + .type cuStreamWaitEvent_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamWaitEvent_ptsz +#endif +cuStreamWaitEvent_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4640(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4640(%rip) +2: + pushq $580 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamWaitValue32 + .p2align 4 + .type cuStreamWaitValue32, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamWaitValue32 +#endif +cuStreamWaitValue32: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4648(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4648(%rip) +2: + pushq $581 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamWaitValue32_ptsz + .p2align 4 + .type cuStreamWaitValue32_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamWaitValue32_ptsz +#endif +cuStreamWaitValue32_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4656(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4656(%rip) +2: + pushq $582 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamWaitValue32_v2 + .p2align 4 + .type cuStreamWaitValue32_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamWaitValue32_v2 +#endif +cuStreamWaitValue32_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4664(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4664(%rip) +2: + pushq $583 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamWaitValue32_v2_ptsz + .p2align 4 + .type cuStreamWaitValue32_v2_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamWaitValue32_v2_ptsz +#endif +cuStreamWaitValue32_v2_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4672(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4672(%rip) +2: + pushq $584 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamWaitValue64 + .p2align 4 + .type cuStreamWaitValue64, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamWaitValue64 +#endif +cuStreamWaitValue64: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4680(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4680(%rip) +2: + pushq $585 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamWaitValue64_ptsz + .p2align 4 + .type cuStreamWaitValue64_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamWaitValue64_ptsz +#endif +cuStreamWaitValue64_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4688(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4688(%rip) +2: + pushq $586 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamWaitValue64_v2 + .p2align 4 + .type cuStreamWaitValue64_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamWaitValue64_v2 +#endif +cuStreamWaitValue64_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4696(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4696(%rip) +2: + pushq $587 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamWaitValue64_v2_ptsz + .p2align 4 + .type cuStreamWaitValue64_v2_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamWaitValue64_v2_ptsz +#endif +cuStreamWaitValue64_v2_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4704(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4704(%rip) +2: + pushq $588 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamWriteValue32 + .p2align 4 + .type cuStreamWriteValue32, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamWriteValue32 +#endif +cuStreamWriteValue32: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4712(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4712(%rip) +2: + pushq $589 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamWriteValue32_ptsz + .p2align 4 + .type cuStreamWriteValue32_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamWriteValue32_ptsz +#endif +cuStreamWriteValue32_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4720(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4720(%rip) +2: + pushq $590 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamWriteValue32_v2 + .p2align 4 + .type cuStreamWriteValue32_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamWriteValue32_v2 +#endif +cuStreamWriteValue32_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4728(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4728(%rip) +2: + pushq $591 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamWriteValue32_v2_ptsz + .p2align 4 + .type cuStreamWriteValue32_v2_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamWriteValue32_v2_ptsz +#endif +cuStreamWriteValue32_v2_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4736(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4736(%rip) +2: + pushq $592 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamWriteValue64 + .p2align 4 + .type cuStreamWriteValue64, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamWriteValue64 +#endif +cuStreamWriteValue64: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4744(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4744(%rip) +2: + pushq $593 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamWriteValue64_ptsz + .p2align 4 + .type cuStreamWriteValue64_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamWriteValue64_ptsz +#endif +cuStreamWriteValue64_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4752(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4752(%rip) +2: + pushq $594 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamWriteValue64_v2 + .p2align 4 + .type cuStreamWriteValue64_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamWriteValue64_v2 +#endif +cuStreamWriteValue64_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4760(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4760(%rip) +2: + pushq $595 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuStreamWriteValue64_v2_ptsz + .p2align 4 + .type cuStreamWriteValue64_v2_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuStreamWriteValue64_v2_ptsz +#endif +cuStreamWriteValue64_v2_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4768(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4768(%rip) +2: + pushq $596 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuSurfObjectCreate + .p2align 4 + .type cuSurfObjectCreate, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuSurfObjectCreate +#endif +cuSurfObjectCreate: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4776(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4776(%rip) +2: + pushq $597 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuSurfObjectDestroy + .p2align 4 + .type cuSurfObjectDestroy, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuSurfObjectDestroy +#endif +cuSurfObjectDestroy: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4784(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4784(%rip) +2: + pushq $598 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuSurfObjectGetResourceDesc + .p2align 4 + .type cuSurfObjectGetResourceDesc, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuSurfObjectGetResourceDesc +#endif +cuSurfObjectGetResourceDesc: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4792(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4792(%rip) +2: + pushq $599 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuSurfRefGetArray + .p2align 4 + .type cuSurfRefGetArray, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuSurfRefGetArray +#endif +cuSurfRefGetArray: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4800(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4800(%rip) +2: + pushq $600 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuSurfRefSetArray + .p2align 4 + .type cuSurfRefSetArray, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuSurfRefSetArray +#endif +cuSurfRefSetArray: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4808(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4808(%rip) +2: + pushq $601 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuTensorMapEncodeIm2col + .p2align 4 + .type cuTensorMapEncodeIm2col, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuTensorMapEncodeIm2col +#endif +cuTensorMapEncodeIm2col: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4816(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4816(%rip) +2: + pushq $602 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuTensorMapEncodeIm2colWide + .p2align 4 + .type cuTensorMapEncodeIm2colWide, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuTensorMapEncodeIm2colWide +#endif +cuTensorMapEncodeIm2colWide: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4824(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4824(%rip) +2: + pushq $603 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuTensorMapEncodeTiled + .p2align 4 + .type cuTensorMapEncodeTiled, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuTensorMapEncodeTiled +#endif +cuTensorMapEncodeTiled: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4832(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4832(%rip) +2: + pushq $604 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuTensorMapReplaceAddress + .p2align 4 + .type cuTensorMapReplaceAddress, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuTensorMapReplaceAddress +#endif +cuTensorMapReplaceAddress: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4840(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4840(%rip) +2: + pushq $605 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuTexObjectCreate + .p2align 4 + .type cuTexObjectCreate, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuTexObjectCreate +#endif +cuTexObjectCreate: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4848(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4848(%rip) +2: + pushq $606 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuTexObjectDestroy + .p2align 4 + .type cuTexObjectDestroy, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuTexObjectDestroy +#endif +cuTexObjectDestroy: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4856(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4856(%rip) +2: + pushq $607 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuTexObjectGetResourceDesc + .p2align 4 + .type cuTexObjectGetResourceDesc, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuTexObjectGetResourceDesc +#endif +cuTexObjectGetResourceDesc: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4864(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4864(%rip) +2: + pushq $608 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuTexObjectGetResourceViewDesc + .p2align 4 + .type cuTexObjectGetResourceViewDesc, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuTexObjectGetResourceViewDesc +#endif +cuTexObjectGetResourceViewDesc: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4872(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4872(%rip) +2: + pushq $609 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuTexObjectGetTextureDesc + .p2align 4 + .type cuTexObjectGetTextureDesc, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuTexObjectGetTextureDesc +#endif +cuTexObjectGetTextureDesc: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4880(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4880(%rip) +2: + pushq $610 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuTexRefCreate + .p2align 4 + .type cuTexRefCreate, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuTexRefCreate +#endif +cuTexRefCreate: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4888(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4888(%rip) +2: + pushq $611 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuTexRefDestroy + .p2align 4 + .type cuTexRefDestroy, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuTexRefDestroy +#endif +cuTexRefDestroy: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4896(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4896(%rip) +2: + pushq $612 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuTexRefGetAddress + .p2align 4 + .type cuTexRefGetAddress, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuTexRefGetAddress +#endif +cuTexRefGetAddress: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4904(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4904(%rip) +2: + pushq $613 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuTexRefGetAddressMode + .p2align 4 + .type cuTexRefGetAddressMode, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuTexRefGetAddressMode +#endif +cuTexRefGetAddressMode: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4912(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4912(%rip) +2: + pushq $614 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuTexRefGetAddress_v2 + .p2align 4 + .type cuTexRefGetAddress_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuTexRefGetAddress_v2 +#endif +cuTexRefGetAddress_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4920(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4920(%rip) +2: + pushq $615 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuTexRefGetArray + .p2align 4 + .type cuTexRefGetArray, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuTexRefGetArray +#endif +cuTexRefGetArray: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4928(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4928(%rip) +2: + pushq $616 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuTexRefGetBorderColor + .p2align 4 + .type cuTexRefGetBorderColor, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuTexRefGetBorderColor +#endif +cuTexRefGetBorderColor: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4936(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4936(%rip) +2: + pushq $617 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuTexRefGetFilterMode + .p2align 4 + .type cuTexRefGetFilterMode, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuTexRefGetFilterMode +#endif +cuTexRefGetFilterMode: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4944(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4944(%rip) +2: + pushq $618 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuTexRefGetFlags + .p2align 4 + .type cuTexRefGetFlags, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuTexRefGetFlags +#endif +cuTexRefGetFlags: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4952(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4952(%rip) +2: + pushq $619 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuTexRefGetFormat + .p2align 4 + .type cuTexRefGetFormat, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuTexRefGetFormat +#endif +cuTexRefGetFormat: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4960(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4960(%rip) +2: + pushq $620 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuTexRefGetMaxAnisotropy + .p2align 4 + .type cuTexRefGetMaxAnisotropy, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuTexRefGetMaxAnisotropy +#endif +cuTexRefGetMaxAnisotropy: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4968(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4968(%rip) +2: + pushq $621 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuTexRefGetMipmapFilterMode + .p2align 4 + .type cuTexRefGetMipmapFilterMode, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuTexRefGetMipmapFilterMode +#endif +cuTexRefGetMipmapFilterMode: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4976(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4976(%rip) +2: + pushq $622 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuTexRefGetMipmapLevelBias + .p2align 4 + .type cuTexRefGetMipmapLevelBias, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuTexRefGetMipmapLevelBias +#endif +cuTexRefGetMipmapLevelBias: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4984(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4984(%rip) +2: + pushq $623 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuTexRefGetMipmapLevelClamp + .p2align 4 + .type cuTexRefGetMipmapLevelClamp, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuTexRefGetMipmapLevelClamp +#endif +cuTexRefGetMipmapLevelClamp: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+4992(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+4992(%rip) +2: + pushq $624 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuTexRefGetMipmappedArray + .p2align 4 + .type cuTexRefGetMipmappedArray, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuTexRefGetMipmappedArray +#endif +cuTexRefGetMipmappedArray: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+5000(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+5000(%rip) +2: + pushq $625 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuTexRefSetAddress + .p2align 4 + .type cuTexRefSetAddress, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuTexRefSetAddress +#endif +cuTexRefSetAddress: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+5008(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+5008(%rip) +2: + pushq $626 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuTexRefSetAddress2D + .p2align 4 + .type cuTexRefSetAddress2D, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuTexRefSetAddress2D +#endif +cuTexRefSetAddress2D: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+5016(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+5016(%rip) +2: + pushq $627 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuTexRefSetAddress2D_v2 + .p2align 4 + .type cuTexRefSetAddress2D_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuTexRefSetAddress2D_v2 +#endif +cuTexRefSetAddress2D_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+5024(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+5024(%rip) +2: + pushq $628 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuTexRefSetAddress2D_v3 + .p2align 4 + .type cuTexRefSetAddress2D_v3, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuTexRefSetAddress2D_v3 +#endif +cuTexRefSetAddress2D_v3: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+5032(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+5032(%rip) +2: + pushq $629 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuTexRefSetAddressMode + .p2align 4 + .type cuTexRefSetAddressMode, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuTexRefSetAddressMode +#endif +cuTexRefSetAddressMode: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+5040(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+5040(%rip) +2: + pushq $630 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuTexRefSetAddress_v2 + .p2align 4 + .type cuTexRefSetAddress_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuTexRefSetAddress_v2 +#endif +cuTexRefSetAddress_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+5048(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+5048(%rip) +2: + pushq $631 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuTexRefSetArray + .p2align 4 + .type cuTexRefSetArray, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuTexRefSetArray +#endif +cuTexRefSetArray: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+5056(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+5056(%rip) +2: + pushq $632 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuTexRefSetBorderColor + .p2align 4 + .type cuTexRefSetBorderColor, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuTexRefSetBorderColor +#endif +cuTexRefSetBorderColor: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+5064(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+5064(%rip) +2: + pushq $633 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuTexRefSetFilterMode + .p2align 4 + .type cuTexRefSetFilterMode, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuTexRefSetFilterMode +#endif +cuTexRefSetFilterMode: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+5072(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+5072(%rip) +2: + pushq $634 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuTexRefSetFlags + .p2align 4 + .type cuTexRefSetFlags, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuTexRefSetFlags +#endif +cuTexRefSetFlags: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+5080(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+5080(%rip) +2: + pushq $635 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuTexRefSetFormat + .p2align 4 + .type cuTexRefSetFormat, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuTexRefSetFormat +#endif +cuTexRefSetFormat: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+5088(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+5088(%rip) +2: + pushq $636 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuTexRefSetMaxAnisotropy + .p2align 4 + .type cuTexRefSetMaxAnisotropy, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuTexRefSetMaxAnisotropy +#endif +cuTexRefSetMaxAnisotropy: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+5096(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+5096(%rip) +2: + pushq $637 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuTexRefSetMipmapFilterMode + .p2align 4 + .type cuTexRefSetMipmapFilterMode, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuTexRefSetMipmapFilterMode +#endif +cuTexRefSetMipmapFilterMode: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+5104(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+5104(%rip) +2: + pushq $638 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuTexRefSetMipmapLevelBias + .p2align 4 + .type cuTexRefSetMipmapLevelBias, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuTexRefSetMipmapLevelBias +#endif +cuTexRefSetMipmapLevelBias: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+5112(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+5112(%rip) +2: + pushq $639 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuTexRefSetMipmapLevelClamp + .p2align 4 + .type cuTexRefSetMipmapLevelClamp, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuTexRefSetMipmapLevelClamp +#endif +cuTexRefSetMipmapLevelClamp: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+5120(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+5120(%rip) +2: + pushq $640 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuTexRefSetMipmappedArray + .p2align 4 + .type cuTexRefSetMipmappedArray, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuTexRefSetMipmappedArray +#endif +cuTexRefSetMipmappedArray: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+5128(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+5128(%rip) +2: + pushq $641 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuThreadExchangeStreamCaptureMode + .p2align 4 + .type cuThreadExchangeStreamCaptureMode, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuThreadExchangeStreamCaptureMode +#endif +cuThreadExchangeStreamCaptureMode: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+5136(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+5136(%rip) +2: + pushq $642 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuUserObjectCreate + .p2align 4 + .type cuUserObjectCreate, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuUserObjectCreate +#endif +cuUserObjectCreate: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+5144(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+5144(%rip) +2: + pushq $643 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuUserObjectRelease + .p2align 4 + .type cuUserObjectRelease, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuUserObjectRelease +#endif +cuUserObjectRelease: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+5152(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+5152(%rip) +2: + pushq $644 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuUserObjectRetain + .p2align 4 + .type cuUserObjectRetain, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuUserObjectRetain +#endif +cuUserObjectRetain: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+5160(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+5160(%rip) +2: + pushq $645 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuVDPAUCtxCreate + .p2align 4 + .type cuVDPAUCtxCreate, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuVDPAUCtxCreate +#endif +cuVDPAUCtxCreate: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+5168(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+5168(%rip) +2: + pushq $646 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuVDPAUCtxCreate_v2 + .p2align 4 + .type cuVDPAUCtxCreate_v2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuVDPAUCtxCreate_v2 +#endif +cuVDPAUCtxCreate_v2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+5176(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+5176(%rip) +2: + pushq $647 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuVDPAUGetDevice + .p2align 4 + .type cuVDPAUGetDevice, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuVDPAUGetDevice +#endif +cuVDPAUGetDevice: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+5184(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+5184(%rip) +2: + pushq $648 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuWaitExternalSemaphoresAsync + .p2align 4 + .type cuWaitExternalSemaphoresAsync, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuWaitExternalSemaphoresAsync +#endif +cuWaitExternalSemaphoresAsync: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+5192(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+5192(%rip) +2: + pushq $649 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuWaitExternalSemaphoresAsync_ptsz + .p2align 4 + .type cuWaitExternalSemaphoresAsync_ptsz, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuWaitExternalSemaphoresAsync_ptsz +#endif +cuWaitExternalSemaphoresAsync_ptsz: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+5200(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+5200(%rip) +2: + pushq $650 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cudbgApiAttach + .p2align 4 + .type cudbgApiAttach, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cudbgApiAttach +#endif +cudbgApiAttach: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+5208(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+5208(%rip) +2: + pushq $651 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cudbgApiDetach + .p2align 4 + .type cudbgApiDetach, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cudbgApiDetach +#endif +cudbgApiDetach: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+5216(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+5216(%rip) +2: + pushq $652 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cudbgApiInit + .p2align 4 + .type cudbgApiInit, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cudbgApiInit +#endif +cudbgApiInit: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+5224(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+5224(%rip) +2: + pushq $653 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cudbgGetAPI + .p2align 4 + .type cudbgGetAPI, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cudbgGetAPI +#endif +cudbgGetAPI: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+5232(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+5232(%rip) +2: + pushq $654 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cudbgGetAPIVersion + .p2align 4 + .type cudbgGetAPIVersion, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cudbgGetAPIVersion +#endif +cudbgGetAPIVersion: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+5240(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+5240(%rip) +2: + pushq $655 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cudbgMain + .p2align 4 + .type cudbgMain, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cudbgMain +#endif +cudbgMain: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+5248(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+5248(%rip) +2: + pushq $656 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cudbgReportDriverApiError + .p2align 4 + .type cudbgReportDriverApiError, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cudbgReportDriverApiError +#endif +cudbgReportDriverApiError: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+5256(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+5256(%rip) +2: + pushq $657 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cudbgReportDriverInternalError + .p2align 4 + .type cudbgReportDriverInternalError, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cudbgReportDriverInternalError +#endif +cudbgReportDriverInternalError: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libcuda_so_tramp_table+5264(%rip) + je 2f +1: + jmp *_libcuda_so_tramp_table+5264(%rip) +2: + pushq $658 + .cfi_adjust_cfa_offset 8 + call _libcuda_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + diff --git a/webrtc-sys/src/nvidia/implib/libnvcuvid.so.init.c b/webrtc-sys/src/nvidia/implib/libnvcuvid.so.init.c new file mode 100644 index 000000000..472d99d67 --- /dev/null +++ b/webrtc-sys/src/nvidia/implib/libnvcuvid.so.init.c @@ -0,0 +1,282 @@ +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE // For RTLD_DEFAULT +#endif + +#define HAS_DLOPEN_CALLBACK 0 +#define HAS_DLSYM_CALLBACK 0 +#define NO_DLOPEN 0 +#define LAZY_LOAD 1 +#define THREAD_SAFE 1 + +#include +#include +#include +#include +#include + +#if THREAD_SAFE +#include +#endif + +// Sanity check for ARM to avoid puzzling runtime crashes +#ifdef __arm__ +# if defined __thumb__ && ! defined __THUMB_INTERWORK__ +# error "ARM trampolines need -mthumb-interwork to work in Thumb mode" +# endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define CHECK(cond, fmt, ...) do { \ + if(!(cond)) { \ + fprintf(stderr, "implib-gen: libnvcuvid.so.1: " fmt "\n", ##__VA_ARGS__); \ + assert(0 && "Assertion in generated code"); \ + abort(); \ + } \ + } while(0) + +static void *lib_handle; +static int dlopened; + +#if ! NO_DLOPEN + +#if THREAD_SAFE + +// We need to consider two cases: +// - different threads calling intercepted APIs in parallel +// - same thread calling 2 intercepted APIs recursively +// due to dlopen calling library constructors +// (usually happens only under IMPLIB_EXPORT_SHIMS) + +// Current recursive mutex approach will deadlock +// if library constructor starts and joins a new thread +// which (directly or indirectly) calls another library function. +// Such situations should be very rare (although chances +// are higher when -DIMLIB_EXPORT_SHIMS are enabled). +// +// Similar issue is present in Glibc so hopefully it's +// not a big deal: // http://sourceware.org/bugzilla/show_bug.cgi?id=15686 +// (also google for "dlopen deadlock). + +static pthread_mutex_t mtx; +static int rec_count; + +static void init_lock(void) { + // We need recursive lock because dlopen will call library constructors + // which may call other intercepted APIs that will call load_library again. + // PTHREAD_RECURSIVE_MUTEX_INITIALIZER is not portable + // so we do it hard way. + + pthread_mutexattr_t attr; + CHECK(0 == pthread_mutexattr_init(&attr), "failed to init mutex"); + CHECK(0 == pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE), "failed to init mutex"); + + CHECK(0 == pthread_mutex_init(&mtx, &attr), "failed to init mutex"); +} + +static int lock(void) { + static pthread_once_t once = PTHREAD_ONCE_INIT; + CHECK(0 == pthread_once(&once, init_lock), "failed to init lock"); + + CHECK(0 == pthread_mutex_lock(&mtx), "failed to lock mutex"); + + return 0 == __sync_fetch_and_add(&rec_count, 1); +} + +static void unlock(void) { + __sync_fetch_and_add(&rec_count, -1); + CHECK(0 == pthread_mutex_unlock(&mtx), "failed to unlock mutex"); +} +#else +static int lock(void) { + return 1; +} +static void unlock(void) {} +#endif + +static int load_library(void) { + int publish = lock(); + + if (lib_handle) { + unlock(); + return publish; + } + +#if HAS_DLOPEN_CALLBACK + extern void *(const char *lib_name); + lib_handle = ("libnvcuvid.so.1"); + CHECK(lib_handle, "failed to load library 'libnvcuvid.so.1' via callback ''"); +#else + lib_handle = dlopen("libnvcuvid.so.1", RTLD_LAZY | RTLD_GLOBAL); + CHECK(lib_handle, "failed to load library 'libnvcuvid.so.1' via dlopen: %s", dlerror()); +#endif + + // With (non-default) IMPLIB_EXPORT_SHIMS we may call dlopen more than once + // so dlclose it if we are not the first ones + if (__sync_val_compare_and_swap(&dlopened, 0, 1)) { + dlclose(lib_handle); + } + + unlock(); + + return publish; +} + +// Run dtor as late as possible in case library functions are +// called in other global dtors +// FIXME: this may crash if one thread is calling into library +// while some other thread executes exit(). It's no clear +// how to fix this besides simply NOT dlclosing library at all. +static void __attribute__((destructor(101))) unload_lib(void) { + if (dlopened) { + dlclose(lib_handle); + lib_handle = 0; + dlopened = 0; + } +} +#endif + +#if ! NO_DLOPEN && ! LAZY_LOAD +static void __attribute__((constructor(101))) load_lib(void) { + load_library(); +} +#endif + +// TODO: convert to single 0-separated string +static const char *const sym_names[] = { + "NvToolCreateInterface", + "NvToolDestroyInterface", + "NvToolGetApiFunctionCount", + "NvToolGetApiID", + "NvToolGetApiNames", + "NvToolGetInterface", + "NvToolSetApiID", + "NvToolSetInterface", + "__std_1U4S4U_X02", + "__std_2U4S4U_X08", + "__std_4U4S4U_X04", + "cuvidConvertYUVToRGB", + "cuvidConvertYUVToRGBArray", + "cuvidCreateDecoder", + "cuvidCreateVideoParser", + "cuvidCreateVideoSource", + "cuvidCreateVideoSourceW", + "cuvidCtxLock", + "cuvidCtxLockCreate", + "cuvidCtxLockDestroy", + "cuvidCtxUnlock", + "cuvidDecodePicture", + "cuvidDestroyDecoder", + "cuvidDestroyVideoParser", + "cuvidDestroyVideoSource", + "cuvidGetDecodeStatus", + "cuvidGetDecoderCaps", + "cuvidGetSourceAudioFormat", + "cuvidGetSourceVideoFormat", + "cuvidGetVideoSourceState", + "cuvidMapVideoFrame", + "cuvidMapVideoFrame64", + "cuvidParseVideoData", + "cuvidPrivateOp", + "cuvidReconfigureDecoder", + "cuvidSetVideoSourceState", + "cuvidUnmapVideoFrame", + "cuvidUnmapVideoFrame64", + 0 +}; + +#define SYM_COUNT (sizeof(sym_names)/sizeof(sym_names[0]) - 1) + +extern void *_libnvcuvid_so_tramp_table[]; + +// Can be sped up by manually parsing library symtab... +void *_libnvcuvid_so_tramp_resolve(size_t i) { + assert(i < SYM_COUNT); + + int publish = 1; + + void *h = 0; +#if NO_DLOPEN + // Library with implementations must have already been loaded. + if (lib_handle) { + // User has specified loaded library + h = lib_handle; + } else { + // User hasn't provided us the loaded library so search the global namespace. +# ifndef IMPLIB_EXPORT_SHIMS + // If shim symbols are hidden we should search + // for first available definition of symbol in library list + h = RTLD_DEFAULT; +# else + // Otherwise look for next available definition + h = RTLD_NEXT; +# endif + } +#else + publish = load_library(); + h = lib_handle; + CHECK(h, "failed to resolve symbol '%s', library failed to load", sym_names[i]); +#endif + + void *addr; +#if HAS_DLSYM_CALLBACK + extern void *(void *handle, const char *sym_name); + addr = (h, sym_names[i]); + CHECK(addr, "failed to resolve symbol '%s' via callback ", sym_names[i]); +#else + // Dlsym is thread-safe so don't need to protect it. + addr = dlsym(h, sym_names[i]); + CHECK(addr, "failed to resolve symbol '%s' via dlsym: %s", sym_names[i], dlerror()); +#endif + + if (publish) { + // Use atomic to please Tsan and ensure that preceeding writes + // in library ctors have been delivered before publishing address + (void)__sync_val_compare_and_swap(&_libnvcuvid_so_tramp_table[i], 0, addr); + } + + return addr; +} + +// Below APIs are not thread-safe +// and it's not clear how make them such +// (we can not know if some other thread is +// currently executing library code). + +// Helper for user to resolve all symbols +void _libnvcuvid_so_tramp_resolve_all(void) { + size_t i; + for(i = 0; i < SYM_COUNT; ++i) + _libnvcuvid_so_tramp_resolve(i); +} + +// Allows user to specify manually loaded implementation library. +void _libnvcuvid_so_tramp_set_handle(void *handle) { + // TODO: call unload_lib ? + lib_handle = handle; + dlopened = 0; +} + +// Resets all resolved symbols. This is needed in case +// client code wants to reload interposed library multiple times. +void _libnvcuvid_so_tramp_reset(void) { + // TODO: call unload_lib ? + memset(_libnvcuvid_so_tramp_table, 0, SYM_COUNT * sizeof(_libnvcuvid_so_tramp_table[0])); + lib_handle = 0; + dlopened = 0; +} + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/webrtc-sys/src/nvidia/implib/libnvcuvid.so.tramp.S b/webrtc-sys/src/nvidia/implib/libnvcuvid.so.tramp.S new file mode 100644 index 000000000..53fd9d2a9 --- /dev/null +++ b/webrtc-sys/src/nvidia/implib/libnvcuvid.so.tramp.S @@ -0,0 +1,1450 @@ +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .section .note.GNU-stack,"",@progbits + + .data + + .globl _libnvcuvid_so_tramp_table + .hidden _libnvcuvid_so_tramp_table + .align 8 +_libnvcuvid_so_tramp_table: + .zero 312 + + .text + + .globl _libnvcuvid_so_tramp_resolve + .hidden _libnvcuvid_so_tramp_resolve + + .globl _libnvcuvid_so_save_regs_and_resolve + .hidden _libnvcuvid_so_save_regs_and_resolve + .type _libnvcuvid_so_save_regs_and_resolve, %function +_libnvcuvid_so_save_regs_and_resolve: + .cfi_startproc + +#define PUSH_REG(reg) pushq %reg ; .cfi_adjust_cfa_offset 8; .cfi_rel_offset reg, 0 +#define POP_REG(reg) popq %reg ; .cfi_adjust_cfa_offset -8; .cfi_restore reg + +#define DEC_STACK(d) subq $d, %rsp; .cfi_adjust_cfa_offset d +#define INC_STACK(d) addq $d, %rsp; .cfi_adjust_cfa_offset -d + +#define PUSH_MMX_REG(reg) DEC_STACK(8); movq %reg, (%rsp); .cfi_rel_offset reg, 0 +#define POP_MMX_REG(reg) movq (%rsp), %reg; .cfi_restore reg; INC_STACK(8) + +#define PUSH_XMM_REG(reg) DEC_STACK(16); movdqa %reg, (%rsp); .cfi_rel_offset reg, 0 +#define POP_XMM_REG(reg) movdqa (%rsp), %reg; .cfi_restore reg; INC_STACK(16) + +// TODO: cfi_offset/cfi_restore +#define PUSH_YMM_REG(reg) DEC_STACK(32); vmovdqu %reg, (%rsp) +#define POP_YMM_REG(reg) vmovdqu (%rsp), %reg; INC_STACK(32) + +// TODO: cfi_offset/cfi_restore +#define PUSH_ZMM_REG(reg) DEC_STACK(64); vmovdqu32 %reg, (%rsp) +#define POP_ZMM_REG(reg) vmovdqu32 (%rsp), %reg; INC_STACK(64) + + // Slow path which calls dlsym, taken only on first call. + // All registers are stored to handle arbitrary calling conventions + // (except x87 FPU registers which do not have to be preserved). + // For Dwarf directives, read https://www.imperialviolet.org/2017/01/18/cfi.html. + + .cfi_def_cfa_offset 8 // Return address + + PUSH_REG(rdi) // 16 + mov 0x10(%rsp), %rdi + PUSH_REG(rbx) + PUSH_REG(rbx) // 16 + PUSH_REG(rcx) + PUSH_REG(rdx) // 16 + PUSH_REG(rbp) + PUSH_REG(rsi) // 16 + PUSH_REG(r8) + PUSH_REG(r9) // 16 + PUSH_REG(r10) + PUSH_REG(r11) // 16 + PUSH_REG(r12) + PUSH_REG(r13) // 16 + PUSH_REG(r14) + PUSH_REG(r15) // 16 + + // Maybe use cpuid instead of macro to detect current vector size... +#ifdef __AVX512F__ + PUSH_ZMM_REG(zmm0) + PUSH_ZMM_REG(zmm1) + PUSH_ZMM_REG(zmm2) + PUSH_ZMM_REG(zmm3) + PUSH_ZMM_REG(zmm4) + PUSH_ZMM_REG(zmm5) + PUSH_ZMM_REG(zmm6) + PUSH_ZMM_REG(zmm7) +#elif defined __AVX__ + PUSH_YMM_REG(ymm0) + PUSH_YMM_REG(ymm1) + PUSH_YMM_REG(ymm2) + PUSH_YMM_REG(ymm3) + PUSH_YMM_REG(ymm4) + PUSH_YMM_REG(ymm5) + PUSH_YMM_REG(ymm6) + PUSH_YMM_REG(ymm7) +#elif defined __SSE__ + PUSH_XMM_REG(xmm0) + PUSH_XMM_REG(xmm1) + PUSH_XMM_REG(xmm2) + PUSH_XMM_REG(xmm3) + PUSH_XMM_REG(xmm4) + PUSH_XMM_REG(xmm5) + PUSH_XMM_REG(xmm6) + PUSH_XMM_REG(xmm7) +#endif + + // MMX registers are not used to pass arguments so we do not save them + + // Stack is just 8-byte aligned but callee will re-align to 16 + call _libnvcuvid_so_tramp_resolve + +#ifdef __AVX512F__ + POP_ZMM_REG(zmm7) + POP_ZMM_REG(zmm6) + POP_ZMM_REG(zmm5) + POP_ZMM_REG(zmm4) + POP_ZMM_REG(zmm3) + POP_ZMM_REG(zmm2) + POP_ZMM_REG(zmm1) + POP_ZMM_REG(zmm0) // 16 +#elif defined __AVX__ + POP_YMM_REG(ymm7) + POP_YMM_REG(ymm6) + POP_YMM_REG(ymm5) + POP_YMM_REG(ymm4) + POP_YMM_REG(ymm3) + POP_YMM_REG(ymm2) + POP_YMM_REG(ymm1) + POP_YMM_REG(ymm0) // 16 +#elif defined __SSE__ + POP_XMM_REG(xmm7) + POP_XMM_REG(xmm6) + POP_XMM_REG(xmm5) + POP_XMM_REG(xmm4) + POP_XMM_REG(xmm3) + POP_XMM_REG(xmm2) + POP_XMM_REG(xmm1) + POP_XMM_REG(xmm0) // 16 +#endif + + POP_REG(r15) + POP_REG(r14) // 16 + POP_REG(r13) + POP_REG(r12) // 16 + POP_REG(r11) + POP_REG(r10) // 16 + POP_REG(r9) + POP_REG(r8) // 16 + POP_REG(rsi) + POP_REG(rbp) // 16 + POP_REG(rdx) + POP_REG(rcx) // 16 + POP_REG(rbx) + POP_REG(rbx) // 16 + POP_REG(rdi) + + ret + + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl NvToolCreateInterface + .p2align 4 + .type NvToolCreateInterface, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden NvToolCreateInterface +#endif +NvToolCreateInterface: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libnvcuvid_so_tramp_table+0(%rip) + je 2f +1: + jmp *_libnvcuvid_so_tramp_table+0(%rip) +2: + pushq $0 + .cfi_adjust_cfa_offset 8 + call _libnvcuvid_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl NvToolDestroyInterface + .p2align 4 + .type NvToolDestroyInterface, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden NvToolDestroyInterface +#endif +NvToolDestroyInterface: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libnvcuvid_so_tramp_table+8(%rip) + je 2f +1: + jmp *_libnvcuvid_so_tramp_table+8(%rip) +2: + pushq $1 + .cfi_adjust_cfa_offset 8 + call _libnvcuvid_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl NvToolGetApiFunctionCount + .p2align 4 + .type NvToolGetApiFunctionCount, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden NvToolGetApiFunctionCount +#endif +NvToolGetApiFunctionCount: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libnvcuvid_so_tramp_table+16(%rip) + je 2f +1: + jmp *_libnvcuvid_so_tramp_table+16(%rip) +2: + pushq $2 + .cfi_adjust_cfa_offset 8 + call _libnvcuvid_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl NvToolGetApiID + .p2align 4 + .type NvToolGetApiID, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden NvToolGetApiID +#endif +NvToolGetApiID: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libnvcuvid_so_tramp_table+24(%rip) + je 2f +1: + jmp *_libnvcuvid_so_tramp_table+24(%rip) +2: + pushq $3 + .cfi_adjust_cfa_offset 8 + call _libnvcuvid_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl NvToolGetApiNames + .p2align 4 + .type NvToolGetApiNames, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden NvToolGetApiNames +#endif +NvToolGetApiNames: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libnvcuvid_so_tramp_table+32(%rip) + je 2f +1: + jmp *_libnvcuvid_so_tramp_table+32(%rip) +2: + pushq $4 + .cfi_adjust_cfa_offset 8 + call _libnvcuvid_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl NvToolGetInterface + .p2align 4 + .type NvToolGetInterface, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden NvToolGetInterface +#endif +NvToolGetInterface: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libnvcuvid_so_tramp_table+40(%rip) + je 2f +1: + jmp *_libnvcuvid_so_tramp_table+40(%rip) +2: + pushq $5 + .cfi_adjust_cfa_offset 8 + call _libnvcuvid_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl NvToolSetApiID + .p2align 4 + .type NvToolSetApiID, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden NvToolSetApiID +#endif +NvToolSetApiID: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libnvcuvid_so_tramp_table+48(%rip) + je 2f +1: + jmp *_libnvcuvid_so_tramp_table+48(%rip) +2: + pushq $6 + .cfi_adjust_cfa_offset 8 + call _libnvcuvid_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl NvToolSetInterface + .p2align 4 + .type NvToolSetInterface, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden NvToolSetInterface +#endif +NvToolSetInterface: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libnvcuvid_so_tramp_table+56(%rip) + je 2f +1: + jmp *_libnvcuvid_so_tramp_table+56(%rip) +2: + pushq $7 + .cfi_adjust_cfa_offset 8 + call _libnvcuvid_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl __std_1U4S4U_X02 + .p2align 4 + .type __std_1U4S4U_X02, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden __std_1U4S4U_X02 +#endif +__std_1U4S4U_X02: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libnvcuvid_so_tramp_table+64(%rip) + je 2f +1: + jmp *_libnvcuvid_so_tramp_table+64(%rip) +2: + pushq $8 + .cfi_adjust_cfa_offset 8 + call _libnvcuvid_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl __std_2U4S4U_X08 + .p2align 4 + .type __std_2U4S4U_X08, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden __std_2U4S4U_X08 +#endif +__std_2U4S4U_X08: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libnvcuvid_so_tramp_table+72(%rip) + je 2f +1: + jmp *_libnvcuvid_so_tramp_table+72(%rip) +2: + pushq $9 + .cfi_adjust_cfa_offset 8 + call _libnvcuvid_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl __std_4U4S4U_X04 + .p2align 4 + .type __std_4U4S4U_X04, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden __std_4U4S4U_X04 +#endif +__std_4U4S4U_X04: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libnvcuvid_so_tramp_table+80(%rip) + je 2f +1: + jmp *_libnvcuvid_so_tramp_table+80(%rip) +2: + pushq $10 + .cfi_adjust_cfa_offset 8 + call _libnvcuvid_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuvidConvertYUVToRGB + .p2align 4 + .type cuvidConvertYUVToRGB, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuvidConvertYUVToRGB +#endif +cuvidConvertYUVToRGB: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libnvcuvid_so_tramp_table+88(%rip) + je 2f +1: + jmp *_libnvcuvid_so_tramp_table+88(%rip) +2: + pushq $11 + .cfi_adjust_cfa_offset 8 + call _libnvcuvid_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuvidConvertYUVToRGBArray + .p2align 4 + .type cuvidConvertYUVToRGBArray, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuvidConvertYUVToRGBArray +#endif +cuvidConvertYUVToRGBArray: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libnvcuvid_so_tramp_table+96(%rip) + je 2f +1: + jmp *_libnvcuvid_so_tramp_table+96(%rip) +2: + pushq $12 + .cfi_adjust_cfa_offset 8 + call _libnvcuvid_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuvidCreateDecoder + .p2align 4 + .type cuvidCreateDecoder, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuvidCreateDecoder +#endif +cuvidCreateDecoder: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libnvcuvid_so_tramp_table+104(%rip) + je 2f +1: + jmp *_libnvcuvid_so_tramp_table+104(%rip) +2: + pushq $13 + .cfi_adjust_cfa_offset 8 + call _libnvcuvid_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuvidCreateVideoParser + .p2align 4 + .type cuvidCreateVideoParser, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuvidCreateVideoParser +#endif +cuvidCreateVideoParser: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libnvcuvid_so_tramp_table+112(%rip) + je 2f +1: + jmp *_libnvcuvid_so_tramp_table+112(%rip) +2: + pushq $14 + .cfi_adjust_cfa_offset 8 + call _libnvcuvid_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuvidCreateVideoSource + .p2align 4 + .type cuvidCreateVideoSource, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuvidCreateVideoSource +#endif +cuvidCreateVideoSource: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libnvcuvid_so_tramp_table+120(%rip) + je 2f +1: + jmp *_libnvcuvid_so_tramp_table+120(%rip) +2: + pushq $15 + .cfi_adjust_cfa_offset 8 + call _libnvcuvid_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuvidCreateVideoSourceW + .p2align 4 + .type cuvidCreateVideoSourceW, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuvidCreateVideoSourceW +#endif +cuvidCreateVideoSourceW: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libnvcuvid_so_tramp_table+128(%rip) + je 2f +1: + jmp *_libnvcuvid_so_tramp_table+128(%rip) +2: + pushq $16 + .cfi_adjust_cfa_offset 8 + call _libnvcuvid_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuvidCtxLock + .p2align 4 + .type cuvidCtxLock, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuvidCtxLock +#endif +cuvidCtxLock: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libnvcuvid_so_tramp_table+136(%rip) + je 2f +1: + jmp *_libnvcuvid_so_tramp_table+136(%rip) +2: + pushq $17 + .cfi_adjust_cfa_offset 8 + call _libnvcuvid_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuvidCtxLockCreate + .p2align 4 + .type cuvidCtxLockCreate, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuvidCtxLockCreate +#endif +cuvidCtxLockCreate: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libnvcuvid_so_tramp_table+144(%rip) + je 2f +1: + jmp *_libnvcuvid_so_tramp_table+144(%rip) +2: + pushq $18 + .cfi_adjust_cfa_offset 8 + call _libnvcuvid_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuvidCtxLockDestroy + .p2align 4 + .type cuvidCtxLockDestroy, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuvidCtxLockDestroy +#endif +cuvidCtxLockDestroy: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libnvcuvid_so_tramp_table+152(%rip) + je 2f +1: + jmp *_libnvcuvid_so_tramp_table+152(%rip) +2: + pushq $19 + .cfi_adjust_cfa_offset 8 + call _libnvcuvid_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuvidCtxUnlock + .p2align 4 + .type cuvidCtxUnlock, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuvidCtxUnlock +#endif +cuvidCtxUnlock: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libnvcuvid_so_tramp_table+160(%rip) + je 2f +1: + jmp *_libnvcuvid_so_tramp_table+160(%rip) +2: + pushq $20 + .cfi_adjust_cfa_offset 8 + call _libnvcuvid_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuvidDecodePicture + .p2align 4 + .type cuvidDecodePicture, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuvidDecodePicture +#endif +cuvidDecodePicture: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libnvcuvid_so_tramp_table+168(%rip) + je 2f +1: + jmp *_libnvcuvid_so_tramp_table+168(%rip) +2: + pushq $21 + .cfi_adjust_cfa_offset 8 + call _libnvcuvid_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuvidDestroyDecoder + .p2align 4 + .type cuvidDestroyDecoder, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuvidDestroyDecoder +#endif +cuvidDestroyDecoder: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libnvcuvid_so_tramp_table+176(%rip) + je 2f +1: + jmp *_libnvcuvid_so_tramp_table+176(%rip) +2: + pushq $22 + .cfi_adjust_cfa_offset 8 + call _libnvcuvid_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuvidDestroyVideoParser + .p2align 4 + .type cuvidDestroyVideoParser, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuvidDestroyVideoParser +#endif +cuvidDestroyVideoParser: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libnvcuvid_so_tramp_table+184(%rip) + je 2f +1: + jmp *_libnvcuvid_so_tramp_table+184(%rip) +2: + pushq $23 + .cfi_adjust_cfa_offset 8 + call _libnvcuvid_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuvidDestroyVideoSource + .p2align 4 + .type cuvidDestroyVideoSource, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuvidDestroyVideoSource +#endif +cuvidDestroyVideoSource: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libnvcuvid_so_tramp_table+192(%rip) + je 2f +1: + jmp *_libnvcuvid_so_tramp_table+192(%rip) +2: + pushq $24 + .cfi_adjust_cfa_offset 8 + call _libnvcuvid_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuvidGetDecodeStatus + .p2align 4 + .type cuvidGetDecodeStatus, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuvidGetDecodeStatus +#endif +cuvidGetDecodeStatus: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libnvcuvid_so_tramp_table+200(%rip) + je 2f +1: + jmp *_libnvcuvid_so_tramp_table+200(%rip) +2: + pushq $25 + .cfi_adjust_cfa_offset 8 + call _libnvcuvid_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuvidGetDecoderCaps + .p2align 4 + .type cuvidGetDecoderCaps, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuvidGetDecoderCaps +#endif +cuvidGetDecoderCaps: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libnvcuvid_so_tramp_table+208(%rip) + je 2f +1: + jmp *_libnvcuvid_so_tramp_table+208(%rip) +2: + pushq $26 + .cfi_adjust_cfa_offset 8 + call _libnvcuvid_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuvidGetSourceAudioFormat + .p2align 4 + .type cuvidGetSourceAudioFormat, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuvidGetSourceAudioFormat +#endif +cuvidGetSourceAudioFormat: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libnvcuvid_so_tramp_table+216(%rip) + je 2f +1: + jmp *_libnvcuvid_so_tramp_table+216(%rip) +2: + pushq $27 + .cfi_adjust_cfa_offset 8 + call _libnvcuvid_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuvidGetSourceVideoFormat + .p2align 4 + .type cuvidGetSourceVideoFormat, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuvidGetSourceVideoFormat +#endif +cuvidGetSourceVideoFormat: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libnvcuvid_so_tramp_table+224(%rip) + je 2f +1: + jmp *_libnvcuvid_so_tramp_table+224(%rip) +2: + pushq $28 + .cfi_adjust_cfa_offset 8 + call _libnvcuvid_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuvidGetVideoSourceState + .p2align 4 + .type cuvidGetVideoSourceState, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuvidGetVideoSourceState +#endif +cuvidGetVideoSourceState: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libnvcuvid_so_tramp_table+232(%rip) + je 2f +1: + jmp *_libnvcuvid_so_tramp_table+232(%rip) +2: + pushq $29 + .cfi_adjust_cfa_offset 8 + call _libnvcuvid_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuvidMapVideoFrame + .p2align 4 + .type cuvidMapVideoFrame, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuvidMapVideoFrame +#endif +cuvidMapVideoFrame: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libnvcuvid_so_tramp_table+240(%rip) + je 2f +1: + jmp *_libnvcuvid_so_tramp_table+240(%rip) +2: + pushq $30 + .cfi_adjust_cfa_offset 8 + call _libnvcuvid_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuvidMapVideoFrame64 + .p2align 4 + .type cuvidMapVideoFrame64, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuvidMapVideoFrame64 +#endif +cuvidMapVideoFrame64: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libnvcuvid_so_tramp_table+248(%rip) + je 2f +1: + jmp *_libnvcuvid_so_tramp_table+248(%rip) +2: + pushq $31 + .cfi_adjust_cfa_offset 8 + call _libnvcuvid_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuvidParseVideoData + .p2align 4 + .type cuvidParseVideoData, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuvidParseVideoData +#endif +cuvidParseVideoData: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libnvcuvid_so_tramp_table+256(%rip) + je 2f +1: + jmp *_libnvcuvid_so_tramp_table+256(%rip) +2: + pushq $32 + .cfi_adjust_cfa_offset 8 + call _libnvcuvid_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuvidPrivateOp + .p2align 4 + .type cuvidPrivateOp, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuvidPrivateOp +#endif +cuvidPrivateOp: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libnvcuvid_so_tramp_table+264(%rip) + je 2f +1: + jmp *_libnvcuvid_so_tramp_table+264(%rip) +2: + pushq $33 + .cfi_adjust_cfa_offset 8 + call _libnvcuvid_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuvidReconfigureDecoder + .p2align 4 + .type cuvidReconfigureDecoder, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuvidReconfigureDecoder +#endif +cuvidReconfigureDecoder: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libnvcuvid_so_tramp_table+272(%rip) + je 2f +1: + jmp *_libnvcuvid_so_tramp_table+272(%rip) +2: + pushq $34 + .cfi_adjust_cfa_offset 8 + call _libnvcuvid_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuvidSetVideoSourceState + .p2align 4 + .type cuvidSetVideoSourceState, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuvidSetVideoSourceState +#endif +cuvidSetVideoSourceState: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libnvcuvid_so_tramp_table+280(%rip) + je 2f +1: + jmp *_libnvcuvid_so_tramp_table+280(%rip) +2: + pushq $35 + .cfi_adjust_cfa_offset 8 + call _libnvcuvid_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuvidUnmapVideoFrame + .p2align 4 + .type cuvidUnmapVideoFrame, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuvidUnmapVideoFrame +#endif +cuvidUnmapVideoFrame: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libnvcuvid_so_tramp_table+288(%rip) + je 2f +1: + jmp *_libnvcuvid_so_tramp_table+288(%rip) +2: + pushq $36 + .cfi_adjust_cfa_offset 8 + call _libnvcuvid_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl cuvidUnmapVideoFrame64 + .p2align 4 + .type cuvidUnmapVideoFrame64, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden cuvidUnmapVideoFrame64 +#endif +cuvidUnmapVideoFrame64: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libnvcuvid_so_tramp_table+296(%rip) + je 2f +1: + jmp *_libnvcuvid_so_tramp_table+296(%rip) +2: + pushq $37 + .cfi_adjust_cfa_offset 8 + call _libnvcuvid_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + diff --git a/webrtc-sys/src/nvidia/nvidia_decoder_factory.cpp b/webrtc-sys/src/nvidia/nvidia_decoder_factory.cpp new file mode 100644 index 000000000..2d0b397c8 --- /dev/null +++ b/webrtc-sys/src/nvidia/nvidia_decoder_factory.cpp @@ -0,0 +1,116 @@ +#include "nvidia_decoder_factory.h" + +#include + +#include +#include + +#include "cuda_context.h" +#include "h264_decoder_impl.h" +#include "rtc_base/logging.h" + +namespace webrtc { + +constexpr char kSdpKeyNameCodecImpl[] = "implementation_name"; +constexpr char kCodecName[] = "NvCodec"; + +static int GetCudaDeviceCapabilityMajorVersion(CUcontext context) { + cuCtxSetCurrent(context); + + CUdevice device; + cuCtxGetDevice(&device); + + int major; + cuDeviceGetAttribute(&major, CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MAJOR, + device); + + return major; +} + +std::vector SupportedNvDecoderCodecs(CUcontext context) { + std::vector supportedFormats; + + // HardwareGeneration Kepler is 3.x + // https://docs.nvidia.com/deploy/cuda-compatibility/index.html#faq + // Kepler support h264 profile Main, Highprofile up to Level4.1 + // https://docs.nvidia.com/video-technologies/video-codec-sdk/nvdec-video-decoder-api-prog-guide/index.html#video-decoder-capabilities__table_o3x_fms_3lb + if (GetCudaDeviceCapabilityMajorVersion(context) <= 3) { + supportedFormats = { + CreateH264Format(webrtc::H264Profile::kProfileHigh, + webrtc::H264Level::kLevel4_1, "1"), + CreateH264Format(webrtc::H264Profile::kProfileMain, + webrtc::H264Level::kLevel4_1, "1"), + }; + } else { + supportedFormats = { + // Constrained Baseline Profile does not support NvDecoder, but WebRTC + // uses this profile by default, + // so it must be returned in this method. + CreateH264Format(webrtc::H264Profile::kProfileConstrainedBaseline, + webrtc::H264Level::kLevel5_1, "1"), + CreateH264Format(webrtc::H264Profile::kProfileBaseline, + webrtc::H264Level::kLevel5_1, "1"), + CreateH264Format(webrtc::H264Profile::kProfileHigh, + webrtc::H264Level::kLevel5_1, "1"), + CreateH264Format(webrtc::H264Profile::kProfileMain, + webrtc::H264Level::kLevel5_1, "1"), + }; + } + + for (auto& format : supportedFormats) { + format.parameters.emplace(kSdpKeyNameCodecImpl, kCodecName); + } + + return supportedFormats; +} + +NvidiaVideoDecoderFactory::NvidiaVideoDecoderFactory() + : cu_context_(std::make_unique()) { + if (cu_context_->Initialize()) { + supported_formats_ = SupportedNvDecoderCodecs(cu_context_->GetContext()); + } else { + RTC_LOG(LS_ERROR) << "Failed to initialize CUDA context."; + } + RTC_LOG(LS_INFO) << "NvidiaVideoDecoderFactory created with " + << supported_formats_.size() << " supported formats."; +} + +NvidiaVideoDecoderFactory::~NvidiaVideoDecoderFactory() {} + +bool NvidiaVideoDecoderFactory::IsSupported() { + // Check if the CUDA context can be initialized. + auto cu_context = std::make_unique(); + if (!cu_context->Initialize()) { + std::cout << "Failed to initialize CUDA context." << std::endl; + return false; + } + std::cout << "Nvidia Decoder is supported." << std::endl; + return true; +} + +std::unique_ptr NvidiaVideoDecoderFactory::Create( + const Environment& env, + const SdpVideoFormat& format) { + // Check if the requested format is supported. + for (const auto& supported_format : supported_formats_) { + if (format.IsSameCodec(supported_format)) { + // If the format is supported, create and return the encoder. + if (!cu_context_) { + cu_context_ = std::make_unique(); + if (!cu_context_->Initialize()) { + RTC_LOG(LS_ERROR) << "Failed to initialize CUDA context."; + return nullptr; + } + } + return std::make_unique(cu_context_->GetContext()); + } + } + return nullptr; +} + +std::vector NvidiaVideoDecoderFactory::GetSupportedFormats() + const { + return supported_formats_; +} + +} // namespace webrtc diff --git a/webrtc-sys/src/nvidia/nvidia_decoder_factory.h b/webrtc-sys/src/nvidia/nvidia_decoder_factory.h new file mode 100644 index 000000000..28094467b --- /dev/null +++ b/webrtc-sys/src/nvidia/nvidia_decoder_factory.h @@ -0,0 +1,34 @@ + + +#ifndef NVIDIA_VIDEO_DECODER_FACTORY_H_ +#define NVIDIA_VIDEO_DECODER_FACTORY_H_ + +#include + +#include "api/environment/environment.h" +#include "api/video_codecs/sdp_video_format.h" +#include "api/video_codecs/video_decoder_factory.h" +#include "cuda_context.h" + +namespace webrtc { + +class NvidiaVideoDecoderFactory : public VideoDecoderFactory { + public: + NvidiaVideoDecoderFactory(); + ~NvidiaVideoDecoderFactory() override; + + static bool IsSupported(); + + std::vector GetSupportedFormats() const override; + std::unique_ptr Create( + const Environment& env, + const SdpVideoFormat& format) override; + + private: + std::vector supported_formats_; + std::unique_ptr cu_context_; +}; + +} // namespace webrtc + +#endif // NVIDIA_VIDEO_DECODER_FACTORY_H_ diff --git a/webrtc-sys/src/nvidia/nvidia_encoder_factory.cpp b/webrtc-sys/src/nvidia/nvidia_encoder_factory.cpp new file mode 100644 index 000000000..8d4e563f2 --- /dev/null +++ b/webrtc-sys/src/nvidia/nvidia_encoder_factory.cpp @@ -0,0 +1,75 @@ +#include "nvidia_encoder_factory.h" + +#include +#include + +#include "cuda_context.h" +#include "h264_encoder_impl.h" +#include "rtc_base/logging.h" + +namespace webrtc { + +NvidiaVideoEncoderFactory::NvidiaVideoEncoderFactory() { + std::map baselineParameters = { + {"profile-level-id", "42e01f"}, + {"level-asymmetry-allowed", "1"}, + {"packetization-mode", "1"}, + }; + supported_formats_.push_back(SdpVideoFormat("H264", baselineParameters)); + + /*std::map highParameters = { + {"profile-level-id", "4d0032"}, + {"level-asymmetry-allowed", "1"}, + {"packetization-mode", "1"}, + }; + + supported_formats_.push_back(SdpVideoFormat("H264", highParameters)); + */ +} + +NvidiaVideoEncoderFactory::~NvidiaVideoEncoderFactory() {} + +bool NvidiaVideoEncoderFactory::IsSupported() { + // Check if the CUDA context can be initialized. + auto cu_context = std::make_unique(); + if (!cu_context->Initialize()) { + std::cout << "Failed to initialize CUDA context." << std::endl; + return false; + } + + std::cout << "Nvidia Encoder is supported." << std::endl; + return true; +} + +std::unique_ptr NvidiaVideoEncoderFactory::Create( + const Environment& env, + const SdpVideoFormat& format) { + // Check if the requested format is supported. + for (const auto& supported_format : supported_formats_) { + if (format.IsSameCodec(supported_format)) { + // If the format is supported, create and return the encoder. + if (!cu_context_) { + cu_context_ = std::make_unique(); + if (!cu_context_->Initialize()) { + RTC_LOG(LS_ERROR) << "Failed to initialize CUDA context."; + return nullptr; + } + } + return std::make_unique( + env, cu_context_->GetContext(), CU_MEMORYTYPE_DEVICE, + NV_ENC_BUFFER_FORMAT_IYUV, format); + } + } + return nullptr; +} +std::vector NvidiaVideoEncoderFactory::GetSupportedFormats() + const { + return supported_formats_; +} + +std::vector NvidiaVideoEncoderFactory::GetImplementations() + const { + return supported_formats_; +} + +} // namespace webrtc diff --git a/webrtc-sys/src/nvidia/nvidia_encoder_factory.h b/webrtc-sys/src/nvidia/nvidia_encoder_factory.h new file mode 100644 index 000000000..b18c49ca0 --- /dev/null +++ b/webrtc-sys/src/nvidia/nvidia_encoder_factory.h @@ -0,0 +1,42 @@ + + +#ifndef NVIDIA_VIDEO_ENCODER_FACTORY_H_ +#define NVIDIA_VIDEO_ENCODER_FACTORY_H_ + +#include + +#include "api/environment/environment.h" +#include "api/video_codecs/sdp_video_format.h" +#include "api/video_codecs/video_encoder_factory.h" +#include "cuda_context.h" + +namespace webrtc { + +class NvidiaVideoEncoderFactory : public VideoEncoderFactory { + public: + NvidiaVideoEncoderFactory(); + ~NvidiaVideoEncoderFactory() override; + + static bool IsSupported(); + + std::unique_ptr Create(const Environment& env, + const SdpVideoFormat& format) override; + + // Returns a list of supported codecs in order of preference. + std::vector GetSupportedFormats() const override; + + std::vector GetImplementations() const override; + + std::unique_ptr GetEncoderSelector() + const override { + return nullptr; + } + + private: + std::vector supported_formats_; + std::unique_ptr cu_context_; +}; + +} // namespace webrtc + +#endif // NVIDIA_VIDEO_ENCODER_FACTORY_H_ diff --git a/webrtc-sys/src/vaapi/h264_encoder_impl.cpp b/webrtc-sys/src/vaapi/h264_encoder_impl.cpp new file mode 100644 index 000000000..ec7965997 --- /dev/null +++ b/webrtc-sys/src/vaapi/h264_encoder_impl.cpp @@ -0,0 +1,315 @@ +#include "h264_encoder_impl.h" + +#include +#include +#include + +#include "absl/strings/match.h" +#include "absl/types/optional.h" +#include "api/video/video_codec_constants.h" +#include "api/video_codecs/scalability_mode.h" +#include "common_video/libyuv/include/webrtc_libyuv.h" +#include "modules/video_coding/include/video_codec_interface.h" +#include "modules/video_coding/include/video_error_codes.h" +#include "modules/video_coding/svc/create_scalability_structure.h" +#include "modules/video_coding/utility/simulcast_rate_allocator.h" +#include "modules/video_coding/utility/simulcast_utility.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/time_utils.h" +#include "system_wrappers/include/metrics.h" +#include "third_party/libyuv/include/libyuv/convert.h" +#include "third_party/libyuv/include/libyuv/scale.h" + +#define VA_FOURCC_I420 0x30323449 // I420 + +namespace webrtc { + +// Used by histograms. Values of entries should not be changed. +enum H264EncoderImplEvent { + kH264EncoderEventInit = 0, + kH264EncoderEventError = 1, + kH264EncoderEventMax = 16, +}; + +VAAPIH264EncoderWrapper::VAAPIH264EncoderWrapper(const webrtc::Environment& env, + const SdpVideoFormat& format) + : encoder_(new livekit::VaapiH264EncoderWrapper()), + packetization_mode_( + H264EncoderSettings::Parse(format).packetization_mode), + format_(format) { + std::string hexString = format_.parameters.at("profile-level-id"); + absl::optional profile_level_id = + webrtc::ParseH264ProfileLevelId(hexString.c_str()); + if (profile_level_id.has_value()) { + profile_ = profile_level_id->profile; + level_ = profile_level_id->level; + } +} + +VAProfile VAAPIH264EncoderWrapper::GetVAProfile() const { + switch (profile_) { + case H264Profile::kProfileConstrainedBaseline: + case H264Profile::kProfileBaseline: + return VAProfileH264ConstrainedBaseline; + + case H264Profile::kProfileMain: + return VAProfileH264Main; + + case H264Profile::kProfileConstrainedHigh: + case H264Profile::kProfileHigh: + return VAProfileH264High; + } + return VAProfileNone; +} + +VAAPIH264EncoderWrapper::~VAAPIH264EncoderWrapper() { + Release(); +} + +void VAAPIH264EncoderWrapper::ReportInit() { + if (has_reported_init_) + return; + RTC_HISTOGRAM_ENUMERATION("WebRTC.Video.H264EncoderImpl.Event", + kH264EncoderEventInit, kH264EncoderEventMax); + has_reported_init_ = true; +} + +void VAAPIH264EncoderWrapper::ReportError() { + if (has_reported_error_) + return; + RTC_HISTOGRAM_ENUMERATION("WebRTC.Video.H264EncoderImpl.Event", + kH264EncoderEventError, kH264EncoderEventMax); + has_reported_error_ = true; +} + +int32_t VAAPIH264EncoderWrapper::InitEncode( + const VideoCodec* inst, + const VideoEncoder::Settings& settings) { + if (!inst || inst->codecType != kVideoCodecH264) { + ReportError(); + return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; + } + if (inst->maxFramerate == 0) { + ReportError(); + return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; + } + if (inst->width < 1 || inst->height < 1) { + ReportError(); + return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; + } + + int32_t release_ret = Release(); + if (release_ret != WEBRTC_VIDEO_CODEC_OK) { + ReportError(); + return release_ret; + } + + codec_ = *inst; + + // Code expects simulcastStream resolutions to be correct, make sure they are + // filled even when there are no simulcast layers. + if (codec_.numberOfSimulcastStreams == 0) { + codec_.simulcastStream[0].width = codec_.width; + codec_.simulcastStream[0].height = codec_.height; + } + + // Initialize encoded image. Default buffer size: size of unencoded data. + const size_t new_capacity = + CalcBufferSize(VideoType::kI420, codec_.width, codec_.height); + encoded_image_.SetEncodedData(EncodedImageBuffer::Create(new_capacity)); + encoded_image_._encodedWidth = codec_.width; + encoded_image_._encodedHeight = codec_.height; + encoded_image_.set_size(0); + + configuration_.sending = false; + configuration_.frame_dropping_on = codec_.GetFrameDropEnabled(); + configuration_.key_frame_interval = codec_.H264()->keyFrameInterval; + + configuration_.width = codec_.width; + configuration_.height = codec_.height; + + configuration_.max_frame_rate = codec_.maxFramerate; + configuration_.target_bps = codec_.startBitrate * 1000; + configuration_.max_bps = codec_.maxBitrate * 1000; + + if (!encoder_->IsInitialized()) { + // Initialize encoder. + int keyFrameInterval = 60; + if (codec_.maxFramerate > 0) { + keyFrameInterval = codec_.maxFramerate * 5; + } + auto va_profile = GetVAProfile(); + if (va_profile == VAProfileNone) { + RTC_LOG(LS_ERROR) << "Unsupported H264 profile: " + << static_cast(profile_); + ReportError(); + return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; + } + encoder_->Initialize(codec_.width, codec_.height, + codec_.startBitrate * 1000, keyFrameInterval, + keyFrameInterval, 1, codec_.maxFramerate, + va_profile, VA_RC_CBR); + } + + SimulcastRateAllocator init_allocator(codec_); + VideoBitrateAllocation allocation = + init_allocator.Allocate(VideoBitrateAllocationParameters( + DataRate::KilobitsPerSec(codec_.startBitrate), codec_.maxFramerate)); + SetRates(RateControlParameters(allocation, codec_.maxFramerate)); + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t VAAPIH264EncoderWrapper::RegisterEncodeCompleteCallback( + EncodedImageCallback* callback) { + RTC_DCHECK(callback); + encoded_image_callback_ = callback; + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t VAAPIH264EncoderWrapper::Release() { + if (encoder_->IsInitialized()) { + encoder_->Destroy(); + } + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t VAAPIH264EncoderWrapper::Encode( + const VideoFrame& input_frame, + const std::vector* frame_types) { + if (!encoder_) { + ReportError(); + return WEBRTC_VIDEO_CODEC_UNINITIALIZED; + } + if (!encoded_image_callback_) { + RTC_LOG(LS_WARNING) + << "InitEncode() has been called, but a callback function " + "has not been set with RegisterEncodeCompleteCallback()"; + ReportError(); + return WEBRTC_VIDEO_CODEC_UNINITIALIZED; + } + + rtc::scoped_refptr frame_buffer = + input_frame.video_frame_buffer()->ToI420(); + if (!frame_buffer) { + RTC_LOG(LS_ERROR) << "Failed to convert " + << VideoFrameBufferTypeToString( + input_frame.video_frame_buffer()->type()) + << " image to I420. Can't encode frame."; + return WEBRTC_VIDEO_CODEC_ENCODER_FAILURE; + } + RTC_CHECK(frame_buffer->type() == VideoFrameBuffer::Type::kI420 || + frame_buffer->type() == VideoFrameBuffer::Type::kI420A); + + bool is_keyframe_needed = false; + if (configuration_.key_frame_request && configuration_.sending) { + is_keyframe_needed = true; + } + + bool send_key_frame = + is_keyframe_needed || + (frame_types && (*frame_types)[0] == VideoFrameType::kVideoFrameKey); + if (send_key_frame) { + is_keyframe_needed = true; + configuration_.key_frame_request = false; + } + + RTC_DCHECK_EQ(configuration_.width, frame_buffer->width()); + RTC_DCHECK_EQ(configuration_.height, frame_buffer->height()); + + if (!configuration_.sending) { + return WEBRTC_VIDEO_CODEC_NO_OUTPUT; + } + + if (frame_types != nullptr) { + // Skip frame? + if ((*frame_types)[0] == VideoFrameType::kEmptyFrame) { + return WEBRTC_VIDEO_CODEC_NO_OUTPUT; + } + } + + std::vector output; + encoder_->Encode(VA_FOURCC_I420, frame_buffer->DataY(), frame_buffer->DataU(), + frame_buffer->DataV(), send_key_frame, output); + + if (output.empty()) { + RTC_LOG(LS_ERROR) << "Failed to encode frame."; + return WEBRTC_VIDEO_CODEC_ERROR; + } + + encoded_image_.SetEncodedData( + EncodedImageBuffer::Create(output.data(), output.size())); + + h264_bitstream_parser_.ParseBitstream(encoded_image_); + + encoded_image_.qp_ = h264_bitstream_parser_.GetLastSliceQp().value_or(-1); + + encoded_image_._encodedWidth = configuration_.width; + encoded_image_._encodedHeight = configuration_.height; + encoded_image_.SetRtpTimestamp(input_frame.rtp_timestamp()); + encoded_image_.SetColorSpace(input_frame.color_space()); + encoded_image_._frameType = send_key_frame ? VideoFrameType::kVideoFrameKey + : VideoFrameType::kVideoFrameDelta; + CodecSpecificInfo codec_specific; + codec_specific.codecType = kVideoCodecH264; + codec_specific.codecSpecific.H264.packetization_mode = packetization_mode_; + codec_specific.codecSpecific.H264.temporal_idx = kNoTemporalIdx; + codec_specific.codecSpecific.H264.base_layer_sync = false; + codec_specific.codecSpecific.H264.idr_frame = send_key_frame; + encoded_image_callback_->OnEncodedImage(encoded_image_, &codec_specific); + + return WEBRTC_VIDEO_CODEC_OK; +} + +VideoEncoder::EncoderInfo VAAPIH264EncoderWrapper::GetEncoderInfo() const { + EncoderInfo info; + info.supports_native_handle = false; + info.implementation_name = "VAAPI H264 Encoder"; + info.scaling_settings = VideoEncoder::ScalingSettings::kOff; + info.is_hardware_accelerated = true; + info.supports_simulcast = false; + info.preferred_pixel_formats = {VideoFrameBuffer::Type::kI420}; + return info; +} + +void VAAPIH264EncoderWrapper::SetRates( + const RateControlParameters& parameters) { + if (!encoder_) { + RTC_LOG(LS_WARNING) << "SetRates() while uninitialized."; + return; + } + + if (parameters.framerate_fps < 1.0) { + RTC_LOG(LS_WARNING) << "Invalid frame rate: " << parameters.framerate_fps; + return; + } + + if (parameters.bitrate.get_sum_bps() == 0) { + configuration_.SetStreamState(false); + return; + } + + codec_.maxFramerate = static_cast(parameters.framerate_fps); + + configuration_.target_bps = parameters.bitrate.GetSpatialLayerSum(0); + configuration_.max_frame_rate = parameters.framerate_fps; + + if (configuration_.target_bps) { + configuration_.SetStreamState(true); + // Update max_frame_rate/target_bitrate for vaapi encoder. + encoder_->UpdateRates(configuration_.max_frame_rate, + configuration_.target_bps); + } else { + configuration_.SetStreamState(false); + } +} + +void VAAPIH264EncoderWrapper::LayerConfig::SetStreamState(bool send_stream) { + if (send_stream && !sending) { + // Need a key frame if we have not sent this stream before. + key_frame_request = true; + } + sending = send_stream; +} + +} // namespace webrtc diff --git a/webrtc-sys/src/vaapi/h264_encoder_impl.h b/webrtc-sys/src/vaapi/h264_encoder_impl.h new file mode 100644 index 000000000..ad67e7fb5 --- /dev/null +++ b/webrtc-sys/src/vaapi/h264_encoder_impl.h @@ -0,0 +1,82 @@ +#ifndef VAAPI_H264_ENCODER_IMPL_H_ +#define VAAPI_H264_ENCODER_IMPL_H_ + +#include +#include + +#include "absl/container/inlined_vector.h" +#include "api/transport/rtp/dependency_descriptor.h" +#include "api/video/i420_buffer.h" +#include "api/video/video_codec_constants.h" +#include "api/video_codecs/scalability_mode.h" +#include "api/video_codecs/video_encoder.h" +#include "common_video/h264/h264_bitstream_parser.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" + +#include "vaapi_h264_encoder_wrapper.h" + +namespace webrtc { + +class VAAPIH264EncoderWrapper : public VideoEncoder { + public: + struct LayerConfig { + int simulcast_idx = 0; + int width = -1; + int height = -1; + bool sending = true; + bool key_frame_request = false; + float max_frame_rate = 0; + uint32_t target_bps = 0; + uint32_t max_bps = 0; + bool frame_dropping_on = false; + int key_frame_interval = 0; + int num_temporal_layers = 1; + + void SetStreamState(bool send_stream); + }; + + public: + VAAPIH264EncoderWrapper(const webrtc::Environment& env, + const SdpVideoFormat& format); + ~VAAPIH264EncoderWrapper() override; + + int32_t InitEncode(const VideoCodec* codec_settings, + const Settings& settings) override; + + int32_t RegisterEncodeCompleteCallback( + EncodedImageCallback* callback) override; + + int32_t Release() override; + + int32_t Encode(const VideoFrame& frame, + const std::vector* frame_types) override; + + void SetRates(const RateControlParameters& rc_parameters) override; + + EncoderInfo GetEncoderInfo() const override; + + private: + VAProfile GetVAProfile() const; + + private: + EncodedImageCallback* encoded_image_callback_ = nullptr; + std::unique_ptr encoder_; + LayerConfig configuration_; + EncodedImage encoded_image_; + H264PacketizationMode packetization_mode_; + VideoCodec codec_; + void ReportInit(); + void ReportError(); + bool has_reported_init_ = false; + bool has_reported_error_ = false; + webrtc::H264BitstreamParser h264_bitstream_parser_; + const SdpVideoFormat format_; + H264Profile profile_ = H264Profile::kProfileConstrainedBaseline; + H264Level level_ = H264Level::kLevel1_b; +}; + +} // namespace webrtc + +#endif // VAAPI_H264_ENCODER_IMPL_H_ diff --git a/webrtc-sys/src/vaapi/implib/libva-drm.so.init.c b/webrtc-sys/src/vaapi/implib/libva-drm.so.init.c new file mode 100644 index 000000000..b9df56f0b --- /dev/null +++ b/webrtc-sys/src/vaapi/implib/libva-drm.so.init.c @@ -0,0 +1,245 @@ +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE // For RTLD_DEFAULT +#endif + +#define HAS_DLOPEN_CALLBACK 0 +#define HAS_DLSYM_CALLBACK 0 +#define NO_DLOPEN 0 +#define LAZY_LOAD 1 +#define THREAD_SAFE 1 + +#include +#include +#include +#include +#include + +#if THREAD_SAFE +#include +#endif + +// Sanity check for ARM to avoid puzzling runtime crashes +#ifdef __arm__ +# if defined __thumb__ && ! defined __THUMB_INTERWORK__ +# error "ARM trampolines need -mthumb-interwork to work in Thumb mode" +# endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define CHECK(cond, fmt, ...) do { \ + if(!(cond)) { \ + fprintf(stderr, "implib-gen: libva-drm.so.2: " fmt "\n", ##__VA_ARGS__); \ + assert(0 && "Assertion in generated code"); \ + abort(); \ + } \ + } while(0) + +static void *lib_handle; +static int dlopened; + +#if ! NO_DLOPEN + +#if THREAD_SAFE + +// We need to consider two cases: +// - different threads calling intercepted APIs in parallel +// - same thread calling 2 intercepted APIs recursively +// due to dlopen calling library constructors +// (usually happens only under IMPLIB_EXPORT_SHIMS) + +// Current recursive mutex approach will deadlock +// if library constructor starts and joins a new thread +// which (directly or indirectly) calls another library function. +// Such situations should be very rare (although chances +// are higher when -DIMLIB_EXPORT_SHIMS are enabled). +// +// Similar issue is present in Glibc so hopefully it's +// not a big deal: // http://sourceware.org/bugzilla/show_bug.cgi?id=15686 +// (also google for "dlopen deadlock). + +static pthread_mutex_t mtx; +static int rec_count; + +static void init_lock(void) { + // We need recursive lock because dlopen will call library constructors + // which may call other intercepted APIs that will call load_library again. + // PTHREAD_RECURSIVE_MUTEX_INITIALIZER is not portable + // so we do it hard way. + + pthread_mutexattr_t attr; + CHECK(0 == pthread_mutexattr_init(&attr), "failed to init mutex"); + CHECK(0 == pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE), "failed to init mutex"); + + CHECK(0 == pthread_mutex_init(&mtx, &attr), "failed to init mutex"); +} + +static int lock(void) { + static pthread_once_t once = PTHREAD_ONCE_INIT; + CHECK(0 == pthread_once(&once, init_lock), "failed to init lock"); + + CHECK(0 == pthread_mutex_lock(&mtx), "failed to lock mutex"); + + return 0 == __sync_fetch_and_add(&rec_count, 1); +} + +static void unlock(void) { + __sync_fetch_and_add(&rec_count, -1); + CHECK(0 == pthread_mutex_unlock(&mtx), "failed to unlock mutex"); +} +#else +static int lock(void) { + return 1; +} +static void unlock(void) {} +#endif + +static int load_library(void) { + int publish = lock(); + + if (lib_handle) { + unlock(); + return publish; + } + +#if HAS_DLOPEN_CALLBACK + extern void *(const char *lib_name); + lib_handle = ("libva-drm.so.2"); + CHECK(lib_handle, "failed to load library 'libva-drm.so.2' via callback ''"); +#else + lib_handle = dlopen("libva-drm.so.2", RTLD_LAZY | RTLD_GLOBAL); + CHECK(lib_handle, "failed to load library 'libva-drm.so.2' via dlopen: %s", dlerror()); +#endif + + // With (non-default) IMPLIB_EXPORT_SHIMS we may call dlopen more than once + // so dlclose it if we are not the first ones + if (__sync_val_compare_and_swap(&dlopened, 0, 1)) { + dlclose(lib_handle); + } + + unlock(); + + return publish; +} + +// Run dtor as late as possible in case library functions are +// called in other global dtors +// FIXME: this may crash if one thread is calling into library +// while some other thread executes exit(). It's no clear +// how to fix this besides simply NOT dlclosing library at all. +static void __attribute__((destructor(101))) unload_lib(void) { + if (dlopened) { + dlclose(lib_handle); + lib_handle = 0; + dlopened = 0; + } +} +#endif + +#if ! NO_DLOPEN && ! LAZY_LOAD +static void __attribute__((constructor(101))) load_lib(void) { + load_library(); +} +#endif + +// TODO: convert to single 0-separated string +static const char *const sym_names[] = { + "vaGetDisplayDRM", + 0 +}; + +#define SYM_COUNT (sizeof(sym_names)/sizeof(sym_names[0]) - 1) + +extern void *_libva_drm_so_tramp_table[]; + +// Can be sped up by manually parsing library symtab... +void *_libva_drm_so_tramp_resolve(size_t i) { + assert(i < SYM_COUNT); + + int publish = 1; + + void *h = 0; +#if NO_DLOPEN + // Library with implementations must have already been loaded. + if (lib_handle) { + // User has specified loaded library + h = lib_handle; + } else { + // User hasn't provided us the loaded library so search the global namespace. +# ifndef IMPLIB_EXPORT_SHIMS + // If shim symbols are hidden we should search + // for first available definition of symbol in library list + h = RTLD_DEFAULT; +# else + // Otherwise look for next available definition + h = RTLD_NEXT; +# endif + } +#else + publish = load_library(); + h = lib_handle; + CHECK(h, "failed to resolve symbol '%s', library failed to load", sym_names[i]); +#endif + + void *addr; +#if HAS_DLSYM_CALLBACK + extern void *(void *handle, const char *sym_name); + addr = (h, sym_names[i]); + CHECK(addr, "failed to resolve symbol '%s' via callback ", sym_names[i]); +#else + // Dlsym is thread-safe so don't need to protect it. + addr = dlsym(h, sym_names[i]); + CHECK(addr, "failed to resolve symbol '%s' via dlsym: %s", sym_names[i], dlerror()); +#endif + + if (publish) { + // Use atomic to please Tsan and ensure that preceeding writes + // in library ctors have been delivered before publishing address + (void)__sync_val_compare_and_swap(&_libva_drm_so_tramp_table[i], 0, addr); + } + + return addr; +} + +// Below APIs are not thread-safe +// and it's not clear how make them such +// (we can not know if some other thread is +// currently executing library code). + +// Helper for user to resolve all symbols +void _libva_drm_so_tramp_resolve_all(void) { + size_t i; + for(i = 0; i < SYM_COUNT; ++i) + _libva_drm_so_tramp_resolve(i); +} + +// Allows user to specify manually loaded implementation library. +void _libva_drm_so_tramp_set_handle(void *handle) { + // TODO: call unload_lib ? + lib_handle = handle; + dlopened = 0; +} + +// Resets all resolved symbols. This is needed in case +// client code wants to reload interposed library multiple times. +void _libva_drm_so_tramp_reset(void) { + // TODO: call unload_lib ? + memset(_libva_drm_so_tramp_table, 0, SYM_COUNT * sizeof(_libva_drm_so_tramp_table[0])); + lib_handle = 0; + dlopened = 0; +} + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/webrtc-sys/src/vaapi/implib/libva-drm.so.tramp.S b/webrtc-sys/src/vaapi/implib/libva-drm.so.tramp.S new file mode 100644 index 000000000..ba19e2091 --- /dev/null +++ b/webrtc-sys/src/vaapi/implib/libva-drm.so.tramp.S @@ -0,0 +1,192 @@ +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .section .note.GNU-stack,"",@progbits + + .data + + .globl _libva_drm_so_tramp_table + .hidden _libva_drm_so_tramp_table + .align 8 +_libva_drm_so_tramp_table: + .zero 16 + + .text + + .globl _libva_drm_so_tramp_resolve + .hidden _libva_drm_so_tramp_resolve + + .globl _libva_drm_so_save_regs_and_resolve + .hidden _libva_drm_so_save_regs_and_resolve + .type _libva_drm_so_save_regs_and_resolve, %function +_libva_drm_so_save_regs_and_resolve: + .cfi_startproc + +#define PUSH_REG(reg) pushq %reg ; .cfi_adjust_cfa_offset 8; .cfi_rel_offset reg, 0 +#define POP_REG(reg) popq %reg ; .cfi_adjust_cfa_offset -8; .cfi_restore reg + +#define DEC_STACK(d) subq $d, %rsp; .cfi_adjust_cfa_offset d +#define INC_STACK(d) addq $d, %rsp; .cfi_adjust_cfa_offset -d + +#define PUSH_MMX_REG(reg) DEC_STACK(8); movq %reg, (%rsp); .cfi_rel_offset reg, 0 +#define POP_MMX_REG(reg) movq (%rsp), %reg; .cfi_restore reg; INC_STACK(8) + +#define PUSH_XMM_REG(reg) DEC_STACK(16); movdqa %reg, (%rsp); .cfi_rel_offset reg, 0 +#define POP_XMM_REG(reg) movdqa (%rsp), %reg; .cfi_restore reg; INC_STACK(16) + +// TODO: cfi_offset/cfi_restore +#define PUSH_YMM_REG(reg) DEC_STACK(32); vmovdqu %reg, (%rsp) +#define POP_YMM_REG(reg) vmovdqu (%rsp), %reg; INC_STACK(32) + +// TODO: cfi_offset/cfi_restore +#define PUSH_ZMM_REG(reg) DEC_STACK(64); vmovdqu32 %reg, (%rsp) +#define POP_ZMM_REG(reg) vmovdqu32 (%rsp), %reg; INC_STACK(64) + + // Slow path which calls dlsym, taken only on first call. + // All registers are stored to handle arbitrary calling conventions + // (except x87 FPU registers which do not have to be preserved). + // For Dwarf directives, read https://www.imperialviolet.org/2017/01/18/cfi.html. + + .cfi_def_cfa_offset 8 // Return address + + PUSH_REG(rdi) // 16 + mov 0x10(%rsp), %rdi + PUSH_REG(rbx) + PUSH_REG(rbx) // 16 + PUSH_REG(rcx) + PUSH_REG(rdx) // 16 + PUSH_REG(rbp) + PUSH_REG(rsi) // 16 + PUSH_REG(r8) + PUSH_REG(r9) // 16 + PUSH_REG(r10) + PUSH_REG(r11) // 16 + PUSH_REG(r12) + PUSH_REG(r13) // 16 + PUSH_REG(r14) + PUSH_REG(r15) // 16 + + // Maybe use cpuid instead of macro to detect current vector size... +#ifdef __AVX512F__ + PUSH_ZMM_REG(zmm0) + PUSH_ZMM_REG(zmm1) + PUSH_ZMM_REG(zmm2) + PUSH_ZMM_REG(zmm3) + PUSH_ZMM_REG(zmm4) + PUSH_ZMM_REG(zmm5) + PUSH_ZMM_REG(zmm6) + PUSH_ZMM_REG(zmm7) +#elif defined __AVX__ + PUSH_YMM_REG(ymm0) + PUSH_YMM_REG(ymm1) + PUSH_YMM_REG(ymm2) + PUSH_YMM_REG(ymm3) + PUSH_YMM_REG(ymm4) + PUSH_YMM_REG(ymm5) + PUSH_YMM_REG(ymm6) + PUSH_YMM_REG(ymm7) +#elif defined __SSE__ + PUSH_XMM_REG(xmm0) + PUSH_XMM_REG(xmm1) + PUSH_XMM_REG(xmm2) + PUSH_XMM_REG(xmm3) + PUSH_XMM_REG(xmm4) + PUSH_XMM_REG(xmm5) + PUSH_XMM_REG(xmm6) + PUSH_XMM_REG(xmm7) +#endif + + // MMX registers are not used to pass arguments so we do not save them + + // Stack is just 8-byte aligned but callee will re-align to 16 + call _libva_drm_so_tramp_resolve + +#ifdef __AVX512F__ + POP_ZMM_REG(zmm7) + POP_ZMM_REG(zmm6) + POP_ZMM_REG(zmm5) + POP_ZMM_REG(zmm4) + POP_ZMM_REG(zmm3) + POP_ZMM_REG(zmm2) + POP_ZMM_REG(zmm1) + POP_ZMM_REG(zmm0) // 16 +#elif defined __AVX__ + POP_YMM_REG(ymm7) + POP_YMM_REG(ymm6) + POP_YMM_REG(ymm5) + POP_YMM_REG(ymm4) + POP_YMM_REG(ymm3) + POP_YMM_REG(ymm2) + POP_YMM_REG(ymm1) + POP_YMM_REG(ymm0) // 16 +#elif defined __SSE__ + POP_XMM_REG(xmm7) + POP_XMM_REG(xmm6) + POP_XMM_REG(xmm5) + POP_XMM_REG(xmm4) + POP_XMM_REG(xmm3) + POP_XMM_REG(xmm2) + POP_XMM_REG(xmm1) + POP_XMM_REG(xmm0) // 16 +#endif + + POP_REG(r15) + POP_REG(r14) // 16 + POP_REG(r13) + POP_REG(r12) // 16 + POP_REG(r11) + POP_REG(r10) // 16 + POP_REG(r9) + POP_REG(r8) // 16 + POP_REG(rsi) + POP_REG(rbp) // 16 + POP_REG(rdx) + POP_REG(rcx) // 16 + POP_REG(rbx) + POP_REG(rbx) // 16 + POP_REG(rdi) + + ret + + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaGetDisplayDRM + .p2align 4 + .type vaGetDisplayDRM, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaGetDisplayDRM +#endif +vaGetDisplayDRM: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_drm_so_tramp_table+0(%rip) + je 2f +1: + jmp *_libva_drm_so_tramp_table+0(%rip) +2: + pushq $0 + .cfi_adjust_cfa_offset 8 + call _libva_drm_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + diff --git a/webrtc-sys/src/vaapi/implib/libva.so.init.c b/webrtc-sys/src/vaapi/implib/libva.so.init.c new file mode 100644 index 000000000..6cd09a958 --- /dev/null +++ b/webrtc-sys/src/vaapi/implib/libva.so.init.c @@ -0,0 +1,333 @@ +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE // For RTLD_DEFAULT +#endif + +#define HAS_DLOPEN_CALLBACK 0 +#define HAS_DLSYM_CALLBACK 0 +#define NO_DLOPEN 0 +#define LAZY_LOAD 1 +#define THREAD_SAFE 1 + +#include +#include +#include +#include +#include + +#if THREAD_SAFE +#include +#endif + +// Sanity check for ARM to avoid puzzling runtime crashes +#ifdef __arm__ +# if defined __thumb__ && ! defined __THUMB_INTERWORK__ +# error "ARM trampolines need -mthumb-interwork to work in Thumb mode" +# endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define CHECK(cond, fmt, ...) do { \ + if(!(cond)) { \ + fprintf(stderr, "implib-gen: libva.so.2: " fmt "\n", ##__VA_ARGS__); \ + assert(0 && "Assertion in generated code"); \ + abort(); \ + } \ + } while(0) + +static void *lib_handle; +static int dlopened; + +#if ! NO_DLOPEN + +#if THREAD_SAFE + +// We need to consider two cases: +// - different threads calling intercepted APIs in parallel +// - same thread calling 2 intercepted APIs recursively +// due to dlopen calling library constructors +// (usually happens only under IMPLIB_EXPORT_SHIMS) + +// Current recursive mutex approach will deadlock +// if library constructor starts and joins a new thread +// which (directly or indirectly) calls another library function. +// Such situations should be very rare (although chances +// are higher when -DIMLIB_EXPORT_SHIMS are enabled). +// +// Similar issue is present in Glibc so hopefully it's +// not a big deal: // http://sourceware.org/bugzilla/show_bug.cgi?id=15686 +// (also google for "dlopen deadlock). + +static pthread_mutex_t mtx; +static int rec_count; + +static void init_lock(void) { + // We need recursive lock because dlopen will call library constructors + // which may call other intercepted APIs that will call load_library again. + // PTHREAD_RECURSIVE_MUTEX_INITIALIZER is not portable + // so we do it hard way. + + pthread_mutexattr_t attr; + CHECK(0 == pthread_mutexattr_init(&attr), "failed to init mutex"); + CHECK(0 == pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE), "failed to init mutex"); + + CHECK(0 == pthread_mutex_init(&mtx, &attr), "failed to init mutex"); +} + +static int lock(void) { + static pthread_once_t once = PTHREAD_ONCE_INIT; + CHECK(0 == pthread_once(&once, init_lock), "failed to init lock"); + + CHECK(0 == pthread_mutex_lock(&mtx), "failed to lock mutex"); + + return 0 == __sync_fetch_and_add(&rec_count, 1); +} + +static void unlock(void) { + __sync_fetch_and_add(&rec_count, -1); + CHECK(0 == pthread_mutex_unlock(&mtx), "failed to unlock mutex"); +} +#else +static int lock(void) { + return 1; +} +static void unlock(void) {} +#endif + +static int load_library(void) { + int publish = lock(); + + if (lib_handle) { + unlock(); + return publish; + } + +#if HAS_DLOPEN_CALLBACK + extern void *(const char *lib_name); + lib_handle = ("libva.so.2"); + CHECK(lib_handle, "failed to load library 'libva.so.2' via callback ''"); +#else + lib_handle = dlopen("libva.so.2", RTLD_LAZY | RTLD_GLOBAL); + CHECK(lib_handle, "failed to load library 'libva.so.2' via dlopen: %s", dlerror()); +#endif + + // With (non-default) IMPLIB_EXPORT_SHIMS we may call dlopen more than once + // so dlclose it if we are not the first ones + if (__sync_val_compare_and_swap(&dlopened, 0, 1)) { + dlclose(lib_handle); + } + + unlock(); + + return publish; +} + +// Run dtor as late as possible in case library functions are +// called in other global dtors +// FIXME: this may crash if one thread is calling into library +// while some other thread executes exit(). It's no clear +// how to fix this besides simply NOT dlclosing library at all. +static void __attribute__((destructor(101))) unload_lib(void) { + if (dlopened) { + dlclose(lib_handle); + lib_handle = 0; + dlopened = 0; + } +} +#endif + +#if ! NO_DLOPEN && ! LAZY_LOAD +static void __attribute__((constructor(101))) load_lib(void) { + load_library(); +} +#endif + +// TODO: convert to single 0-separated string +static const char *const sym_names[] = { + "disabled_va_TraceInit", + "vaAcquireBufferHandle", + "vaAssociateSubpicture", + "vaAttachProtectedSession", + "vaBeginPicture", + "vaBufferInfo", + "vaBufferSetNumElements", + "vaBufferTypeStr", + "vaConfigAttribTypeStr", + "vaCopy", + "vaCreateBuffer", + "vaCreateBuffer2", + "vaCreateConfig", + "vaCreateContext", + "vaCreateImage", + "vaCreateMFContext", + "vaCreateProtectedSession", + "vaCreateSubpicture", + "vaCreateSurfaces", + "vaDeassociateSubpicture", + "vaDeriveImage", + "vaDestroyBuffer", + "vaDestroyConfig", + "vaDestroyContext", + "vaDestroyImage", + "vaDestroyProtectedSession", + "vaDestroySubpicture", + "vaDestroySurfaces", + "vaDetachProtectedSession", + "vaDisplayIsValid", + "vaEndPicture", + "vaEntrypointStr", + "vaErrorStr", + "vaExportSurfaceHandle", + "vaGetConfigAttributes", + "vaGetDisplayAttributes", + "vaGetImage", + "vaGetLibFunc", + "vaInitialize", + "vaLockSurface", + "vaMFAddContext", + "vaMFReleaseContext", + "vaMFSubmit", + "vaMapBuffer", + "vaMapBuffer2", + "vaMaxNumConfigAttributes", + "vaMaxNumDisplayAttributes", + "vaMaxNumEntrypoints", + "vaMaxNumImageFormats", + "vaMaxNumProfiles", + "vaMaxNumSubpictureFormats", + "vaProfileStr", + "vaProtectedSessionExecute", + "vaPutImage", + "vaQueryConfigAttributes", + "vaQueryConfigEntrypoints", + "vaQueryConfigProfiles", + "vaQueryDisplayAttributes", + "vaQueryImageFormats", + "vaQueryProcessingRate", + "vaQuerySubpictureFormats", + "vaQuerySurfaceAttributes", + "vaQuerySurfaceError", + "vaQuerySurfaceStatus", + "vaQueryVendorString", + "vaQueryVideoProcFilterCaps", + "vaQueryVideoProcFilters", + "vaQueryVideoProcPipelineCaps", + "vaReleaseBufferHandle", + "vaRenderPicture", + "vaSetDisplayAttributes", + "vaSetDriverName", + "vaSetErrorCallback", + "vaSetImagePalette", + "vaSetInfoCallback", + "vaSetSubpictureChromakey", + "vaSetSubpictureGlobalAlpha", + "vaSetSubpictureImage", + "vaStatusStr", + "vaSyncBuffer", + "vaSyncSurface", + "vaSyncSurface2", + "vaTerminate", + "vaUnlockSurface", + "vaUnmapBuffer", + "va_TracePutSurface", + "va_TraceStatus", + "va_newDisplayContext", + "va_newDriverContext", + 0 +}; + +#define SYM_COUNT (sizeof(sym_names)/sizeof(sym_names[0]) - 1) + +extern void *_libva_so_tramp_table[]; + +// Can be sped up by manually parsing library symtab... +void *_libva_so_tramp_resolve(size_t i) { + assert(i < SYM_COUNT); + + int publish = 1; + + void *h = 0; +#if NO_DLOPEN + // Library with implementations must have already been loaded. + if (lib_handle) { + // User has specified loaded library + h = lib_handle; + } else { + // User hasn't provided us the loaded library so search the global namespace. +# ifndef IMPLIB_EXPORT_SHIMS + // If shim symbols are hidden we should search + // for first available definition of symbol in library list + h = RTLD_DEFAULT; +# else + // Otherwise look for next available definition + h = RTLD_NEXT; +# endif + } +#else + publish = load_library(); + h = lib_handle; + CHECK(h, "failed to resolve symbol '%s', library failed to load", sym_names[i]); +#endif + + void *addr; +#if HAS_DLSYM_CALLBACK + extern void *(void *handle, const char *sym_name); + addr = (h, sym_names[i]); + CHECK(addr, "failed to resolve symbol '%s' via callback ", sym_names[i]); +#else + // Dlsym is thread-safe so don't need to protect it. + addr = dlsym(h, sym_names[i]); + CHECK(addr, "failed to resolve symbol '%s' via dlsym: %s", sym_names[i], dlerror()); +#endif + + if (publish) { + // Use atomic to please Tsan and ensure that preceeding writes + // in library ctors have been delivered before publishing address + (void)__sync_val_compare_and_swap(&_libva_so_tramp_table[i], 0, addr); + } + + return addr; +} + +// Below APIs are not thread-safe +// and it's not clear how make them such +// (we can not know if some other thread is +// currently executing library code). + +// Helper for user to resolve all symbols +void _libva_so_tramp_resolve_all(void) { + size_t i; + for(i = 0; i < SYM_COUNT; ++i) + _libva_so_tramp_resolve(i); +} + +// Allows user to specify manually loaded implementation library. +void _libva_so_tramp_set_handle(void *handle) { + // TODO: call unload_lib ? + lib_handle = handle; + dlopened = 0; +} + +// Resets all resolved symbols. This is needed in case +// client code wants to reload interposed library multiple times. +void _libva_so_tramp_reset(void) { + // TODO: call unload_lib ? + memset(_libva_so_tramp_table, 0, SYM_COUNT * sizeof(_libva_so_tramp_table[0])); + lib_handle = 0; + dlopened = 0; +} + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/webrtc-sys/src/vaapi/implib/libva.so.tramp.S b/webrtc-sys/src/vaapi/implib/libva.so.tramp.S new file mode 100644 index 000000000..4c7f1540d --- /dev/null +++ b/webrtc-sys/src/vaapi/implib/libva.so.tramp.S @@ -0,0 +1,3184 @@ +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .section .note.GNU-stack,"",@progbits + + .data + + .globl _libva_so_tramp_table + .hidden _libva_so_tramp_table + .align 8 +_libva_so_tramp_table: + .zero 720 + + .text + + .globl _libva_so_tramp_resolve + .hidden _libva_so_tramp_resolve + + .globl _libva_so_save_regs_and_resolve + .hidden _libva_so_save_regs_and_resolve + .type _libva_so_save_regs_and_resolve, %function +_libva_so_save_regs_and_resolve: + .cfi_startproc + +#define PUSH_REG(reg) pushq %reg ; .cfi_adjust_cfa_offset 8; .cfi_rel_offset reg, 0 +#define POP_REG(reg) popq %reg ; .cfi_adjust_cfa_offset -8; .cfi_restore reg + +#define DEC_STACK(d) subq $d, %rsp; .cfi_adjust_cfa_offset d +#define INC_STACK(d) addq $d, %rsp; .cfi_adjust_cfa_offset -d + +#define PUSH_MMX_REG(reg) DEC_STACK(8); movq %reg, (%rsp); .cfi_rel_offset reg, 0 +#define POP_MMX_REG(reg) movq (%rsp), %reg; .cfi_restore reg; INC_STACK(8) + +#define PUSH_XMM_REG(reg) DEC_STACK(16); movdqa %reg, (%rsp); .cfi_rel_offset reg, 0 +#define POP_XMM_REG(reg) movdqa (%rsp), %reg; .cfi_restore reg; INC_STACK(16) + +// TODO: cfi_offset/cfi_restore +#define PUSH_YMM_REG(reg) DEC_STACK(32); vmovdqu %reg, (%rsp) +#define POP_YMM_REG(reg) vmovdqu (%rsp), %reg; INC_STACK(32) + +// TODO: cfi_offset/cfi_restore +#define PUSH_ZMM_REG(reg) DEC_STACK(64); vmovdqu32 %reg, (%rsp) +#define POP_ZMM_REG(reg) vmovdqu32 (%rsp), %reg; INC_STACK(64) + + // Slow path which calls dlsym, taken only on first call. + // All registers are stored to handle arbitrary calling conventions + // (except x87 FPU registers which do not have to be preserved). + // For Dwarf directives, read https://www.imperialviolet.org/2017/01/18/cfi.html. + + .cfi_def_cfa_offset 8 // Return address + + PUSH_REG(rdi) // 16 + mov 0x10(%rsp), %rdi + PUSH_REG(rbx) + PUSH_REG(rbx) // 16 + PUSH_REG(rcx) + PUSH_REG(rdx) // 16 + PUSH_REG(rbp) + PUSH_REG(rsi) // 16 + PUSH_REG(r8) + PUSH_REG(r9) // 16 + PUSH_REG(r10) + PUSH_REG(r11) // 16 + PUSH_REG(r12) + PUSH_REG(r13) // 16 + PUSH_REG(r14) + PUSH_REG(r15) // 16 + + // Maybe use cpuid instead of macro to detect current vector size... +#ifdef __AVX512F__ + PUSH_ZMM_REG(zmm0) + PUSH_ZMM_REG(zmm1) + PUSH_ZMM_REG(zmm2) + PUSH_ZMM_REG(zmm3) + PUSH_ZMM_REG(zmm4) + PUSH_ZMM_REG(zmm5) + PUSH_ZMM_REG(zmm6) + PUSH_ZMM_REG(zmm7) +#elif defined __AVX__ + PUSH_YMM_REG(ymm0) + PUSH_YMM_REG(ymm1) + PUSH_YMM_REG(ymm2) + PUSH_YMM_REG(ymm3) + PUSH_YMM_REG(ymm4) + PUSH_YMM_REG(ymm5) + PUSH_YMM_REG(ymm6) + PUSH_YMM_REG(ymm7) +#elif defined __SSE__ + PUSH_XMM_REG(xmm0) + PUSH_XMM_REG(xmm1) + PUSH_XMM_REG(xmm2) + PUSH_XMM_REG(xmm3) + PUSH_XMM_REG(xmm4) + PUSH_XMM_REG(xmm5) + PUSH_XMM_REG(xmm6) + PUSH_XMM_REG(xmm7) +#endif + + // MMX registers are not used to pass arguments so we do not save them + + // Stack is just 8-byte aligned but callee will re-align to 16 + call _libva_so_tramp_resolve + +#ifdef __AVX512F__ + POP_ZMM_REG(zmm7) + POP_ZMM_REG(zmm6) + POP_ZMM_REG(zmm5) + POP_ZMM_REG(zmm4) + POP_ZMM_REG(zmm3) + POP_ZMM_REG(zmm2) + POP_ZMM_REG(zmm1) + POP_ZMM_REG(zmm0) // 16 +#elif defined __AVX__ + POP_YMM_REG(ymm7) + POP_YMM_REG(ymm6) + POP_YMM_REG(ymm5) + POP_YMM_REG(ymm4) + POP_YMM_REG(ymm3) + POP_YMM_REG(ymm2) + POP_YMM_REG(ymm1) + POP_YMM_REG(ymm0) // 16 +#elif defined __SSE__ + POP_XMM_REG(xmm7) + POP_XMM_REG(xmm6) + POP_XMM_REG(xmm5) + POP_XMM_REG(xmm4) + POP_XMM_REG(xmm3) + POP_XMM_REG(xmm2) + POP_XMM_REG(xmm1) + POP_XMM_REG(xmm0) // 16 +#endif + + POP_REG(r15) + POP_REG(r14) // 16 + POP_REG(r13) + POP_REG(r12) // 16 + POP_REG(r11) + POP_REG(r10) // 16 + POP_REG(r9) + POP_REG(r8) // 16 + POP_REG(rsi) + POP_REG(rbp) // 16 + POP_REG(rdx) + POP_REG(rcx) // 16 + POP_REG(rbx) + POP_REG(rbx) // 16 + POP_REG(rdi) + + ret + + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl disabled_va_TraceInit + .p2align 4 + .type disabled_va_TraceInit, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden disabled_va_TraceInit +#endif +disabled_va_TraceInit: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+0(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+0(%rip) +2: + pushq $0 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaAcquireBufferHandle + .p2align 4 + .type vaAcquireBufferHandle, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaAcquireBufferHandle +#endif +vaAcquireBufferHandle: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+8(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+8(%rip) +2: + pushq $1 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaAssociateSubpicture + .p2align 4 + .type vaAssociateSubpicture, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaAssociateSubpicture +#endif +vaAssociateSubpicture: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+16(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+16(%rip) +2: + pushq $2 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaAttachProtectedSession + .p2align 4 + .type vaAttachProtectedSession, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaAttachProtectedSession +#endif +vaAttachProtectedSession: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+24(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+24(%rip) +2: + pushq $3 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaBeginPicture + .p2align 4 + .type vaBeginPicture, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaBeginPicture +#endif +vaBeginPicture: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+32(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+32(%rip) +2: + pushq $4 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaBufferInfo + .p2align 4 + .type vaBufferInfo, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaBufferInfo +#endif +vaBufferInfo: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+40(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+40(%rip) +2: + pushq $5 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaBufferSetNumElements + .p2align 4 + .type vaBufferSetNumElements, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaBufferSetNumElements +#endif +vaBufferSetNumElements: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+48(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+48(%rip) +2: + pushq $6 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaBufferTypeStr + .p2align 4 + .type vaBufferTypeStr, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaBufferTypeStr +#endif +vaBufferTypeStr: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+56(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+56(%rip) +2: + pushq $7 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaConfigAttribTypeStr + .p2align 4 + .type vaConfigAttribTypeStr, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaConfigAttribTypeStr +#endif +vaConfigAttribTypeStr: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+64(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+64(%rip) +2: + pushq $8 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaCopy + .p2align 4 + .type vaCopy, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaCopy +#endif +vaCopy: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+72(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+72(%rip) +2: + pushq $9 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaCreateBuffer + .p2align 4 + .type vaCreateBuffer, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaCreateBuffer +#endif +vaCreateBuffer: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+80(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+80(%rip) +2: + pushq $10 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaCreateBuffer2 + .p2align 4 + .type vaCreateBuffer2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaCreateBuffer2 +#endif +vaCreateBuffer2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+88(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+88(%rip) +2: + pushq $11 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaCreateConfig + .p2align 4 + .type vaCreateConfig, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaCreateConfig +#endif +vaCreateConfig: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+96(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+96(%rip) +2: + pushq $12 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaCreateContext + .p2align 4 + .type vaCreateContext, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaCreateContext +#endif +vaCreateContext: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+104(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+104(%rip) +2: + pushq $13 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaCreateImage + .p2align 4 + .type vaCreateImage, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaCreateImage +#endif +vaCreateImage: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+112(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+112(%rip) +2: + pushq $14 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaCreateMFContext + .p2align 4 + .type vaCreateMFContext, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaCreateMFContext +#endif +vaCreateMFContext: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+120(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+120(%rip) +2: + pushq $15 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaCreateProtectedSession + .p2align 4 + .type vaCreateProtectedSession, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaCreateProtectedSession +#endif +vaCreateProtectedSession: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+128(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+128(%rip) +2: + pushq $16 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaCreateSubpicture + .p2align 4 + .type vaCreateSubpicture, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaCreateSubpicture +#endif +vaCreateSubpicture: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+136(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+136(%rip) +2: + pushq $17 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaCreateSurfaces + .p2align 4 + .type vaCreateSurfaces, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaCreateSurfaces +#endif +vaCreateSurfaces: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+144(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+144(%rip) +2: + pushq $18 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaDeassociateSubpicture + .p2align 4 + .type vaDeassociateSubpicture, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaDeassociateSubpicture +#endif +vaDeassociateSubpicture: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+152(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+152(%rip) +2: + pushq $19 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaDeriveImage + .p2align 4 + .type vaDeriveImage, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaDeriveImage +#endif +vaDeriveImage: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+160(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+160(%rip) +2: + pushq $20 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaDestroyBuffer + .p2align 4 + .type vaDestroyBuffer, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaDestroyBuffer +#endif +vaDestroyBuffer: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+168(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+168(%rip) +2: + pushq $21 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaDestroyConfig + .p2align 4 + .type vaDestroyConfig, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaDestroyConfig +#endif +vaDestroyConfig: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+176(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+176(%rip) +2: + pushq $22 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaDestroyContext + .p2align 4 + .type vaDestroyContext, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaDestroyContext +#endif +vaDestroyContext: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+184(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+184(%rip) +2: + pushq $23 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaDestroyImage + .p2align 4 + .type vaDestroyImage, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaDestroyImage +#endif +vaDestroyImage: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+192(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+192(%rip) +2: + pushq $24 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaDestroyProtectedSession + .p2align 4 + .type vaDestroyProtectedSession, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaDestroyProtectedSession +#endif +vaDestroyProtectedSession: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+200(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+200(%rip) +2: + pushq $25 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaDestroySubpicture + .p2align 4 + .type vaDestroySubpicture, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaDestroySubpicture +#endif +vaDestroySubpicture: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+208(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+208(%rip) +2: + pushq $26 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaDestroySurfaces + .p2align 4 + .type vaDestroySurfaces, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaDestroySurfaces +#endif +vaDestroySurfaces: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+216(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+216(%rip) +2: + pushq $27 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaDetachProtectedSession + .p2align 4 + .type vaDetachProtectedSession, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaDetachProtectedSession +#endif +vaDetachProtectedSession: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+224(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+224(%rip) +2: + pushq $28 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaDisplayIsValid + .p2align 4 + .type vaDisplayIsValid, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaDisplayIsValid +#endif +vaDisplayIsValid: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+232(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+232(%rip) +2: + pushq $29 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaEndPicture + .p2align 4 + .type vaEndPicture, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaEndPicture +#endif +vaEndPicture: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+240(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+240(%rip) +2: + pushq $30 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaEntrypointStr + .p2align 4 + .type vaEntrypointStr, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaEntrypointStr +#endif +vaEntrypointStr: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+248(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+248(%rip) +2: + pushq $31 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaErrorStr + .p2align 4 + .type vaErrorStr, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaErrorStr +#endif +vaErrorStr: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+256(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+256(%rip) +2: + pushq $32 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaExportSurfaceHandle + .p2align 4 + .type vaExportSurfaceHandle, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaExportSurfaceHandle +#endif +vaExportSurfaceHandle: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+264(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+264(%rip) +2: + pushq $33 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaGetConfigAttributes + .p2align 4 + .type vaGetConfigAttributes, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaGetConfigAttributes +#endif +vaGetConfigAttributes: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+272(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+272(%rip) +2: + pushq $34 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaGetDisplayAttributes + .p2align 4 + .type vaGetDisplayAttributes, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaGetDisplayAttributes +#endif +vaGetDisplayAttributes: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+280(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+280(%rip) +2: + pushq $35 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaGetImage + .p2align 4 + .type vaGetImage, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaGetImage +#endif +vaGetImage: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+288(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+288(%rip) +2: + pushq $36 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaGetLibFunc + .p2align 4 + .type vaGetLibFunc, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaGetLibFunc +#endif +vaGetLibFunc: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+296(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+296(%rip) +2: + pushq $37 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaInitialize + .p2align 4 + .type vaInitialize, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaInitialize +#endif +vaInitialize: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+304(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+304(%rip) +2: + pushq $38 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaLockSurface + .p2align 4 + .type vaLockSurface, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaLockSurface +#endif +vaLockSurface: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+312(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+312(%rip) +2: + pushq $39 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaMFAddContext + .p2align 4 + .type vaMFAddContext, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaMFAddContext +#endif +vaMFAddContext: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+320(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+320(%rip) +2: + pushq $40 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaMFReleaseContext + .p2align 4 + .type vaMFReleaseContext, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaMFReleaseContext +#endif +vaMFReleaseContext: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+328(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+328(%rip) +2: + pushq $41 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaMFSubmit + .p2align 4 + .type vaMFSubmit, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaMFSubmit +#endif +vaMFSubmit: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+336(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+336(%rip) +2: + pushq $42 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaMapBuffer + .p2align 4 + .type vaMapBuffer, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaMapBuffer +#endif +vaMapBuffer: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+344(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+344(%rip) +2: + pushq $43 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaMapBuffer2 + .p2align 4 + .type vaMapBuffer2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaMapBuffer2 +#endif +vaMapBuffer2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+352(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+352(%rip) +2: + pushq $44 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaMaxNumConfigAttributes + .p2align 4 + .type vaMaxNumConfigAttributes, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaMaxNumConfigAttributes +#endif +vaMaxNumConfigAttributes: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+360(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+360(%rip) +2: + pushq $45 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaMaxNumDisplayAttributes + .p2align 4 + .type vaMaxNumDisplayAttributes, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaMaxNumDisplayAttributes +#endif +vaMaxNumDisplayAttributes: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+368(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+368(%rip) +2: + pushq $46 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaMaxNumEntrypoints + .p2align 4 + .type vaMaxNumEntrypoints, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaMaxNumEntrypoints +#endif +vaMaxNumEntrypoints: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+376(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+376(%rip) +2: + pushq $47 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaMaxNumImageFormats + .p2align 4 + .type vaMaxNumImageFormats, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaMaxNumImageFormats +#endif +vaMaxNumImageFormats: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+384(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+384(%rip) +2: + pushq $48 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaMaxNumProfiles + .p2align 4 + .type vaMaxNumProfiles, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaMaxNumProfiles +#endif +vaMaxNumProfiles: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+392(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+392(%rip) +2: + pushq $49 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaMaxNumSubpictureFormats + .p2align 4 + .type vaMaxNumSubpictureFormats, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaMaxNumSubpictureFormats +#endif +vaMaxNumSubpictureFormats: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+400(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+400(%rip) +2: + pushq $50 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaProfileStr + .p2align 4 + .type vaProfileStr, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaProfileStr +#endif +vaProfileStr: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+408(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+408(%rip) +2: + pushq $51 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaProtectedSessionExecute + .p2align 4 + .type vaProtectedSessionExecute, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaProtectedSessionExecute +#endif +vaProtectedSessionExecute: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+416(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+416(%rip) +2: + pushq $52 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaPutImage + .p2align 4 + .type vaPutImage, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaPutImage +#endif +vaPutImage: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+424(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+424(%rip) +2: + pushq $53 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaQueryConfigAttributes + .p2align 4 + .type vaQueryConfigAttributes, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaQueryConfigAttributes +#endif +vaQueryConfigAttributes: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+432(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+432(%rip) +2: + pushq $54 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaQueryConfigEntrypoints + .p2align 4 + .type vaQueryConfigEntrypoints, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaQueryConfigEntrypoints +#endif +vaQueryConfigEntrypoints: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+440(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+440(%rip) +2: + pushq $55 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaQueryConfigProfiles + .p2align 4 + .type vaQueryConfigProfiles, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaQueryConfigProfiles +#endif +vaQueryConfigProfiles: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+448(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+448(%rip) +2: + pushq $56 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaQueryDisplayAttributes + .p2align 4 + .type vaQueryDisplayAttributes, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaQueryDisplayAttributes +#endif +vaQueryDisplayAttributes: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+456(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+456(%rip) +2: + pushq $57 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaQueryImageFormats + .p2align 4 + .type vaQueryImageFormats, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaQueryImageFormats +#endif +vaQueryImageFormats: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+464(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+464(%rip) +2: + pushq $58 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaQueryProcessingRate + .p2align 4 + .type vaQueryProcessingRate, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaQueryProcessingRate +#endif +vaQueryProcessingRate: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+472(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+472(%rip) +2: + pushq $59 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaQuerySubpictureFormats + .p2align 4 + .type vaQuerySubpictureFormats, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaQuerySubpictureFormats +#endif +vaQuerySubpictureFormats: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+480(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+480(%rip) +2: + pushq $60 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaQuerySurfaceAttributes + .p2align 4 + .type vaQuerySurfaceAttributes, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaQuerySurfaceAttributes +#endif +vaQuerySurfaceAttributes: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+488(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+488(%rip) +2: + pushq $61 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaQuerySurfaceError + .p2align 4 + .type vaQuerySurfaceError, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaQuerySurfaceError +#endif +vaQuerySurfaceError: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+496(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+496(%rip) +2: + pushq $62 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaQuerySurfaceStatus + .p2align 4 + .type vaQuerySurfaceStatus, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaQuerySurfaceStatus +#endif +vaQuerySurfaceStatus: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+504(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+504(%rip) +2: + pushq $63 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaQueryVendorString + .p2align 4 + .type vaQueryVendorString, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaQueryVendorString +#endif +vaQueryVendorString: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+512(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+512(%rip) +2: + pushq $64 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaQueryVideoProcFilterCaps + .p2align 4 + .type vaQueryVideoProcFilterCaps, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaQueryVideoProcFilterCaps +#endif +vaQueryVideoProcFilterCaps: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+520(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+520(%rip) +2: + pushq $65 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaQueryVideoProcFilters + .p2align 4 + .type vaQueryVideoProcFilters, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaQueryVideoProcFilters +#endif +vaQueryVideoProcFilters: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+528(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+528(%rip) +2: + pushq $66 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaQueryVideoProcPipelineCaps + .p2align 4 + .type vaQueryVideoProcPipelineCaps, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaQueryVideoProcPipelineCaps +#endif +vaQueryVideoProcPipelineCaps: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+536(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+536(%rip) +2: + pushq $67 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaReleaseBufferHandle + .p2align 4 + .type vaReleaseBufferHandle, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaReleaseBufferHandle +#endif +vaReleaseBufferHandle: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+544(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+544(%rip) +2: + pushq $68 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaRenderPicture + .p2align 4 + .type vaRenderPicture, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaRenderPicture +#endif +vaRenderPicture: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+552(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+552(%rip) +2: + pushq $69 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaSetDisplayAttributes + .p2align 4 + .type vaSetDisplayAttributes, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaSetDisplayAttributes +#endif +vaSetDisplayAttributes: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+560(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+560(%rip) +2: + pushq $70 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaSetDriverName + .p2align 4 + .type vaSetDriverName, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaSetDriverName +#endif +vaSetDriverName: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+568(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+568(%rip) +2: + pushq $71 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaSetErrorCallback + .p2align 4 + .type vaSetErrorCallback, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaSetErrorCallback +#endif +vaSetErrorCallback: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+576(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+576(%rip) +2: + pushq $72 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaSetImagePalette + .p2align 4 + .type vaSetImagePalette, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaSetImagePalette +#endif +vaSetImagePalette: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+584(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+584(%rip) +2: + pushq $73 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaSetInfoCallback + .p2align 4 + .type vaSetInfoCallback, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaSetInfoCallback +#endif +vaSetInfoCallback: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+592(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+592(%rip) +2: + pushq $74 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaSetSubpictureChromakey + .p2align 4 + .type vaSetSubpictureChromakey, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaSetSubpictureChromakey +#endif +vaSetSubpictureChromakey: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+600(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+600(%rip) +2: + pushq $75 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaSetSubpictureGlobalAlpha + .p2align 4 + .type vaSetSubpictureGlobalAlpha, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaSetSubpictureGlobalAlpha +#endif +vaSetSubpictureGlobalAlpha: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+608(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+608(%rip) +2: + pushq $76 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaSetSubpictureImage + .p2align 4 + .type vaSetSubpictureImage, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaSetSubpictureImage +#endif +vaSetSubpictureImage: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+616(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+616(%rip) +2: + pushq $77 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaStatusStr + .p2align 4 + .type vaStatusStr, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaStatusStr +#endif +vaStatusStr: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+624(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+624(%rip) +2: + pushq $78 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaSyncBuffer + .p2align 4 + .type vaSyncBuffer, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaSyncBuffer +#endif +vaSyncBuffer: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+632(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+632(%rip) +2: + pushq $79 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaSyncSurface + .p2align 4 + .type vaSyncSurface, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaSyncSurface +#endif +vaSyncSurface: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+640(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+640(%rip) +2: + pushq $80 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaSyncSurface2 + .p2align 4 + .type vaSyncSurface2, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaSyncSurface2 +#endif +vaSyncSurface2: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+648(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+648(%rip) +2: + pushq $81 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaTerminate + .p2align 4 + .type vaTerminate, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaTerminate +#endif +vaTerminate: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+656(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+656(%rip) +2: + pushq $82 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaUnlockSurface + .p2align 4 + .type vaUnlockSurface, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaUnlockSurface +#endif +vaUnlockSurface: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+664(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+664(%rip) +2: + pushq $83 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl vaUnmapBuffer + .p2align 4 + .type vaUnmapBuffer, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden vaUnmapBuffer +#endif +vaUnmapBuffer: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+672(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+672(%rip) +2: + pushq $84 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl va_TracePutSurface + .p2align 4 + .type va_TracePutSurface, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden va_TracePutSurface +#endif +va_TracePutSurface: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+680(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+680(%rip) +2: + pushq $85 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl va_TraceStatus + .p2align 4 + .type va_TraceStatus, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden va_TraceStatus +#endif +va_TraceStatus: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+688(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+688(%rip) +2: + pushq $86 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl va_newDisplayContext + .p2align 4 + .type va_newDisplayContext, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden va_newDisplayContext +#endif +va_newDisplayContext: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+696(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+696(%rip) +2: + pushq $87 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + +/* + * Copyright 2018-2025 Yury Gribov + * + * The MIT License (MIT) + * + * Use of this source code is governed by MIT license that can be + * found in the LICENSE.txt file. + */ + + .globl va_newDriverContext + .p2align 4 + .type va_newDriverContext, %function +#ifndef IMPLIB_EXPORT_SHIMS + .hidden va_newDriverContext +#endif +va_newDriverContext: + .cfi_startproc + .cfi_def_cfa_offset 8 // Return address + // Intel opt. manual says to + // "make the fall-through code following a conditional branch be the likely target for a branch with a forward target" + // to hint static predictor. + cmpq $0, _libva_so_tramp_table+704(%rip) + je 2f +1: + jmp *_libva_so_tramp_table+704(%rip) +2: + pushq $88 + .cfi_adjust_cfa_offset 8 + call _libva_so_save_regs_and_resolve + addq $8, %rsp + .cfi_adjust_cfa_offset -8 + jmp *%rax + .cfi_endproc + diff --git a/webrtc-sys/src/vaapi/vaapi_display_drm.cpp b/webrtc-sys/src/vaapi/vaapi_display_drm.cpp new file mode 100644 index 000000000..ca382a769 --- /dev/null +++ b/webrtc-sys/src/vaapi/vaapi_display_drm.cpp @@ -0,0 +1,135 @@ +#include "vaapi_display_drm.h" + +#include +#include +#include +#include +#include + +#ifdef IN_LIBVA +#include "va/drm/va_drm.h" +#else +#include +#endif + +#include "rtc_base/logging.h" + +static bool check_h264_encoding_support(VADisplay va_display) { + VAProfile profile_list[] = {VAProfileH264High, VAProfileH264Main, + VAProfileH264ConstrainedBaseline}; + + VAProfile h264_profile = VAProfileH264ConstrainedBaseline; + VAEntrypoint* entrypoints; + int num_entrypoints, slice_entrypoint; + bool support_encode = false; + int selected_entrypoint = -1; + int major_ver, minor_ver; + VAStatus va_status; + uint32_t i; + + if (!va_display) { + return false; + } + + va_status = vaInitialize(va_display, &major_ver, &minor_ver); + + if (major_ver < 0 || minor_ver < 0 || va_status != VA_STATUS_SUCCESS) { + RTC_LOG(LS_ERROR) << "vaInitialize failed"; + return false; + } + + num_entrypoints = vaMaxNumEntrypoints(va_display); + entrypoints = new VAEntrypoint[num_entrypoints * sizeof(*entrypoints)]; + if (!entrypoints) { + RTC_LOG(LS_ERROR) << "failed to allocate VA entrypoints"; + return false; + } + + /* use the highest profile */ + for (i = 0; i < sizeof(profile_list) / sizeof(profile_list[0]); i++) { + if ((h264_profile != ~0) && h264_profile != profile_list[i]) + continue; + + h264_profile = profile_list[i]; + vaQueryConfigEntrypoints(va_display, h264_profile, entrypoints, + &num_entrypoints); + for (slice_entrypoint = 0; slice_entrypoint < num_entrypoints; + slice_entrypoint++) { + if ((entrypoints[slice_entrypoint] == VAEntrypointEncSlice) || + (entrypoints[slice_entrypoint] == VAEntrypointEncSliceLP)) { + support_encode = true; + selected_entrypoint = entrypoints[slice_entrypoint]; + break; + } + } + if (support_encode) { + RTC_LOG(LS_INFO) << "Using EntryPoint - " << selected_entrypoint; + break; + } + } + + if (support_encode) { + RTC_LOG(LS_INFO) << "Supported H264 Encoder, Using EntryPoint - " + << selected_entrypoint; + } else { + RTC_LOG(LS_ERROR) + << "Can't find VAEntrypointEncSlice or VAEntrypointEncSliceLP for " + "H264 profiles"; + delete[] entrypoints; + return false; + } + + delete[] entrypoints; + return true; +} + +static VADisplay va_open_display_drm(int* drm_fd) { + VADisplay va_dpy; + int i; + + static const char* drm_device_paths[] = {"/dev/dri/renderD128", + "/dev/dri/renderD129", NULL}; + for (i = 0; drm_device_paths[i]; i++) { + *drm_fd = open(drm_device_paths[i], O_RDWR); + if (*drm_fd < 0) + continue; + + va_dpy = vaGetDisplayDRM(*drm_fd); + vaSetErrorCallback(va_dpy, NULL, NULL); + vaSetInfoCallback(va_dpy, NULL, NULL); + if (va_dpy && check_h264_encoding_support(va_dpy)) + return va_dpy; + + close(*drm_fd); + *drm_fd = -1; + } + return NULL; +} + +namespace livekit { + +bool VaapiDisplayDrm::Open() { + va_display_ = va_open_display_drm(&drm_fd_); + if (!va_display_) { + std::cout << "Failed to open VA display. Maybe the AMD video driver or libva-dev/libdrm-dev is not installed?" << std::endl; + return false; + } + return true; +} + +bool VaapiDisplayDrm::isOpen() const { + return va_display_ != nullptr; +} + +void VaapiDisplayDrm::Close() { + if (va_display_) { + if (drm_fd_ < 0) + return; + + close(drm_fd_); + drm_fd_ = -1; + va_display_ = nullptr; + } +} + +} // namespace livekit diff --git a/webrtc-sys/src/vaapi/vaapi_display_drm.h b/webrtc-sys/src/vaapi/vaapi_display_drm.h new file mode 100644 index 000000000..f7fb22627 --- /dev/null +++ b/webrtc-sys/src/vaapi/vaapi_display_drm.h @@ -0,0 +1,35 @@ +#ifndef VAAPI_DISPLAY_DRM_H_ +#define VAAPI_DISPLAY_DRM_H_ + +#include +#include + +namespace livekit { + +// VAAPI drm display wrapper class +class VaapiDisplayDrm { + public: + VaapiDisplayDrm() = default; + VaapiDisplayDrm(const VaapiDisplayDrm&) = delete; + ~VaapiDisplayDrm() = default; + + // Initialize the VAAPI display + bool Open(); + + // Check if the VAAPI display is open + bool isOpen() const; + + // Close the VAAPI display + void Close(); + + // Get the VAAPI display handle + VADisplay display() const { return va_display_; } + + private: + VADisplay va_display_; + int drm_fd_; +}; + +} // namespace livekit + +#endif // VAAPI_DISPLAY_DRM_H_ diff --git a/webrtc-sys/src/vaapi/vaapi_display_win32.cpp b/webrtc-sys/src/vaapi/vaapi_display_win32.cpp new file mode 100644 index 000000000..34c94d683 --- /dev/null +++ b/webrtc-sys/src/vaapi/vaapi_display_win32.cpp @@ -0,0 +1,192 @@ +/* + * Copyright © Microsoft Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + + #include "vaapi_display_win32.h" + +#include +#include +#include +#include + +static const char* g_device_name; + +void dxcore_resolve_adapter(const char* adapter_string, + /*out*/ bool* ptr_device_found, + /*out*/ LUID* ptr_adapter_luid) { + int selected_adapter_index = -1; + IDXCoreAdapterFactory* factory = nullptr; + IDXCoreAdapterList* adapter_list = nullptr; + IDXCoreAdapter* adapter = nullptr; + typedef HRESULT(WINAPI * PFN_CREATE_DXCORE_ADAPTER_FACTORY)(REFIID riid, + void** ppFactory); + PFN_CREATE_DXCORE_ADAPTER_FACTORY DXCoreCreateAdapterFactory; + HRESULT hr = S_OK; + + memset(ptr_adapter_luid, 0, sizeof(LUID)); + *ptr_device_found = false; + + HMODULE dxcore_mod = LoadLibraryA("DXCore.DLL"); + if (!dxcore_mod) { + fprintf(stderr, "Failed to load DXCore.DLL to enumerate adapters.\n"); + goto fail; + } + + DXCoreCreateAdapterFactory = + (PFN_CREATE_DXCORE_ADAPTER_FACTORY)GetProcAddress( + dxcore_mod, "DXCoreCreateAdapterFactory"); + if (!DXCoreCreateAdapterFactory) { + fprintf(stderr, + "Failed to load DXCoreCreateAdapterFactory from DXCore.DLL.\n"); + goto fail; + } + + hr = DXCoreCreateAdapterFactory(IID_IDXCoreAdapterFactory, (void**)&factory); + if (FAILED(hr)) { + fprintf(stderr, "DXCoreCreateAdapterFactory failed: %lx\n", hr); + goto fail; + } + + hr = + factory->CreateAdapterList(1, &DXCORE_ADAPTER_ATTRIBUTE_D3D12_GRAPHICS, + IID_IDXCoreAdapterList, (void**)&adapter_list); + if (FAILED(hr)) { + fprintf(stderr, "CreateAdapterList failed: %lx\n", hr); + goto fail; + } + + if (adapter_string && + (sscanf_s(adapter_string, "%d", &selected_adapter_index) != 1)) { + fprintf(stderr, "Invalid device index received for -hwaccel_device %s\n", + adapter_string ? adapter_string : ""); + } + + if (!adapter_string) + fprintf(stdout, "Available devices for --display win32:\n"); + for (int i = 0; i < adapter_list->GetAdapterCount(); i++) { + if (SUCCEEDED(adapter_list->GetAdapter(i, IID_IDXCoreAdapter, + (void**)&adapter))) { + size_t desc_size = 0; + if (FAILED(adapter->GetPropertySize( + DXCoreAdapterProperty::DriverDescription, &desc_size))) { + adapter->Release(); + continue; + } + + char* adapter_name = (char*)malloc(desc_size); + if (!adapter_name) { + adapter->Release(); + continue; + } + + if (FAILED(adapter->GetProperty(DXCoreAdapterProperty::DriverDescription, + desc_size, adapter_name))) { + free(adapter_name); + adapter->Release(); + continue; + } + + LUID cur_adapter_luid = {0, 0}; + if (FAILED(adapter->GetProperty(DXCoreAdapterProperty::InstanceLuid, + &cur_adapter_luid))) { + free(adapter_name); + adapter->Release(); + continue; + } + + if (selected_adapter_index == i) { + *ptr_adapter_luid = cur_adapter_luid; + *ptr_device_found = true; + } + + if (!adapter_string) + fprintf(stdout, + "\tDevice Index: %d Device LUID: %lu %ld - Device Name: %s\n", + i, cur_adapter_luid.LowPart, cur_adapter_luid.HighPart, + adapter_name); + free(adapter_name); + adapter->Release(); + } + } + +fail: + if (adapter_list) + adapter_list->Release(); + if (factory) + factory->Release(); + if (dxcore_mod) + FreeLibrary(dxcore_mod); +} + +static VADisplay va_open_display_win32(void) { + LUID adapter_luid = {0, 0}; + bool device_found = false; + if (g_device_name) { + bool print_devices = (0 == strcmp(g_device_name, "help")); + dxcore_resolve_adapter(print_devices ? NULL : g_device_name, &device_found, + &adapter_luid); + if (print_devices) { + exit(0); + } else if (g_device_name && !device_found) { + fprintf(stderr, + "Could not find device %s for --display win32. Please try " + "--device help for a list of available devices.\n", + g_device_name); + exit(0); + } + } + + // Adapter automatic selection supported by sending NULL adapter to + // vaGetDisplayWin32 + return vaGetDisplayWin32(device_found ? &adapter_luid : NULL); +} + +static void va_close_display_win32(VADisplay va_dpy) {} + +namespace livekit { + +VaapiDisplayWin32::VaapiDisplayWin32() : va_display_(nullptr) { + putenv("LIBVA_DRIVER_NAME=vaon12"); + putenv("LIBVA_DRIVERS_PATH=."); +} + +bool VaapiDisplayWin32::Open() { + va_display_ = va_open_display_win32(); + if (!va_display_) { + fprintf(stderr, "Failed to open VA display\n"); + return false; + } + return true; +} + +bool VaapiDisplayWin32::isOpen() const { + return va_display_ != nullptr; +} + +void VaapiDisplayWin32::Close() { + if (va_display_) { + va_close_display_win32(va_display_); + va_display_ = nullptr; + } +} + +} // namespace livekit diff --git a/webrtc-sys/src/vaapi/vaapi_display_win32.h b/webrtc-sys/src/vaapi/vaapi_display_win32.h new file mode 100644 index 000000000..9e6332816 --- /dev/null +++ b/webrtc-sys/src/vaapi/vaapi_display_win32.h @@ -0,0 +1,33 @@ +#ifndef VAAPI_DISPLAY_WIN32_H_ +#define VAAPI_DISPLAY_WIN32_H_ + +#include +#include + +namespace livekit { + +// VAAPI win32 display wrapper class +class VaapiDisplayWin32 { + public: + VaapiDisplayWin32(); + ~VaapiDisplayWin32() {} + + // Initialize the VAAPI display + bool Open(); + + // Check if the VAAPI display is open + bool isOpen() const; + + // Close the VAAPI display + void Close(); + + // Get the VAAPI display handle + VADisplay display() const { return va_display_; } + + private: + VADisplay va_display_ = nullptr; +}; + +} // namespace livekit + +#endif // VAAPI_DISPLAY_WIN32_H_ diff --git a/webrtc-sys/src/vaapi/vaapi_encoder_factory.cpp b/webrtc-sys/src/vaapi/vaapi_encoder_factory.cpp new file mode 100644 index 000000000..83c29a645 --- /dev/null +++ b/webrtc-sys/src/vaapi/vaapi_encoder_factory.cpp @@ -0,0 +1,76 @@ +#include "vaapi_encoder_factory.h" + +#include + +#include "h264_encoder_impl.h" + +#include + +#if defined(WIN32) +#include "vaapi_display_win32.h" +using VaapiDisplay = livekit::VaapiDisplayWin32; +#elif defined(__linux__) +#include "vaapi_display_drm.h" +using VaapiDisplay = livekit::VaapiDisplayDrm ; +#endif + +namespace webrtc { + +VAAPIVideoEncoderFactory::VAAPIVideoEncoderFactory() { + std::map baselineParameters = { + {"profile-level-id", "42e01f"}, + {"level-asymmetry-allowed", "1"}, + {"packetization-mode", "1"}, + }; + supported_formats_.push_back(SdpVideoFormat("H264", baselineParameters)); + /* + std::map highParameters = { + {"profile-level-id", "4d0032"}, + {"level-asymmetry-allowed", "1"}, + {"packetization-mode", "1"}, + }; + + supported_formats_.push_back(SdpVideoFormat("H264", highParameters)); + */ +} + +VAAPIVideoEncoderFactory::~VAAPIVideoEncoderFactory() {} + +bool VAAPIVideoEncoderFactory::IsSupported() { + // Check if VAAPI is supported by the environment. + // This could involve checking if the VAAPI display can be opened. + VaapiDisplay vaapi_display; + if (!vaapi_display.Open()) { + std::cerr << "Failed to open VAAPI display." << std::endl; + return false; + } + + vaapi_display.Close(); + // If we can open the VAAPI display, we consider it supported. + std::cout << "VAAPI is supported." << std::endl; + return true; +} + +std::unique_ptr VAAPIVideoEncoderFactory::Create( + const Environment& env, + const SdpVideoFormat& format) { + // Check if the requested format is supported. + for (const auto& supported_format : supported_formats_) { + if (format.IsSameCodec(supported_format)) { + // If the format is supported, create and return the encoder. + return std::make_unique(env, format); + } + } + return nullptr; +} +std::vector VAAPIVideoEncoderFactory::GetSupportedFormats() + const { + return supported_formats_; +} + +std::vector VAAPIVideoEncoderFactory::GetImplementations() + const { + return supported_formats_; +} + +} // namespace webrtc diff --git a/webrtc-sys/src/vaapi/vaapi_encoder_factory.h b/webrtc-sys/src/vaapi/vaapi_encoder_factory.h new file mode 100644 index 000000000..c1e576685 --- /dev/null +++ b/webrtc-sys/src/vaapi/vaapi_encoder_factory.h @@ -0,0 +1,40 @@ + + + #ifndef VAAPI_VIDEO_ENCODER_FACTORY_H_ + #define VAAPI_VIDEO_ENCODER_FACTORY_H_ + + #include + + #include "api/environment/environment.h" + #include "api/video_codecs/sdp_video_format.h" + #include "api/video_codecs/video_encoder_factory.h" + + namespace webrtc { + + class VAAPIVideoEncoderFactory : public VideoEncoderFactory { + public: + VAAPIVideoEncoderFactory(); + ~VAAPIVideoEncoderFactory() override; + + static bool IsSupported(); + + std::unique_ptr Create(const Environment& env, + const SdpVideoFormat& format) override; + + // Returns a list of supported codecs in order of preference. + std::vector GetSupportedFormats() const override; + + std::vector GetImplementations() const override; + + std::unique_ptr GetEncoderSelector() const override { + return nullptr; + } + + private: + std::vector supported_formats_; + }; + + } // namespace webrtc + + #endif // VAAPI_VIDEO_ENCODER_FACTORY_H_ + \ No newline at end of file diff --git a/webrtc-sys/src/vaapi/vaapi_h264_encoder_wrapper.cpp b/webrtc-sys/src/vaapi/vaapi_h264_encoder_wrapper.cpp new file mode 100644 index 000000000..b9361273b --- /dev/null +++ b/webrtc-sys/src/vaapi/vaapi_h264_encoder_wrapper.cpp @@ -0,0 +1,1880 @@ +#include "vaapi_h264_encoder_wrapper.h" + +#include +#include +#include +#include + +#include + +#include "rtc_base/logging.h" + +#define NAL_REF_IDC_NONE 0 +#define NAL_REF_IDC_LOW 1 +#define NAL_REF_IDC_MEDIUM 2 +#define NAL_REF_IDC_HIGH 3 + +#define NAL_NON_IDR 1 +#define NAL_IDR 5 +#define NAL_SPS 7 +#define NAL_PPS 8 +#define NAL_SEI 6 + +#define SLICE_TYPE_P 0 +#define SLICE_TYPE_B 1 +#define SLICE_TYPE_I 2 +#define IS_P_SLICE(type) (SLICE_TYPE_P == (type)) +#define IS_B_SLICE(type) (SLICE_TYPE_B == (type)) +#define IS_I_SLICE(type) (SLICE_TYPE_I == (type)) + +#define ENTROPY_MODE_CAVLC 0 +#define ENTROPY_MODE_CABAC 1 + +#define PROFILE_IDC_BASELINE 66 +#define PROFILE_IDC_MAIN 77 +#define PROFILE_IDC_HIGH 100 + +#define BITSTREAM_ALLOCATE_STEPPING 4096 + +static const uint32_t MaxFrameNum = (2 << 16); +static const uint32_t MaxPicOrderCntLsb = (2 << 8); +static const uint32_t Log2MaxFrameNum = 16; +static const uint32_t Log2MaxPicOrderCntLsb = 8; +static const uint32_t num_ref_frames = 2; +static const int srcyuv_fourcc = VA_FOURCC_NV12; +static const uint32_t frame_slices = 1; + +static const int rc_default_modes[] = { + VA_RC_VBR, VA_RC_CQP, VA_RC_VBR_CONSTRAINED, + VA_RC_CBR, VA_RC_VCM, VA_RC_NONE, +}; + +VAImageFormat kImageFormatI420 = { + .fourcc = VA_FOURCC_I420, + .byte_order = VA_LSB_FIRST, + .bits_per_pixel = 12, +}; + +static int upload_surface_yuv(VADisplay va_dpy, + VASurfaceID surface_id, + int src_fourcc, + int src_width, + int src_height, + uint8_t* src_Y, + uint8_t* src_U, + uint8_t* src_V) { + VAImage surface_image; + uint8_t *surface_p = NULL, *Y_start = NULL, *U_start = NULL; + int Y_pitch = 0, U_pitch = 0, row; + VAStatus va_status; + + va_status = vaDeriveImage(va_dpy, surface_id, &surface_image); + if (va_status != VA_STATUS_SUCCESS) { + // If the driver does not support vaDeriveImage, create a new image. + va_status = vaCreateImage(va_dpy, &kImageFormatI420, src_width, src_height, + &surface_image); + if (va_status != VA_STATUS_SUCCESS) { + RTC_LOG(LS_ERROR) << "vaCreateImage failed with status " << va_status; + return -1; + } + } + + vaMapBuffer(va_dpy, surface_image.buf, (void**)&surface_p); + assert(VA_STATUS_SUCCESS == va_status); + + Y_start = surface_p; + Y_pitch = surface_image.pitches[0]; + switch (surface_image.format.fourcc) { + case VA_FOURCC_NV12: + U_start = (unsigned char*)surface_p + surface_image.offsets[1]; + U_pitch = surface_image.pitches[1]; + break; + case VA_FOURCC_I420: + U_start = (unsigned char*)surface_p + surface_image.offsets[1]; + U_pitch = surface_image.pitches[1]; + break; + case VA_FOURCC_YV12: + U_start = (unsigned char*)surface_p + surface_image.offsets[2]; + U_pitch = surface_image.pitches[2]; + break; + case VA_FOURCC_YUY2: + U_start = surface_p + 1; + U_pitch = surface_image.pitches[0]; + break; + default: + assert(0); + } + + /* copy Y plane */ + for (row = 0; row < src_height; row++) { + uint8_t* Y_row = Y_start + row * Y_pitch; + memcpy(Y_row, src_Y + row * src_width, src_width); + } + for (row = 0; row < src_height / 2; row++) { + uint8_t* U_row = U_start + row * U_pitch; + uint8_t *u_ptr = NULL, *v_ptr = NULL; + int j; + if (src_fourcc == VA_FOURCC_NV12) { + memcpy(U_row, src_U + row * src_width, src_width); + break; + } else if (src_fourcc == VA_FOURCC_I420) { + u_ptr = src_U + row * (src_width / 2); + v_ptr = src_V + row * (src_width / 2); + } else if (src_fourcc == VA_FOURCC_YV12) { + v_ptr = src_U + row * (src_width / 2); + u_ptr = src_V + row * (src_width / 2); + } + if ((src_fourcc == VA_FOURCC_I420) || (src_fourcc == VA_FOURCC_YV12)) { + for (j = 0; j < src_width / 2; j++) { + U_row[2 * j] = u_ptr[j]; + U_row[2 * j + 1] = v_ptr[j]; + } + } + } + vaUnmapBuffer(va_dpy, surface_image.buf); + vaDestroyImage(va_dpy, surface_image.image_id); + + return 0; +} + +#define MIN(a, b) ((a) > (b) ? (b) : (a)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) + +struct __bitstream { + uint32_t* buffer; + int bit_offset; + int max_size_in_dword; +}; +typedef struct __bitstream bitstream; + +static uint32_t va_swap32(uint32_t val) { + unsigned char* pval = (unsigned char*)&val; + + return ((pval[0] << 24) | (pval[1] << 16) | (pval[2] << 8) | (pval[3] << 0)); +} + +static void bitstream_start(bitstream* bs) { + bs->max_size_in_dword = BITSTREAM_ALLOCATE_STEPPING; + bs->buffer = (uint32_t*)calloc(bs->max_size_in_dword * sizeof(int), 1); + assert(bs->buffer); + bs->bit_offset = 0; +} + +static void bitstream_end(bitstream* bs) { + int pos = (bs->bit_offset >> 5); + int bit_offset = (bs->bit_offset & 0x1f); + int bit_left = 32 - bit_offset; + + if (bit_offset) { + bs->buffer[pos] = va_swap32((bs->buffer[pos] << bit_left)); + } +} + +static void bitstream_put_ui(bitstream* bs, uint32_t val, int size_in_bits) { + int pos = (bs->bit_offset >> 5); + int bit_offset = (bs->bit_offset & 0x1f); + int bit_left = 32 - bit_offset; + + if (!size_in_bits) + return; + + bs->bit_offset += size_in_bits; + + if (bit_left > size_in_bits) { + bs->buffer[pos] = (bs->buffer[pos] << size_in_bits | val); + } else { + size_in_bits -= bit_left; + bs->buffer[pos] = (bs->buffer[pos] << bit_left) | (val >> size_in_bits); + bs->buffer[pos] = va_swap32(bs->buffer[pos]); + + if (pos + 1 == bs->max_size_in_dword) { + bs->max_size_in_dword += BITSTREAM_ALLOCATE_STEPPING; + bs->buffer = bs->buffer = (uint32_t*)realloc( + bs->buffer, bs->max_size_in_dword * sizeof(uint32_t)); + assert(bs->buffer); + } + + bs->buffer[pos + 1] = val; + } +} + +static void bitstream_put_ue(bitstream* bs, uint32_t val) { + int size_in_bits = 0; + int tmp_val = ++val; + + while (tmp_val) { + tmp_val >>= 1; + size_in_bits++; + } + + bitstream_put_ui(bs, 0, size_in_bits - 1); // leading zero + bitstream_put_ui(bs, val, size_in_bits); +} + +static void bitstream_put_se(bitstream* bs, int val) { + uint32_t new_val; + + if (val <= 0) + new_val = -2 * val; + else + new_val = 2 * val - 1; + + bitstream_put_ue(bs, new_val); +} + +static void bitstream_byte_aligning(bitstream* bs, int bit) { + int bit_offset = (bs->bit_offset & 0x7); + int bit_left = 8 - bit_offset; + int new_val; + + if (!bit_offset) + return; + + assert(bit == 0 || bit == 1); + + if (bit) + new_val = (1 << bit_left) - 1; + else + new_val = 0; + + bitstream_put_ui(bs, new_val, bit_left); +} + +static void rbsp_trailing_bits(bitstream* bs) { + bitstream_put_ui(bs, 1, 1); + bitstream_byte_aligning(bs, 0); +} + +static void nal_start_code_prefix(bitstream* bs) { + bitstream_put_ui(bs, 0x00000001, 32); +} + +static void nal_header(bitstream* bs, int nal_ref_idc, int nal_unit_type) { + bitstream_put_ui(bs, 0, 1); /* forbidden_zero_bit: 0 */ + bitstream_put_ui(bs, nal_ref_idc, 2); + bitstream_put_ui(bs, nal_unit_type, 5); +} + +static void sps_rbsp(VA264Context* context, bitstream* bs) { + int profile_idc = PROFILE_IDC_BASELINE; + + if (context->config.h264_profile == VAProfileH264High) + profile_idc = PROFILE_IDC_HIGH; + else if (context->config.h264_profile == VAProfileH264Main) + profile_idc = PROFILE_IDC_MAIN; + + bitstream_put_ui(bs, profile_idc, 8); /* profile_idc */ + bitstream_put_ui(bs, !!(context->constraint_set_flag & 1), + 1); /* constraint_set0_flag */ + bitstream_put_ui(bs, !!(context->constraint_set_flag & 2), + 1); /* constraint_set1_flag */ + bitstream_put_ui(bs, !!(context->constraint_set_flag & 4), + 1); /* constraint_set2_flag */ + bitstream_put_ui(bs, !!(context->constraint_set_flag & 8), + 1); /* constraint_set3_flag */ + bitstream_put_ui(bs, 0, 4); /* reserved_zero_4bits */ + bitstream_put_ui(bs, context->seq_param.level_idc, 8); /* level_idc */ + bitstream_put_ue( + bs, context->seq_param.seq_parameter_set_id); /* seq_parameter_set_id */ + + if (profile_idc == PROFILE_IDC_HIGH) { + bitstream_put_ue(bs, 1); /* chroma_format_idc = 1, 4:2:0 */ + bitstream_put_ue(bs, 0); /* bit_depth_luma_minus8 */ + bitstream_put_ue(bs, 0); /* bit_depth_chroma_minus8 */ + bitstream_put_ui(bs, 0, 1); /* qpprime_y_zero_transform_bypass_flag */ + bitstream_put_ui(bs, 0, 1); /* seq_scaling_matrix_present_flag */ + } + + bitstream_put_ue( + bs, context->seq_param.seq_fields.bits + .log2_max_frame_num_minus4); /* log2_max_frame_num_minus4 */ + bitstream_put_ue(bs, context->seq_param.seq_fields.bits + .pic_order_cnt_type); /* pic_order_cnt_type */ + + if (context->seq_param.seq_fields.bits.pic_order_cnt_type == 0) + bitstream_put_ue( + bs, + context->seq_param.seq_fields.bits + .log2_max_pic_order_cnt_lsb_minus4); /* log2_max_pic_order_cnt_lsb_minus4 + */ + else { + assert(0); + } + + bitstream_put_ue(bs, + context->seq_param.max_num_ref_frames); /* num_ref_frames */ + bitstream_put_ui(bs, 0, 1); /* gaps_in_frame_num_value_allowed_flag */ + + bitstream_put_ue(bs, context->seq_param.picture_width_in_mbs - + 1); /* pic_width_in_mbs_minus1 */ + bitstream_put_ue(bs, context->seq_param.picture_height_in_mbs - + 1); /* pic_height_in_map_units_minus1 */ + bitstream_put_ui(bs, context->seq_param.seq_fields.bits.frame_mbs_only_flag, + 1); /* frame_mbs_only_flag */ + + if (!context->seq_param.seq_fields.bits.frame_mbs_only_flag) { + assert(0); + } + + bitstream_put_ui(bs, + context->seq_param.seq_fields.bits.direct_8x8_inference_flag, + 1); /* direct_8x8_inference_flag */ + bitstream_put_ui(bs, context->seq_param.frame_cropping_flag, + 1); /* frame_cropping_flag */ + + if (context->seq_param.frame_cropping_flag) { + bitstream_put_ue( + bs, + context->seq_param.frame_crop_left_offset); /* frame_crop_left_offset */ + bitstream_put_ue( + bs, context->seq_param + .frame_crop_right_offset); /* frame_crop_right_offset */ + bitstream_put_ue( + bs, + context->seq_param.frame_crop_top_offset); /* frame_crop_top_offset */ + bitstream_put_ue( + bs, context->seq_param + .frame_crop_bottom_offset); /* frame_crop_bottom_offset */ + } + + // if ( frame_bit_rate < 0 ) { //TODO EW: the vui header isn't correct + if (1) { + bitstream_put_ui(bs, 0, 1); /* vui_parameters_present_flag */ + } else { + bitstream_put_ui(bs, 1, 1); /* vui_parameters_present_flag */ + bitstream_put_ui(bs, 0, 1); /* aspect_ratio_info_present_flag */ + bitstream_put_ui(bs, 0, 1); /* overscan_info_present_flag */ + bitstream_put_ui(bs, 0, 1); /* video_signal_type_present_flag */ + bitstream_put_ui(bs, 0, 1); /* chroma_loc_info_present_flag */ + bitstream_put_ui(bs, 1, 1); /* timing_info_present_flag */ + { + bitstream_put_ui(bs, 15, 32); + bitstream_put_ui(bs, 900, 32); + bitstream_put_ui(bs, 1, 1); + } + bitstream_put_ui(bs, 1, 1); /* nal_hrd_parameters_present_flag */ + { + // hrd_parameters + bitstream_put_ue(bs, 0); /* cpb_cnt_minus1 */ + bitstream_put_ui(bs, 4, 4); /* bit_rate_scale */ + bitstream_put_ui(bs, 6, 4); /* cpb_size_scale */ + + bitstream_put_ue( + bs, context->config.bitrate - 1); /* bit_rate_value_minus1[0] */ + bitstream_put_ue( + bs, context->config.bitrate * 8 - 1); /* cpb_size_value_minus1[0] */ + bitstream_put_ui(bs, 1, 1); /* cbr_flag[0] */ + + bitstream_put_ui(bs, 23, 5); /* initial_cpb_removal_delay_length_minus1 */ + bitstream_put_ui(bs, 23, 5); /* cpb_removal_delay_length_minus1 */ + bitstream_put_ui(bs, 23, 5); /* dpb_output_delay_length_minus1 */ + bitstream_put_ui(bs, 23, 5); /* time_offset_length */ + } + bitstream_put_ui(bs, 0, 1); /* vcl_hrd_parameters_present_flag */ + bitstream_put_ui(bs, 0, 1); /* low_delay_hrd_flag */ + + bitstream_put_ui(bs, 0, 1); /* pic_struct_present_flag */ + bitstream_put_ui(bs, 0, 1); /* bitstream_restriction_flag */ + } + + rbsp_trailing_bits(bs); /* rbsp_trailing_bits */ +} + +static void pps_rbsp(VA264Context* context, bitstream* bs) { + bitstream_put_ue( + bs, context->pic_param.pic_parameter_set_id); /* pic_parameter_set_id */ + bitstream_put_ue( + bs, context->pic_param.seq_parameter_set_id); /* seq_parameter_set_id */ + + bitstream_put_ui(bs, + context->pic_param.pic_fields.bits.entropy_coding_mode_flag, + 1); /* entropy_coding_mode_flag */ + + bitstream_put_ui(bs, 0, 1); /* pic_order_present_flag: 0 */ + + bitstream_put_ue(bs, 0); /* num_slice_groups_minus1 */ + + bitstream_put_ue( + bs, context->pic_param + .num_ref_idx_l0_active_minus1); /* num_ref_idx_l0_active_minus1 */ + bitstream_put_ue( + bs, context->pic_param + .num_ref_idx_l1_active_minus1); /* num_ref_idx_l1_active_minus1 + 1 */ + + bitstream_put_ui(bs, context->pic_param.pic_fields.bits.weighted_pred_flag, + 1); /* weighted_pred_flag: 0 */ + bitstream_put_ui(bs, context->pic_param.pic_fields.bits.weighted_bipred_idc, + 2); /* weighted_bipred_idc: 0 */ + + bitstream_put_se( + bs, context->pic_param.pic_init_qp - 26); /* pic_init_qp_minus26 */ + bitstream_put_se(bs, 0); /* pic_init_qs_minus26 */ + bitstream_put_se(bs, 0); /* chroma_qp_index_offset */ + + bitstream_put_ui( + bs, + context->pic_param.pic_fields.bits.deblocking_filter_control_present_flag, + 1); /* deblocking_filter_control_present_flag */ + bitstream_put_ui(bs, 0, 1); /* constrained_intra_pred_flag */ + bitstream_put_ui(bs, 0, 1); /* redundant_pic_cnt_present_flag */ + + /* more_rbsp_data */ + bitstream_put_ui(bs, + context->pic_param.pic_fields.bits.transform_8x8_mode_flag, + 1); /*transform_8x8_mode_flag */ + bitstream_put_ui(bs, 0, 1); /* pic_scaling_matrix_present_flag */ + bitstream_put_se( + bs, context->pic_param + .second_chroma_qp_index_offset); /*second_chroma_qp_index_offset + */ + + rbsp_trailing_bits(bs); +} + +static void slice_header(VA264Context* context, bitstream* bs) { + int first_mb_in_slice = context->slice_param.macroblock_address; + + bitstream_put_ue(bs, first_mb_in_slice); /* first_mb_in_slice: 0 */ + bitstream_put_ue(bs, context->slice_param.slice_type); /* slice_type */ + bitstream_put_ue( + bs, + context->slice_param.pic_parameter_set_id); /* pic_parameter_set_id: 0 */ + bitstream_put_ui( + bs, context->pic_param.frame_num, + context->seq_param.seq_fields.bits.log2_max_frame_num_minus4 + + 4); /* frame_num */ + + if (context->pic_param.pic_fields.bits.idr_pic_flag) + bitstream_put_ue(bs, context->slice_param.idr_pic_id); /* idr_pic_id: 0 */ + + if (context->seq_param.seq_fields.bits.pic_order_cnt_type == 0) { + bitstream_put_ui( + bs, context->pic_param.CurrPic.TopFieldOrderCnt, + context->seq_param.seq_fields.bits.log2_max_pic_order_cnt_lsb_minus4 + + 4); + /* pic_order_present_flag == 0 */ + } + + /* redundant_pic_cnt_present_flag == 0 */ + /* slice type */ + if (IS_P_SLICE(context->slice_param.slice_type)) { + bitstream_put_ui(bs, context->slice_param.num_ref_idx_active_override_flag, + 1); /* num_ref_idx_active_override_flag: */ + + if (context->slice_param.num_ref_idx_active_override_flag) + bitstream_put_ue(bs, context->slice_param.num_ref_idx_l0_active_minus1); + + /* ref_pic_list_reordering */ + bitstream_put_ui(bs, 0, 1); /* ref_pic_list_reordering_flag_l0: 0 */ + } else if (IS_B_SLICE(context->slice_param.slice_type)) { + bitstream_put_ui(bs, context->slice_param.direct_spatial_mv_pred_flag, + 1); /* direct_spatial_mv_pred: 1 */ + + bitstream_put_ui(bs, context->slice_param.num_ref_idx_active_override_flag, + 1); /* num_ref_idx_active_override_flag: */ + + if (context->slice_param.num_ref_idx_active_override_flag) { + bitstream_put_ue(bs, context->slice_param.num_ref_idx_l0_active_minus1); + bitstream_put_ue(bs, context->slice_param.num_ref_idx_l1_active_minus1); + } + + /* ref_pic_list_reordering */ + bitstream_put_ui(bs, 0, 1); /* ref_pic_list_reordering_flag_l0: 0 */ + bitstream_put_ui(bs, 0, 1); /* ref_pic_list_reordering_flag_l1: 0 */ + } + + if ((context->pic_param.pic_fields.bits.weighted_pred_flag && + IS_P_SLICE(context->slice_param.slice_type)) || + ((context->pic_param.pic_fields.bits.weighted_bipred_idc == 1) && + IS_B_SLICE(context->slice_param.slice_type))) { + } + + /* dec_ref_pic_marking */ + if (context->pic_param.pic_fields.bits + .reference_pic_flag) { /* nal_ref_idc != 0 */ + unsigned char no_output_of_prior_pics_flag = 0; + unsigned char long_term_reference_flag = 0; + unsigned char adaptive_ref_pic_marking_mode_flag = 0; + + if (context->pic_param.pic_fields.bits.idr_pic_flag) { + bitstream_put_ui(bs, no_output_of_prior_pics_flag, + 1); /* no_output_of_prior_pics_flag: 0 */ + bitstream_put_ui(bs, long_term_reference_flag, + 1); /* long_term_reference_flag: 0 */ + } else { + bitstream_put_ui(bs, adaptive_ref_pic_marking_mode_flag, + 1); /* adaptive_ref_pic_marking_mode_flag: 0 */ + } + } + + if (context->pic_param.pic_fields.bits.entropy_coding_mode_flag && + !IS_I_SLICE(context->slice_param.slice_type)) + bitstream_put_ue( + bs, context->slice_param.cabac_init_idc); /* cabac_init_idc: 0 */ + + bitstream_put_se(bs, + context->slice_param.slice_qp_delta); /* slice_qp_delta: 0 */ + + /* ignore for SP/SI */ + + if (context->pic_param.pic_fields.bits + .deblocking_filter_control_present_flag) { + bitstream_put_ue( + bs, + context->slice_param + .disable_deblocking_filter_idc); /* disable_deblocking_filter_idc: + 0 */ + + if (context->slice_param.disable_deblocking_filter_idc != 1) { + bitstream_put_se( + bs, + context->slice_param + .slice_alpha_c0_offset_div2); /* slice_alpha_c0_offset_div2: 2 */ + bitstream_put_se( + bs, context->slice_param + .slice_beta_offset_div2); /* slice_beta_offset_div2: 2 */ + } + } + + if (context->pic_param.pic_fields.bits.entropy_coding_mode_flag) { + bitstream_byte_aligning(bs, 1); + } +} + +static int build_packed_pic_buffer(VA264Context* context, + unsigned char** header_buffer) { + bitstream bs; + + bitstream_start(&bs); + nal_start_code_prefix(&bs); + nal_header(&bs, NAL_REF_IDC_HIGH, NAL_PPS); + pps_rbsp(context, &bs); + bitstream_end(&bs); + + *header_buffer = (unsigned char*)bs.buffer; + return bs.bit_offset; +} + +static int build_packed_seq_buffer(VA264Context* context, + unsigned char** header_buffer) { + bitstream bs; + + bitstream_start(&bs); + nal_start_code_prefix(&bs); + nal_header(&bs, NAL_REF_IDC_HIGH, NAL_SPS); + sps_rbsp(context, &bs); + bitstream_end(&bs); + + *header_buffer = (unsigned char*)bs.buffer; + return bs.bit_offset; +} + +static int build_packed_slice_buffer(VA264Context* context, + unsigned char** header_buffer) { + bitstream bs; + int is_idr = !!context->pic_param.pic_fields.bits.idr_pic_flag; + int is_ref = !!context->pic_param.pic_fields.bits.reference_pic_flag; + + bitstream_start(&bs); + nal_start_code_prefix(&bs); + + if (IS_I_SLICE(context->slice_param.slice_type)) { + nal_header(&bs, NAL_REF_IDC_HIGH, is_idr ? NAL_IDR : NAL_NON_IDR); + } else if (IS_P_SLICE(context->slice_param.slice_type)) { + nal_header(&bs, NAL_REF_IDC_MEDIUM, NAL_NON_IDR); + } else { + assert(IS_B_SLICE(context->slice_param.slice_type)); + nal_header(&bs, is_ref ? NAL_REF_IDC_LOW : NAL_REF_IDC_NONE, NAL_NON_IDR); + } + + slice_header(context, &bs); + bitstream_end(&bs); + + *header_buffer = (unsigned char*)bs.buffer; + return bs.bit_offset; +} + +/* + Assume frame sequence is: Frame#0,#1,#2,...,#M,...,#X,... (encoding order) + 1) period between Frame #X and Frame #N = #X - #N + 2) 0 means infinite for intra_period/intra_idr_period, and 0 is invalid for + ip_period 3) intra_idr_period % intra_period (intra_period > 0) and + intra_period % ip_period must be 0 4) intra_period and intra_idr_period take + precedence over ip_period 5) if ip_period > 1, intra_period and + intra_idr_period are not the strict periods of I/IDR frames, see bellow + examples + ------------------------------------------------------------------- + intra_period intra_idr_period ip_period frame sequence + (intra_period/intra_idr_period/ip_period) 0 ignored 1 + IDRPPPPPPP ... (No IDR/I any more) 0 ignored >=2 + IDR(PBB)(PBB)... (No IDR/I any more) 1 0 ignored + IDRIIIIIII... (No IDR any more) 1 1 ignored IDR + IDR IDR IDR... 1 >=2 ignored IDRII IDRII IDR... + (1/3/ignore) + >=2 0 1 IDRPPP IPPP I... (3/0/1) + >=2 0 >=2 IDR(PBB)(PBB)(IBB) (6/0/3) + (PBB)(IBB)(PBB)(IBB)... + >=2 >=2 1 IDRPPPPP IPPPPP IPPPPP (6/18/1) + IDRPPPPP IPPPPP IPPPPP... + >=2 >=2 >=2 {IDR(PBB)(PBB)(IBB)(PBB)(IBB)(PBB)} + (6/18/3) {IDR(PBB)(PBB)(IBB)(PBB)(IBB)(PBB)}... {IDR(PBB)(PBB)(IBB)(PBB)} + (6/12/3) {IDR(PBB)(PBB)(IBB)(PBB)}... {IDR(PBB)(PBB)} (6/6/3) {IDR(PBB)(PBB)}. +*/ + +/* + * Return displaying order with specified periods and encoding order + * displaying_order: displaying order + * frame_type: frame type + */ +#define FRAME_P 0 +#define FRAME_B 1 +#define FRAME_I 2 +#define FRAME_IDR 7 +void encoding2display_order(uint64_t encoding_order, + int intra_period, + int intra_idr_period, + int ip_period, + uint64_t* displaying_order, + int* frame_type) { + int encoding_order_gop = 0; + + if (intra_period == 1) { /* all are I/IDR frames */ + *displaying_order = encoding_order; + if (intra_idr_period == 0) + *frame_type = (encoding_order == 0) ? FRAME_IDR : FRAME_I; + else + *frame_type = + (encoding_order % intra_idr_period == 0) ? FRAME_IDR : FRAME_I; + return; + } + + if (intra_period == 0) + intra_idr_period = 0; + + /* new sequence like + * IDR PPPPP IPPPPP + * IDR (PBB)(PBB)(IBB)(PBB) + */ + encoding_order_gop = + (intra_idr_period == 0) + ? encoding_order + : (encoding_order % (intra_idr_period + ((ip_period == 1) ? 0 : 1))); + + if (encoding_order_gop == 0) { /* the first frame */ + *frame_type = FRAME_IDR; + *displaying_order = encoding_order; + } else if (((encoding_order_gop - 1) % ip_period) != 0) { /* B frames */ + *frame_type = FRAME_B; + *displaying_order = encoding_order - 1; + } else if ((intra_period != 0) && /* have I frames */ + (encoding_order_gop >= 2) && + ((ip_period == 1 && encoding_order_gop % intra_period == + 0) || /* for IDR PPPPP IPPPP */ + /* for IDR (PBB)(PBB)(IBB) */ + (ip_period >= 2 && ((encoding_order_gop - 1) / ip_period % + (intra_period / ip_period)) == 0))) { + *frame_type = FRAME_I; + *displaying_order = encoding_order + ip_period - 1; + } else { + *frame_type = FRAME_P; + *displaying_order = encoding_order + ip_period - 1; + } +} + +std::map fourcc_map = {{VA_FOURCC_NV12, "NV12"}, + {VA_FOURCC_I420, "I420"}, + {VA_FOURCC_YV12, "YV12"}, + {VA_FOURCC_UYVY, "UYVY"}}; + +static std::string fourcc_to_string(int fourcc) { + auto it = fourcc_map.find(fourcc); + if (it != fourcc_map.end()) { + return it->second; + } + RTC_LOG(LS_ERROR) << "Unknow FOURCC"; + return "Unknown"; +} + +std::map rc_mode_map = { + {VA_RC_NONE, "NONE"}, {VA_RC_CBR, "CBR"}, + {VA_RC_VBR, "VBR"}, {VA_RC_VCM, "VCM"}, + {VA_RC_CQP, "CQP"}, {VA_RC_VBR_CONSTRAINED, "VBR_CONSTRAINED"}}; + +static std::string rc_to_string(int rcmode) { + auto it = rc_mode_map.find(rcmode); + if (it != rc_mode_map.end()) { + return it->second; + } + return "Unknown"; +} + +static int init_va(VA264Context* context, VADisplay va_dpy) { + VAProfile profile_list[] = {VAProfileH264High, VAProfileH264Main, + VAProfileH264ConstrainedBaseline}; + VAEntrypoint* entrypoints; + int num_entrypoints, slice_entrypoint; + int support_encode = 0; + int major_ver, minor_ver; + VAStatus va_status; + uint32_t i; + + context->va_dpy = va_dpy; + if (!context->va_dpy) { + return VA_STATUS_ERROR_INVALID_DISPLAY; + } + + va_status = vaInitialize(context->va_dpy, &major_ver, &minor_ver); + + if (major_ver < 0 || minor_ver < 0 || va_status != VA_STATUS_SUCCESS) { + RTC_LOG(LS_ERROR) << "vaInitialize failed"; + return VA_STATUS_ERROR_INVALID_DISPLAY; + } + + num_entrypoints = vaMaxNumEntrypoints(context->va_dpy); + entrypoints = new VAEntrypoint[num_entrypoints * sizeof(*entrypoints)]; + if (!entrypoints) { + RTC_LOG(LS_ERROR) << "failed to allocate VA entrypoints"; + return VA_STATUS_ERROR_INVALID_DISPLAY; + } + + /* use the highest profile */ + for (i = 0; i < sizeof(profile_list) / sizeof(profile_list[0]); i++) { + if ((context->config.h264_profile != ~0) && + context->config.h264_profile != profile_list[i]) + continue; + + context->config.h264_profile = profile_list[i]; + vaQueryConfigEntrypoints(context->va_dpy, context->config.h264_profile, + entrypoints, &num_entrypoints); + for (slice_entrypoint = 0; slice_entrypoint < num_entrypoints; + slice_entrypoint++) { + if (context->requested_entrypoint == -1) { + // Select the entry point based on what is avaiable + if ((entrypoints[slice_entrypoint] == VAEntrypointEncSlice) || + (entrypoints[slice_entrypoint] == VAEntrypointEncSliceLP)) { + support_encode = 1; + context->selected_entrypoint = entrypoints[slice_entrypoint]; + break; + } + } else if ((entrypoints[slice_entrypoint] == + context->requested_entrypoint)) { + // Select the entry point based on what was requested in cmd line option + support_encode = 1; + context->selected_entrypoint = entrypoints[slice_entrypoint]; + break; + } + } + if (support_encode == 1) { + RTC_LOG(LS_INFO) << "Using EntryPoint - " << context->selected_entrypoint; + break; + } + } + + if (support_encode == 0) { + RTC_LOG(LS_ERROR) + << "Can't find VAEntrypointEncSlice or VAEntrypointEncSliceLP for " + "H264 profiles"; + return VA_STATUS_ERROR_UNSUPPORTED_ENTRYPOINT; + } else { + switch (context->config.h264_profile) { + case VAProfileH264ConstrainedBaseline: + RTC_LOG(LS_INFO) << "Use profile VAProfileH264ConstrainedBaseline"; + context->constraint_set_flag |= (1 << 0 | 1 << 1); /* Annex A.2.2 */ + context->config.ip_period = 1; + break; + + case VAProfileH264Main: + RTC_LOG(LS_INFO) << "Use profile VAProfileH264Main"; + context->constraint_set_flag |= (1 << 1); /* Annex A.2.2 */ + break; + + case VAProfileH264High: + context->constraint_set_flag |= (1 << 3); /* Annex A.2.4 */ + RTC_LOG(LS_INFO) << "Use profile VAProfileH264High"; + break; + default: + RTC_LOG(LS_INFO) << "unknow profile. Set to Constrained Baseline"; + context->config.h264_profile = VAProfileH264ConstrainedBaseline; + context->constraint_set_flag |= + (1 << 0 | 1 << 1); /* Annex A.2.1 & A.2.2 */ + context->config.ip_period = 1; + break; + } + } + + /* find out the format for the render target, and rate control mode */ + for (i = 0; i < VAConfigAttribTypeMax; i++) + context->attrib[i].type = (VAConfigAttribType)i; + + va_status = + vaGetConfigAttributes(context->va_dpy, context->config.h264_profile, + (VAEntrypoint)context->selected_entrypoint, + &context->attrib[0], VAConfigAttribTypeMax); + if (va_status != VA_STATUS_SUCCESS) { + RTC_LOG(LS_ERROR) << "vaGetConfigAttributes failed"; + delete[] entrypoints; + return va_status; + } + + /* check the interested configattrib */ + if ((context->attrib[VAConfigAttribRTFormat].value & VA_RT_FORMAT_YUV420) == + 0) { + RTC_LOG(LS_ERROR) << "Not find desired YUV420 RT format"; + return VA_STATUS_ERROR_INVALID_CONFIG; + } else { + context->config_attrib[context->config_attrib_num].type = + VAConfigAttribRTFormat; + context->config_attrib[context->config_attrib_num].value = + VA_RT_FORMAT_YUV420; + context->config_attrib_num++; + } + + if (context->attrib[VAConfigAttribRateControl].value != + VA_ATTRIB_NOT_SUPPORTED) { + int tmp = context->attrib[VAConfigAttribRateControl].value; + + // context->attrib[VAConfigAttribRateControl].value = + // context->config.rc_mode; + + std::string rc_modes; + if (tmp & VA_RC_NONE) + rc_modes += "NONE "; + if (tmp & VA_RC_VBR) + rc_modes += "VBR "; + if (tmp & VA_RC_CBR) + rc_modes += "CBR "; + if (tmp & VA_RC_VCM) + rc_modes += "VCM "; + if (tmp & VA_RC_CQP) + rc_modes += "CQP "; + if (tmp & VA_RC_VBR_CONSTRAINED) + rc_modes += "VBR_CONSTRAINED "; + + RTC_LOG(LS_INFO) << "Support rate control mode: " << rc_modes; + + if (context->config.rc_mode == -1 || !(context->config.rc_mode & tmp)) { + if (context->config.rc_mode != -1) { + RTC_LOG(LS_WARNING) + << "Warning: Don't support the specified RateControl mode: " + << rc_to_string(context->config.rc_mode) << "!!!, switch to "; + } + + for (i = 0; i < sizeof(rc_default_modes) / sizeof(rc_default_modes[0]); + i++) { + if (rc_default_modes[i] & tmp) { + context->config.rc_mode = rc_default_modes[i]; + break; + } + } + + RTC_LOG(LS_INFO) << "RateControl mode: " + << rc_to_string(context->config.rc_mode); + } + + context->config_attrib[context->config_attrib_num].type = + VAConfigAttribRateControl; + context->config_attrib[context->config_attrib_num].value = + context->config.rc_mode; + context->config_attrib_num++; + } + + if (context->attrib[VAConfigAttribEncPackedHeaders].value != + VA_ATTRIB_NOT_SUPPORTED) { + int tmp = context->attrib[VAConfigAttribEncPackedHeaders].value; + + RTC_LOG(LS_INFO) << "Support VAConfigAttribEncPackedHeaders: "; + + context->h264_packedheader = 1; + context->config_attrib[context->config_attrib_num].type = + VAConfigAttribEncPackedHeaders; + context->config_attrib[context->config_attrib_num].value = + VA_ENC_PACKED_HEADER_NONE; + + if (tmp & VA_ENC_PACKED_HEADER_SEQUENCE) { + RTC_LOG(LS_INFO) << "Support packed sequence headers"; + context->config_attrib[context->config_attrib_num].value |= + VA_ENC_PACKED_HEADER_SEQUENCE; + } + + if (tmp & VA_ENC_PACKED_HEADER_PICTURE) { + RTC_LOG(LS_INFO) << "Support packed picture headers"; + context->config_attrib[context->config_attrib_num].value |= + VA_ENC_PACKED_HEADER_PICTURE; + } + + if (tmp & VA_ENC_PACKED_HEADER_SLICE) { + RTC_LOG(LS_INFO) << "Support packed slice headers"; + context->config_attrib[context->config_attrib_num].value |= + VA_ENC_PACKED_HEADER_SLICE; + } + + if (tmp & VA_ENC_PACKED_HEADER_MISC) { + RTC_LOG(LS_INFO) << "Support packed misc headers"; + context->config_attrib[context->config_attrib_num].value |= + VA_ENC_PACKED_HEADER_MISC; + } + + context->enc_packed_header_idx = context->config_attrib_num; + context->config_attrib_num++; + } + + if (context->attrib[VAConfigAttribEncInterlaced].value != + VA_ATTRIB_NOT_SUPPORTED) { + int tmp = context->attrib[VAConfigAttribEncInterlaced].value; + + RTC_LOG(LS_INFO) << "Support VAConfigAttribEncInterlaced: "; + + if (tmp & VA_ENC_INTERLACED_FRAME) + RTC_LOG(LS_INFO) << "Support VA_ENC_INTERLACED_FRAME"; + if (tmp & VA_ENC_INTERLACED_FIELD) + RTC_LOG(LS_INFO) << "Support VA_ENC_INTERLACED_FIELD"; + if (tmp & VA_ENC_INTERLACED_MBAFF) + RTC_LOG(LS_INFO) << "Support VA_ENC_INTERLACED_MBAFF"; + if (tmp & VA_ENC_INTERLACED_PAFF) + RTC_LOG(LS_INFO) << "Support VA_ENC_INTERLACED_PAFF"; + + context->config_attrib[context->config_attrib_num].type = + VAConfigAttribEncInterlaced; + context->config_attrib[context->config_attrib_num].value = + VA_ENC_PACKED_HEADER_NONE; + context->config_attrib_num++; + } + + if (context->attrib[VAConfigAttribEncMaxRefFrames].value != + VA_ATTRIB_NOT_SUPPORTED) { + context->h264_maxref = context->attrib[VAConfigAttribEncMaxRefFrames].value; + + RTC_LOG(LS_INFO) << "Support " << (context->h264_maxref & 0xffff) + << " RefPicList0 and " + << ((context->h264_maxref >> 16) & 0xffff) + << " RefPicList1"; + } + + if (context->attrib[VAConfigAttribEncMaxSlices].value != + VA_ATTRIB_NOT_SUPPORTED) + + RTC_LOG(LS_INFO) << "Support " + << context->attrib[VAConfigAttribEncMaxSlices].value + << " slices"; + + if (context->attrib[VAConfigAttribEncSliceStructure].value != + VA_ATTRIB_NOT_SUPPORTED) { + int tmp = context->attrib[VAConfigAttribEncSliceStructure].value; + + RTC_LOG(LS_INFO) << "Support VAConfigAttribEncSliceStructure: "; + + RTC_LOG(LS_INFO) << "Support VAConfigAttribEncSliceStructure"; + + if (tmp & VA_ENC_SLICE_STRUCTURE_ARBITRARY_ROWS) + RTC_LOG(LS_INFO) << "Support VA_ENC_SLICE_STRUCTURE_ARBITRARY_ROWS"; + if (tmp & VA_ENC_SLICE_STRUCTURE_POWER_OF_TWO_ROWS) + RTC_LOG(LS_INFO) << "Support VA_ENC_SLICE_STRUCTURE_POWER_OF_TWO_ROWS"; + if (tmp & VA_ENC_SLICE_STRUCTURE_ARBITRARY_MACROBLOCKS) + RTC_LOG(LS_INFO) + << "Support VA_ENC_SLICE_STRUCTURE_ARBITRARY_MACROBLOCKS"; + } + if (context->attrib[VAConfigAttribEncMacroblockInfo].value != + VA_ATTRIB_NOT_SUPPORTED) { + RTC_LOG(LS_INFO) << "Support VAConfigAttribEncMacroblockInfo"; + } + + delete[] entrypoints; + + return 0; +} + +static int setup_encode(VA264Context* context) { + VAStatus va_status; + VASurfaceID* tmp_surfaceid; + int codedbuf_size, i; + + va_status = vaCreateConfig(context->va_dpy, context->config.h264_profile, + (VAEntrypoint)context->selected_entrypoint, + &context->config_attrib[0], + context->config_attrib_num, &context->config_id); + + if (context->config_id == VA_INVALID_ID) { + RTC_LOG(LS_ERROR) << "vaCreateConfig failed va_status = " << va_status; + return -1; + } + + /* create source surfaces */ + va_status = vaCreateSurfaces(context->va_dpy, VA_RT_FORMAT_YUV420, + context->frame_width_mbaligned, + context->frame_height_mbaligned, + &context->src_surface[0], SURFACE_NUM, NULL, 0); + if (va_status != VA_STATUS_SUCCESS) { + RTC_LOG(LS_ERROR) << "vaCreateSurfaces failed va_status = " << va_status; + return -1; + } + + /* create reference surfaces */ + va_status = vaCreateSurfaces(context->va_dpy, VA_RT_FORMAT_YUV420, + context->frame_width_mbaligned, + context->frame_height_mbaligned, + &context->ref_surface[0], SURFACE_NUM, NULL, 0); + if (va_status != VA_STATUS_SUCCESS) { + RTC_LOG(LS_ERROR) << "vaCreateSurfaces failed va_status = " << va_status; + return -1; + } + + tmp_surfaceid = new VASurfaceID[2 * SURFACE_NUM]; + assert(tmp_surfaceid); + memcpy(tmp_surfaceid, context->src_surface, + SURFACE_NUM * sizeof(VASurfaceID)); + memcpy(tmp_surfaceid + SURFACE_NUM, context->ref_surface, + SURFACE_NUM * sizeof(VASurfaceID)); + + /* Create a context for this encode pipe */ + va_status = vaCreateContext( + context->va_dpy, context->config_id, context->frame_width_mbaligned, + context->frame_height_mbaligned, VA_PROGRESSIVE, tmp_surfaceid, + 2 * SURFACE_NUM, &context->context_id); + + if (va_status != VA_STATUS_SUCCESS) { + RTC_LOG(LS_ERROR) << "vaCreateContext failed va_status = " << va_status; + delete[] tmp_surfaceid; + return -1; + } + + delete[] tmp_surfaceid; + + codedbuf_size = + (context->frame_width_mbaligned * context->frame_height_mbaligned * 400) / + (16 * 16); + + for (i = 0; i < SURFACE_NUM; i++) { + /* create coded buffer once for all + * other VA buffers which won't be used again after vaRenderPicture. + * so APP can always vaCreateBuffer for every frame + * but coded buffer need to be mapped and accessed after + * vaRenderPicture/vaEndPicture so VA won't maintain the coded buffer + */ + va_status = vaCreateBuffer(context->va_dpy, context->context_id, + VAEncCodedBufferType, codedbuf_size, 1, NULL, + &context->coded_buf[i]); + if (va_status != VA_STATUS_SUCCESS) { + RTC_LOG(LS_ERROR) << "vaCreateBuffer failed va_status = " << va_status; + return -1; + } + } + + return 0; +} + +#define partition(ref, field, key, ascending) \ + while (i <= j) { \ + if (ascending) { \ + while (ref[i].field < key) \ + i++; \ + while (ref[j].field > key) \ + j--; \ + } else { \ + while (ref[i].field > key) \ + i++; \ + while (ref[j].field < key) \ + j--; \ + } \ + if (i <= j) { \ + tmp = ref[i]; \ + ref[i] = ref[j]; \ + ref[j] = tmp; \ + i++; \ + j--; \ + } \ + } + +static void sort_one(VAPictureH264 ref[], + int left, + int right, + int ascending, + int frame_idx) { + int i = left, j = right; + uint32_t key; + VAPictureH264 tmp; + + if (frame_idx) { + key = ref[(left + right) / 2].frame_idx; + partition(ref, frame_idx, key, ascending); + } else { + key = ref[(left + right) / 2].TopFieldOrderCnt; + partition(ref, TopFieldOrderCnt, (signed int)key, ascending); + } + + /* recursion */ + if (left < j) + sort_one(ref, left, j, ascending, frame_idx); + + if (i < right) + sort_one(ref, i, right, ascending, frame_idx); +} + +static void sort_two(VAPictureH264 ref[], + int left, + int right, + uint32_t key, + uint32_t frame_idx, + int partition_ascending, + int list0_ascending, + int list1_ascending) { + int i = left, j = right; + VAPictureH264 tmp; + + if (frame_idx) { + partition(ref, frame_idx, key, partition_ascending); + } else { + partition(ref, TopFieldOrderCnt, (signed int)key, partition_ascending); + } + + sort_one(ref, left, i - 1, list0_ascending, frame_idx); + sort_one(ref, j + 1, right, list1_ascending, frame_idx); +} + +static int update_ReferenceFrames(VA264Context* context) { + int i; + + if (context->current_frame_type == FRAME_B) + return 0; + + context->current_curr_pic.flags = VA_PICTURE_H264_SHORT_TERM_REFERENCE; + context->num_short_term++; + if (context->num_short_term > num_ref_frames) + context->num_short_term = num_ref_frames; + for (i = context->num_short_term - 1; i > 0; i--) + context->reference_frames[i] = context->reference_frames[i - 1]; + context->reference_frames[0] = context->current_curr_pic; + + if (context->current_frame_type != FRAME_B) + context->current_frame_num++; + if (context->current_frame_num > MaxFrameNum) + context->current_frame_num = 0; + + return 0; +} + +static int update_RefPicList(VA264Context* context) { + uint32_t current_poc = context->current_curr_pic.TopFieldOrderCnt; + + if (context->current_frame_type == FRAME_IDR) { + // per issue 1189 in Intel Media Driver: + // https://github.com/intel/media-driver/issues/1189 + // For the start of each IDR, reset ALL the reference pic lists to invalid + uint32_t flags = VA_PICTURE_H264_INVALID; + for (int i = 0; i < SURFACE_NUM * 2; i++) { + context->slice_param.RefPicList0[i].flags = flags; + context->slice_param.RefPicList1[i].flags = flags; + context->ref_pic_list0_p[i].flags = flags; + context->ref_pic_list0_b[i].flags = flags; + context->ref_pic_list1_b[i].flags = flags; + context->slice_param.RefPicList1[i].picture_id = VA_INVALID_SURFACE; + context->slice_param.RefPicList0[i].picture_id = VA_INVALID_SURFACE; + context->ref_pic_list0_p[i].picture_id = VA_INVALID_SURFACE; + context->ref_pic_list0_b[i].picture_id = VA_INVALID_SURFACE; + context->ref_pic_list1_b[i].picture_id = VA_INVALID_SURFACE; + } + + for (int i = 0; i < SURFACE_NUM; i++) { + context->reference_frames[i].picture_id = VA_INVALID_SURFACE; + context->reference_frames[i].flags = flags; + } + } + + if (context->current_frame_type == FRAME_P) { + memcpy(context->ref_pic_list0_p, context->reference_frames, + context->num_short_term * sizeof(VAPictureH264)); + sort_one(context->ref_pic_list0_p, 0, context->num_short_term - 1, 0, 1); + } + + if (context->current_frame_type == FRAME_B) { + memcpy(context->ref_pic_list0_b, context->reference_frames, + context->num_short_term * sizeof(VAPictureH264)); + sort_two(context->ref_pic_list0_b, 0, context->num_short_term - 1, + current_poc, 0, 1, 0, 1); + + memcpy(context->ref_pic_list1_b, context->reference_frames, + context->num_short_term * sizeof(VAPictureH264)); + sort_two(context->ref_pic_list1_b, 0, context->num_short_term - 1, + current_poc, 0, 0, 1, 0); + } + + return 0; +} + +template +VAEncMiscParam& AllocateMiscParameterBuffer( + std::vector& misc_buffer, + VAEncMiscParameterType misc_param_type) { + constexpr size_t buffer_size = + sizeof(VAEncMiscParameterBuffer) + sizeof(VAEncMiscParam); + misc_buffer.resize(buffer_size); + auto* va_buffer = + reinterpret_cast(misc_buffer.data()); + va_buffer->type = misc_param_type; + return *reinterpret_cast(va_buffer->data); +} + +void CreateVAEncRateControlParams(uint32_t bps, + uint32_t target_percentage, + uint32_t window_size, + uint32_t initial_qp, + uint32_t min_qp, + uint32_t max_qp, + uint32_t framerate, + uint32_t buffer_size, + std::vector misc_buffers[3]) { + auto& rate_control_param = + AllocateMiscParameterBuffer( + misc_buffers[0], VAEncMiscParameterTypeRateControl); + rate_control_param.bits_per_second = bps; + rate_control_param.target_percentage = target_percentage; + rate_control_param.window_size = window_size; + rate_control_param.initial_qp = initial_qp; + rate_control_param.min_qp = min_qp; + rate_control_param.max_qp = max_qp; + rate_control_param.rc_flags.bits.disable_frame_skip = true; + + auto& framerate_param = + AllocateMiscParameterBuffer( + misc_buffers[1], VAEncMiscParameterTypeFrameRate); + framerate_param.framerate = framerate; + + auto& hrd_param = AllocateMiscParameterBuffer( + misc_buffers[2], VAEncMiscParameterTypeHRD); + hrd_param.buffer_size = buffer_size; + hrd_param.initial_buffer_fullness = buffer_size / 2; +} + +static int render_sequence(VA264Context* context) { + VABufferID seq_param_buf, rc_param_buf, misc_param_tmpbuf, render_id[2]; + VAStatus va_status; + VAEncMiscParameterBuffer *misc_param, *misc_param_tmp; + VAEncMiscParameterRateControl* misc_rate_ctrl; + + context->seq_param.level_idc = 41 /*SH_LEVEL_3*/; + context->seq_param.picture_width_in_mbs = context->frame_width_mbaligned / 16; + context->seq_param.picture_height_in_mbs = + context->frame_height_mbaligned / 16; + context->seq_param.bits_per_second = context->config.bitrate; + + context->seq_param.intra_period = context->config.intra_period; + context->seq_param.intra_idr_period = context->config.intra_idr_period; + context->seq_param.ip_period = context->config.ip_period; + + context->seq_param.max_num_ref_frames = num_ref_frames; + context->seq_param.seq_fields.bits.frame_mbs_only_flag = 1; + context->seq_param.time_scale = 900; + context->seq_param.num_units_in_tick = + 15; /* Tc = num_units_in_tick / time_sacle */ + context->seq_param.seq_fields.bits.log2_max_pic_order_cnt_lsb_minus4 = + Log2MaxPicOrderCntLsb - 4; + context->seq_param.seq_fields.bits.log2_max_frame_num_minus4 = + Log2MaxFrameNum - 4; + context->seq_param.seq_fields.bits.frame_mbs_only_flag = 1; + context->seq_param.seq_fields.bits.chroma_format_idc = 1; + context->seq_param.seq_fields.bits.direct_8x8_inference_flag = 1; + + if (context->config.frame_width != context->frame_width_mbaligned || + context->config.frame_height != context->frame_height_mbaligned) { + context->seq_param.frame_cropping_flag = 1; + context->seq_param.frame_crop_left_offset = 0; + context->seq_param.frame_crop_right_offset = + (context->frame_width_mbaligned - context->config.frame_width) / 2; + context->seq_param.frame_crop_top_offset = 0; + context->seq_param.frame_crop_bottom_offset = + (context->frame_height_mbaligned - context->config.frame_height) / 2; + } + + va_status = vaCreateBuffer( + context->va_dpy, context->context_id, VAEncSequenceParameterBufferType, + sizeof(context->seq_param), 1, &context->seq_param, &seq_param_buf); + + if (va_status != VA_STATUS_SUCCESS) { + RTC_LOG(LS_ERROR) << "vaCreateBuffer failed va_status = " << va_status; + return -1; + } + + va_status = vaCreateBuffer( + context->va_dpy, context->context_id, VAEncMiscParameterBufferType, + sizeof(VAEncMiscParameterBuffer) + sizeof(VAEncMiscParameterRateControl), + 1, NULL, &rc_param_buf); + if (va_status != VA_STATUS_SUCCESS) { + RTC_LOG(LS_ERROR) << "vaCreateBuffer failed va_status = " << va_status; + return -1; + } + + vaMapBuffer(context->va_dpy, rc_param_buf, (void**)&misc_param); + misc_param->type = VAEncMiscParameterTypeRateControl; + misc_rate_ctrl = (VAEncMiscParameterRateControl*)misc_param->data; + memset(misc_rate_ctrl, 0, sizeof(*misc_rate_ctrl)); + misc_rate_ctrl->bits_per_second = context->config.bitrate; + misc_rate_ctrl->target_percentage = 66; + misc_rate_ctrl->window_size = 1000; + misc_rate_ctrl->initial_qp = context->config.initial_qp; + misc_rate_ctrl->min_qp = context->config.minimal_qp; + misc_rate_ctrl->basic_unit_size = 0; + vaUnmapBuffer(context->va_dpy, rc_param_buf); + + render_id[0] = seq_param_buf; + render_id[1] = rc_param_buf; + + va_status = + vaRenderPicture(context->va_dpy, context->context_id, &render_id[0], 2); + + if (va_status != VA_STATUS_SUCCESS) { + RTC_LOG(LS_ERROR) << "vaRenderPicture failed va_status = " << va_status; + return -1; + } + + return 0; +} + +std::map frame_type_map = {{FRAME_P, "P"}, + {FRAME_B, "B"}, + {FRAME_I, "I"}, + {FRAME_IDR, "IDR"}}; + +static std::string frametype_to_string(int ftype) { + auto it = frame_type_map.find(ftype); + if (it != frame_type_map.end()) { + return it->second; + } + return "Unknown"; +} + +static int calc_poc(VA264Context* context, int pic_order_cnt_lsb) { + static int PicOrderCntMsb_ref = 0, pic_order_cnt_lsb_ref = 0; + int prevPicOrderCntMsb, prevPicOrderCntLsb; + int PicOrderCntMsb, TopFieldOrderCnt; + + if (context->current_frame_type == FRAME_IDR) + prevPicOrderCntMsb = prevPicOrderCntLsb = 0; + else { + prevPicOrderCntMsb = PicOrderCntMsb_ref; + prevPicOrderCntLsb = pic_order_cnt_lsb_ref; + } + + if ((pic_order_cnt_lsb < prevPicOrderCntLsb) && + ((prevPicOrderCntLsb - pic_order_cnt_lsb) >= + (int)(MaxPicOrderCntLsb / 2))) + PicOrderCntMsb = prevPicOrderCntMsb + MaxPicOrderCntLsb; + else if ((pic_order_cnt_lsb > prevPicOrderCntLsb) && + ((pic_order_cnt_lsb - prevPicOrderCntLsb) > + (int)(MaxPicOrderCntLsb / 2))) + PicOrderCntMsb = prevPicOrderCntMsb - MaxPicOrderCntLsb; + else + PicOrderCntMsb = prevPicOrderCntMsb; + + TopFieldOrderCnt = PicOrderCntMsb + pic_order_cnt_lsb; + + if (context->current_frame_type != FRAME_B) { + PicOrderCntMsb_ref = PicOrderCntMsb; + pic_order_cnt_lsb_ref = pic_order_cnt_lsb; + } + + return TopFieldOrderCnt; +} + +static int render_picture(VA264Context* context) { + VABufferID pic_param_buf; + VAStatus va_status; + int i = 0; + + context->pic_param.CurrPic.picture_id = + context->ref_surface[(context->current_frame_display % SURFACE_NUM)]; + context->pic_param.CurrPic.frame_idx = context->current_frame_num; + context->pic_param.CurrPic.flags = 0; + context->pic_param.CurrPic.TopFieldOrderCnt = calc_poc( + context, (context->current_frame_display - context->current_idr_display) % + MaxPicOrderCntLsb); + context->pic_param.CurrPic.BottomFieldOrderCnt = + context->pic_param.CurrPic.TopFieldOrderCnt; + context->current_curr_pic = context->pic_param.CurrPic; + + memcpy(context->pic_param.ReferenceFrames, context->reference_frames, + context->num_short_term * sizeof(VAPictureH264)); + for (i = context->num_short_term; i < SURFACE_NUM; i++) { + context->pic_param.ReferenceFrames[i].picture_id = VA_INVALID_SURFACE; + context->pic_param.ReferenceFrames[i].flags = VA_PICTURE_H264_INVALID; + } + + context->pic_param.pic_fields.bits.idr_pic_flag = + (context->current_frame_type == FRAME_IDR); + context->pic_param.pic_fields.bits.reference_pic_flag = + (context->current_frame_type != FRAME_B); + context->pic_param.pic_fields.bits.entropy_coding_mode_flag = + context->config.h264_entropy_mode; + context->pic_param.pic_fields.bits.deblocking_filter_control_present_flag = 1; + context->pic_param.frame_num = context->current_frame_num; + context->pic_param.coded_buf = + context->coded_buf[(context->current_frame_display % SURFACE_NUM)]; + context->pic_param.last_picture = + 0; // (context->current_frame_encoding == frame_count); + context->pic_param.pic_init_qp = context->config.initial_qp; + + va_status = vaCreateBuffer( + context->va_dpy, context->context_id, VAEncPictureParameterBufferType, + sizeof(context->pic_param), 1, &context->pic_param, &pic_param_buf); + + if (va_status != VA_STATUS_SUCCESS) { + RTC_LOG(LS_ERROR) << "vaCreateBuffer failed va_status = " << va_status; + return -1; + } + + va_status = + vaRenderPicture(context->va_dpy, context->context_id, &pic_param_buf, 1); + + if (va_status != VA_STATUS_SUCCESS) { + RTC_LOG(LS_ERROR) << "vaRenderPicture failed va_status = " << va_status; + return -1; + } + + return 0; +} + +static int render_packedsequence(VA264Context* context) { + VAEncPackedHeaderParameterBuffer packedheader_param_buffer; + VABufferID packedseq_para_bufid, packedseq_data_bufid, render_id[2]; + uint32_t length_in_bits; + unsigned char* packedseq_buffer = NULL; + VAStatus va_status; + + length_in_bits = build_packed_seq_buffer(context, &packedseq_buffer); + + packedheader_param_buffer.type = VAEncPackedHeaderSequence; + + packedheader_param_buffer.bit_length = length_in_bits; /*length_in_bits*/ + packedheader_param_buffer.has_emulation_bytes = 0; + va_status = vaCreateBuffer(context->va_dpy, context->context_id, + VAEncPackedHeaderParameterBufferType, + sizeof(packedheader_param_buffer), 1, + &packedheader_param_buffer, &packedseq_para_bufid); + + if (va_status != VA_STATUS_SUCCESS) { + RTC_LOG(LS_ERROR) << "vaCreateBuffer failed va_status = " << va_status; + return -1; + } + + va_status = vaCreateBuffer( + context->va_dpy, context->context_id, VAEncPackedHeaderDataBufferType, + (length_in_bits + 7) / 8, 1, packedseq_buffer, &packedseq_data_bufid); + + if (va_status != VA_STATUS_SUCCESS) { + RTC_LOG(LS_ERROR) << "vaCreateBuffer failed va_status = " << va_status; + return -1; + } + + render_id[0] = packedseq_para_bufid; + render_id[1] = packedseq_data_bufid; + va_status = + vaRenderPicture(context->va_dpy, context->context_id, render_id, 2); + + if (va_status != VA_STATUS_SUCCESS) { + RTC_LOG(LS_ERROR) << "vaRenderPicture failed va_status = " << va_status; + return -1; + } + + free(packedseq_buffer); + + return 0; +} + +static int render_packedpicture(VA264Context* context) { + VAEncPackedHeaderParameterBuffer packedheader_param_buffer; + VABufferID packedpic_para_bufid, packedpic_data_bufid, render_id[2]; + uint32_t length_in_bits; + unsigned char* packedpic_buffer = NULL; + VAStatus va_status; + + length_in_bits = build_packed_pic_buffer(context, &packedpic_buffer); + packedheader_param_buffer.type = VAEncPackedHeaderPicture; + packedheader_param_buffer.bit_length = length_in_bits; + packedheader_param_buffer.has_emulation_bytes = 0; + + va_status = vaCreateBuffer(context->va_dpy, context->context_id, + VAEncPackedHeaderParameterBufferType, + sizeof(packedheader_param_buffer), 1, + &packedheader_param_buffer, &packedpic_para_bufid); + + if (va_status != VA_STATUS_SUCCESS) { + RTC_LOG(LS_ERROR) << "vaCreateBuffer failed va_status = " << va_status; + return -1; + } + + va_status = vaCreateBuffer( + context->va_dpy, context->context_id, VAEncPackedHeaderDataBufferType, + (length_in_bits + 7) / 8, 1, packedpic_buffer, &packedpic_data_bufid); + + if (va_status != VA_STATUS_SUCCESS) { + RTC_LOG(LS_ERROR) << "vaCreateBuffer failed va_status = " << va_status; + return -1; + } + render_id[0] = packedpic_para_bufid; + render_id[1] = packedpic_data_bufid; + va_status = + vaRenderPicture(context->va_dpy, context->context_id, render_id, 2); + + if (va_status != VA_STATUS_SUCCESS) { + RTC_LOG(LS_ERROR) << "vaRenderPicture failed va_status = " << va_status; + return -1; + } + free(packedpic_buffer); + + return 0; +} + +static void render_packedslice(VA264Context* context) { + VAEncPackedHeaderParameterBuffer packedheader_param_buffer; + VABufferID packedslice_para_bufid, packedslice_data_bufid, render_id[2]; + uint32_t length_in_bits; + unsigned char* packedslice_buffer = NULL; + VAStatus va_status; + + length_in_bits = build_packed_slice_buffer(context, &packedslice_buffer); + packedheader_param_buffer.type = VAEncPackedHeaderSlice; + packedheader_param_buffer.bit_length = length_in_bits; + packedheader_param_buffer.has_emulation_bytes = 0; + + va_status = vaCreateBuffer( + context->va_dpy, context->context_id, + VAEncPackedHeaderParameterBufferType, sizeof(packedheader_param_buffer), + 1, &packedheader_param_buffer, &packedslice_para_bufid); + + if (va_status != VA_STATUS_SUCCESS) { + RTC_LOG(LS_ERROR) << "vaCreateBuffer failed va_status = " << va_status; + return; + } + + va_status = vaCreateBuffer( + context->va_dpy, context->context_id, VAEncPackedHeaderDataBufferType, + (length_in_bits + 7) / 8, 1, packedslice_buffer, &packedslice_data_bufid); + + if (va_status != VA_STATUS_SUCCESS) { + RTC_LOG(LS_ERROR) << "vaCreateBuffer failed va_status = " << va_status; + return; + } + render_id[0] = packedslice_para_bufid; + render_id[1] = packedslice_data_bufid; + va_status = + vaRenderPicture(context->va_dpy, context->context_id, render_id, 2); + + if (va_status != VA_STATUS_SUCCESS) { + RTC_LOG(LS_ERROR) << "vaRenderPicture failed va_status = " << va_status; + return; + } + free(packedslice_buffer); +} + +static int render_slice(VA264Context* context) { + VABufferID slice_param_buf; + VAStatus va_status; + int i; + + update_RefPicList(context); + + /* one frame, one slice */ + context->slice_param.macroblock_address = 0; + context->slice_param.num_macroblocks = context->frame_width_mbaligned * + context->frame_height_mbaligned / + (16 * 16); /* Measured by MB */ + context->slice_param.slice_type = (context->current_frame_type == FRAME_IDR) + ? 2 + : context->current_frame_type; + if (context->current_frame_type == FRAME_IDR) { + if (context->current_frame_encoding != 0) + ++context->slice_param.idr_pic_id; + } else if (context->current_frame_type == FRAME_P) { + int refpiclist0_max = context->h264_maxref & 0xffff; + memcpy(context->slice_param.RefPicList0, context->ref_pic_list0_p, + ((refpiclist0_max > 32) ? 32 : refpiclist0_max) * + sizeof(VAPictureH264)); + + for (i = refpiclist0_max; i < 32; i++) { + context->slice_param.RefPicList0[i].picture_id = VA_INVALID_SURFACE; + context->slice_param.RefPicList0[i].flags = VA_PICTURE_H264_INVALID; + } + } else if (context->current_frame_type == FRAME_B) { + int refpiclist0_max = context->h264_maxref & 0xffff; + int refpiclist1_max = (context->h264_maxref >> 16) & 0xffff; + + memcpy(context->slice_param.RefPicList0, context->ref_pic_list0_b, + ((refpiclist0_max > 32) ? 32 : refpiclist0_max) * + sizeof(VAPictureH264)); + for (i = refpiclist0_max; i < 32; i++) { + context->slice_param.RefPicList0[i].picture_id = VA_INVALID_SURFACE; + context->slice_param.RefPicList0[i].flags = VA_PICTURE_H264_INVALID; + } + + memcpy(context->slice_param.RefPicList1, context->ref_pic_list1_b, + ((refpiclist1_max > 32) ? 32 : refpiclist1_max) * + sizeof(VAPictureH264)); + for (i = refpiclist1_max; i < 32; i++) { + context->slice_param.RefPicList1[i].picture_id = VA_INVALID_SURFACE; + context->slice_param.RefPicList1[i].flags = VA_PICTURE_H264_INVALID; + } + } + + context->slice_param.slice_alpha_c0_offset_div2 = 0; + context->slice_param.slice_beta_offset_div2 = 0; + context->slice_param.direct_spatial_mv_pred_flag = 1; + context->slice_param.pic_order_cnt_lsb = + (context->current_frame_display - context->current_idr_display) % + MaxPicOrderCntLsb; + + if (context->h264_packedheader && + context->config_attrib[context->enc_packed_header_idx].value & + VA_ENC_PACKED_HEADER_SLICE) + render_packedslice(context); + + va_status = vaCreateBuffer( + context->va_dpy, context->context_id, VAEncSliceParameterBufferType, + sizeof(context->slice_param), 1, &context->slice_param, &slice_param_buf); + + if (va_status != VA_STATUS_SUCCESS) { + RTC_LOG(LS_ERROR) << "vaCreateBuffer failed va_status = " << va_status; + return -1; + } + + va_status = vaRenderPicture(context->va_dpy, context->context_id, + &slice_param_buf, 1); + + if (va_status != VA_STATUS_SUCCESS) { + RTC_LOG(LS_ERROR) << "vaRenderPicture failed va_status = " << va_status; + return -1; + } + + return 0; +} + +namespace livekit { + +VaapiH264EncoderWrapper::VaapiH264EncoderWrapper() + : va_display_(std::make_unique()) { + context_ = std::make_unique(); + memset((void*)context_.get(), 0, sizeof(VA264Context)); +} + +VaapiH264EncoderWrapper::~VaapiH264EncoderWrapper() {} + +void VaapiH264EncoderWrapper::Destroy() { + if (context_->va_dpy) { + vaDestroySurfaces(context_->va_dpy, &context_->src_surface[0], SURFACE_NUM); + vaDestroySurfaces(context_->va_dpy, &context_->ref_surface[0], SURFACE_NUM); + } + + if (context_->encoded_buffer) { + free(context_->encoded_buffer); + context_->encoded_buffer = nullptr; + } + + for (int i = 0; i < SURFACE_NUM; i++) { + vaDestroyBuffer(context_->va_dpy, context_->coded_buf[i]); + } + + vaDestroyContext(context_->va_dpy, context_->context_id); + vaDestroyConfig(context_->va_dpy, context_->config_id); + + if (va_display_->isOpen()) { + vaTerminate(va_display_->display()); + va_display_->Close(); + } + + context_->va_dpy = nullptr; + context_->context_id = VA_INVALID_ID; + memset((void*)context_.get(), 0, sizeof(VA264Context)); + initialized_ = false; +} + +bool VaapiH264EncoderWrapper::Initialize(int width, + int height, + int bitrate, + int intra_period, + int idr_period, + int ip_period, + int frame_rate, + VAProfile profile, + int rc_mode) { + context_->config.h264_entropy_mode = 1; // cabac + context_->config.frame_width = width; + context_->config.frame_height = height; + context_->config.frame_rate = frame_rate; + context_->config.bitrate = bitrate; + context_->config.initial_qp = 26; + context_->config.minimal_qp = 0; + context_->config.intra_period = intra_period; + context_->config.intra_idr_period = idr_period; + context_->config.ip_period = ip_period; + context_->config.rc_mode = rc_mode; + context_->h264_maxref = (1 << 16 | 1); + context_->requested_entrypoint = context_->selected_entrypoint = -1; + + if (context_->config.ip_period < 1) { + RTC_LOG(LS_WARNING) << "ip_period must be greater than 0"; + return false; + } + if (context_->config.intra_period != 1 && + context_->config.intra_period % context_->config.ip_period != 0) { + RTC_LOG(LS_WARNING) << "intra_period must be a multiplier of ip_period"; + return false; + } + if (context_->config.intra_period != 0 && + context_->config.intra_idr_period % context_->config.intra_period != 0) { + RTC_LOG(LS_WARNING) + << "intra_idr_period must be a multiplier of intra_period"; + return false; + } + + if (context_->config.bitrate == 0) { + context_->config.bitrate = context_->config.frame_width * + context_->config.frame_height * 12 * + context_->config.frame_rate / 50; + } + + context_->config.h264_profile = profile; + + context_->frame_width_mbaligned = (context_->config.frame_width + 15) & (~15); + context_->frame_height_mbaligned = + (context_->config.frame_height + 15) & (~15); + if (context_->config.frame_width != context_->frame_width_mbaligned || + context_->config.frame_height != context_->frame_height_mbaligned) { + RTC_LOG(LS_INFO) << "Source frame is " << context_->config.frame_width + << "x" << context_->config.frame_height + << " and will code clip to " + << context_->frame_width_mbaligned << "x" + << context_->frame_height_mbaligned << " with crop"; + } + + // the buffer to receive the encoded frames from encodeImage + context_->encoded_buffer = (uint8_t*)malloc( + context_->frame_width_mbaligned * context_->frame_height_mbaligned * 3); + + if (!va_display_->isOpen()) { + if (!va_display_->Open()) { + free(context_->encoded_buffer); + context_->encoded_buffer = nullptr; + return false; + } + } + + if (init_va(context_.get(), va_display_->display()) != VA_STATUS_SUCCESS) { + free(context_->encoded_buffer); + context_->encoded_buffer = nullptr; + return false; + } + + if (setup_encode(context_.get()) != VA_STATUS_SUCCESS) { + free(context_->encoded_buffer); + context_->encoded_buffer = nullptr; + return false; + } + + // reset sps/pps/slice params + memset(&context_->seq_param, 0, sizeof(context_->seq_param)); + memset(&context_->pic_param, 0, sizeof(context_->pic_param)); + memset(&context_->slice_param, 0, sizeof(context_->slice_param)); + + initialized_ = true; + return true; +} + +bool VaapiH264EncoderWrapper::Encode(int fourcc, + const uint8_t* y, + const uint8_t* u, + const uint8_t* v, + bool forceIDR, + std::vector& encoded) { + if (forceIDR) { + // reset the sequence to start with a new IDR regardless of layout + context_->current_frame_num = context_->current_frame_display = + context_->current_frame_encoding = 0; + } + + uint8_t* output = context_->encoded_buffer; + VASurfaceID surface = + context_->src_surface[context_->current_frame_encoding % SURFACE_NUM]; + int retv = upload_surface_yuv( + context_->va_dpy, surface, fourcc, context_->config.frame_width, + context_->config.frame_height, (uint8_t*)y, (uint8_t*)u, (uint8_t*)v); + + if (retv != 0) { + RTC_LOG(LS_ERROR) << "Failed to upload surface"; + return false; + } + + encoding2display_order( + context_->current_frame_encoding, context_->config.intra_period, + context_->config.intra_idr_period, context_->config.ip_period, + &context_->current_frame_display, &context_->current_frame_type); + + if (context_->current_frame_type == FRAME_IDR) { + context_->num_short_term = 0; + context_->current_frame_num = 0; + context_->current_idr_display = context_->current_frame_display; + } + + VAStatus va_status = vaBeginPicture( + context_->va_dpy, context_->context_id, + context_->src_surface[(context_->current_frame_display % SURFACE_NUM)]); + + if (va_status != VA_STATUS_SUCCESS) { + RTC_LOG(LS_ERROR) << "vaBeginPicture failed va_status = " << va_status; + return false; + } + + // render sequence and picture parameters + if (context_->current_frame_type == FRAME_IDR) { + render_sequence(context_.get()); + render_picture(context_.get()); + if (context_->h264_packedheader) { + render_packedsequence(context_.get()); + render_packedpicture(context_.get()); + } + } else { + render_picture(context_.get()); + } + render_slice(context_.get()); + + va_status = vaEndPicture(context_->va_dpy, context_->context_id); + + if (va_status != VA_STATUS_SUCCESS) { + RTC_LOG(LS_ERROR) << "vaEndPicture failed va_status = " << va_status; + return false; + } + va_status = vaSyncSurface( + context_->va_dpy, + context_->src_surface[context_->current_frame_display % SURFACE_NUM]); + + if (va_status != VA_STATUS_SUCCESS) { + RTC_LOG(LS_ERROR) << "vaSyncSurface failed va_status = " << va_status; + return false; + } + VACodedBufferSegment* buf_list = NULL; + uint32_t coded_size = 0; + + va_status = vaMapBuffer( + context_->va_dpy, + context_->coded_buf[context_->current_frame_display % SURFACE_NUM], + (void**)(&buf_list)); + if (va_status != VA_STATUS_SUCCESS) { + RTC_LOG(LS_ERROR) << "vaMapBuffer failed va_status = " << va_status; + return false; + } + while (buf_list != NULL) { + memcpy(&output[coded_size], buf_list->buf, buf_list->size); + coded_size += buf_list->size; + buf_list = (VACodedBufferSegment*)buf_list->next; + } + + vaUnmapBuffer( + context_->va_dpy, + context_->coded_buf[context_->current_frame_display % SURFACE_NUM]); + + update_ReferenceFrames(context_.get()); + + context_->current_frame_encoding++; + + encoded = std::vector(output, output + coded_size); + return true; +} + +} // namespace livekit \ No newline at end of file diff --git a/webrtc-sys/src/vaapi/vaapi_h264_encoder_wrapper.h b/webrtc-sys/src/vaapi/vaapi_h264_encoder_wrapper.h new file mode 100644 index 000000000..13e6a5cff --- /dev/null +++ b/webrtc-sys/src/vaapi/vaapi_h264_encoder_wrapper.h @@ -0,0 +1,127 @@ +#ifndef VAAPI_H264_ENCODER_WRAPPER_H_ +#define VAAPI_H264_ENCODER_WRAPPER_H_ + +#include +#include +#include +#include + +#include +#include + +#if defined(WIN32) +#include "vaapi_display_win32.h" +using VaapiDisplay = livekit::VaapiDisplayWin32; +#elif defined(__linux__) +#include "vaapi_display_drm.h" +using VaapiDisplay = livekit::VaapiDisplayDrm ; +#endif +#define SURFACE_NUM 16 /* 16 surfaces for reference */ + +typedef struct { + // one of: VAProfileH264ConstrainedBaseline, VAProfileH264Main, + // VAProfileH264High + VAProfile h264_profile; + int h264_entropy_mode; + int frame_width; + int frame_height; + int frame_rate; + uint32_t bitrate; + int initial_qp; + int minimal_qp; + int intra_period; + int intra_idr_period; + int ip_period; + int rc_mode; +} VA264Config; + +typedef struct { + VADisplay va_dpy; + + VAConfigAttrib attrib[VAConfigAttribTypeMax]; + VAConfigAttrib config_attrib[VAConfigAttribTypeMax]; + int config_attrib_num; + int enc_packed_header_idx; + VASurfaceID src_surface[SURFACE_NUM]; + VABufferID coded_buf[SURFACE_NUM]; + VASurfaceID ref_surface[SURFACE_NUM]; + VAConfigID config_id; + VAContextID context_id; + VAEncSequenceParameterBufferH264 seq_param; + VAEncPictureParameterBufferH264 pic_param; + VAEncSliceParameterBufferH264 slice_param; + VAPictureH264 current_curr_pic; + VAPictureH264 reference_frames[SURFACE_NUM]; + VAPictureH264 ref_pic_list0_p[SURFACE_NUM * 2]; + VAPictureH264 ref_pic_list0_b[SURFACE_NUM * 2]; + VAPictureH264 ref_pic_list1_b[SURFACE_NUM * 2]; + + // Default entrypoint for Encode + int requested_entrypoint; + int selected_entrypoint; + + uint32_t num_short_term; + int constraint_set_flag; + int h264_packedheader; /* support pack header? */ + int h264_maxref; + int frame_width_mbaligned; + int frame_height_mbaligned; + uint32_t current_frame_num; + int current_frame_type; + uint64_t current_frame_encoding; + uint64_t current_frame_display; + uint64_t current_idr_display; + + uint8_t* encoded_buffer; + VA264Config config; +} VA264Context; + +namespace livekit { + +class VaapiH264EncoderWrapper { + public: + VaapiH264EncoderWrapper(); + ~VaapiH264EncoderWrapper(); + + // Initialize the encoder with the given parameters. + bool Initialize(int width, + int height, + int bitrate, + int intra_period, + int idr_period, + int ip_period, + int frame_rate, + VAProfile profile, + int rc_mode); + + // Encode a frame and return the encoded data. + bool Encode(int fourcc, + const uint8_t* y, + const uint8_t* u, + const uint8_t* v, + bool forceIDR, + std::vector& output); + + void UpdateRates(int frame_rate, int bitrate) { + if (context_) { + context_->config.frame_rate = frame_rate; + context_->config.bitrate = bitrate; + } + } + + bool IsInitialized() const { + return initialized_; + } + + // Release resources. + void Destroy(); + + private: + std::unique_ptr context_; + std::unique_ptr va_display_; + bool initialized_ = false; +}; + +} // namespace livekit + +#endif // VAAPI_H264_ENCODER_WRAPPER_H_ diff --git a/webrtc-sys/src/video_decoder_factory.cpp b/webrtc-sys/src/video_decoder_factory.cpp index d9ea8bae8..d12ad1e88 100644 --- a/webrtc-sys/src/video_decoder_factory.cpp +++ b/webrtc-sys/src/video_decoder_factory.cpp @@ -35,6 +35,10 @@ #include "livekit/android.h" #endif +#if defined(__linux__) && defined(__x86_64__) && !defined(WEBRTC_ANDROID) +#include "nvidia/nvidia_decoder_factory.h" +#endif + namespace livekit { VideoDecoderFactory::VideoDecoderFactory() { @@ -46,7 +50,11 @@ VideoDecoderFactory::VideoDecoderFactory() { factories_.push_back(CreateAndroidVideoDecoderFactory()); #endif - // TODO(theomonnom): Add other HW decoders here +#if defined(__linux__) && defined(__x86_64__) && !defined(WEBRTC_ANDROID) + if (webrtc::NvidiaVideoDecoderFactory::IsSupported()) { + factories_.push_back(std::make_unique()); + } +#endif } std::vector VideoDecoderFactory::GetSupportedFormats() diff --git a/webrtc-sys/src/video_encoder_factory.cpp b/webrtc-sys/src/video_encoder_factory.cpp index cd362675e..370a7d257 100644 --- a/webrtc-sys/src/video_encoder_factory.cpp +++ b/webrtc-sys/src/video_encoder_factory.cpp @@ -37,6 +37,11 @@ #include "livekit/android.h" #endif +#if defined(__linux__) && defined(__x86_64__) && !defined(WEBRTC_ANDROID) +#include "nvidia/nvidia_encoder_factory.h" +#include "vaapi/vaapi_encoder_factory.h" +#endif + namespace livekit { using Factory = webrtc::VideoEncoderFactoryTemplate< @@ -58,7 +63,13 @@ VideoEncoderFactory::InternalFactory::InternalFactory() { factories_.push_back(CreateAndroidVideoEncoderFactory()); #endif - // TODO(theomonnom): Add other HW encoders here +#if defined(__linux__) && defined(__x86_64__) && !defined(WEBRTC_ANDROID) + if (webrtc::NvidiaVideoEncoderFactory::IsSupported()) { + factories_.push_back(std::make_unique()); + } else if (webrtc::VAAPIVideoEncoderFactory::IsSupported()) { + factories_.push_back(std::make_unique()); + } +#endif } std::vector @@ -86,7 +97,8 @@ VideoEncoderFactory::InternalFactory::QueryCodecSupport( std::unique_ptr VideoEncoderFactory::InternalFactory::Create( - const webrtc::Environment& env, const webrtc::SdpVideoFormat& format) { + const webrtc::Environment& env, + const webrtc::SdpVideoFormat& format) { for (const auto& factory : factories_) { for (const auto& supported_format : factory->GetSupportedFormats()) { if (supported_format.IsSameCodec(format)) @@ -121,7 +133,8 @@ VideoEncoderFactory::CodecSupport VideoEncoderFactory::QueryCodecSupport( } std::unique_ptr VideoEncoderFactory::Create( - const webrtc::Environment& env, const webrtc::SdpVideoFormat& format) { + const webrtc::Environment& env, + const webrtc::SdpVideoFormat& format) { std::unique_ptr encoder; if (format.IsCodecInList(internal_factory_->GetSupportedFormats())) { encoder = std::make_unique( diff --git a/webrtc-sys/test/CMakeLists.txt b/webrtc-sys/test/CMakeLists.txt new file mode 100644 index 000000000..07af2c706 --- /dev/null +++ b/webrtc-sys/test/CMakeLists.txt @@ -0,0 +1,92 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX C) +enable_language(ASM) + +set(BINARY_NAME "h264_benchmark") + +# Set the C++ standard to C++23. +set(CMAKE_CXX_STANDARD 20) + +set(CMAKE_BUILD_TYPE Debug) + + +add_definitions(-DWEBRTC_POSIX) +add_definitions(-DWEBRTC_LINUX) +add_definitions(-DUSE_UDEV) +add_definitions(-DUSE_AURA=1) +add_definitions(-DUSE_GLIB=1) +add_definitions(-DUSE_OZONE=1) +add_definitions(-D__STDC_CONSTANT_MACROS) +add_definitions(-D__STDC_FORMAT_MACROS) +add_definitions(-D_FILE_OFFSET_BITS=64) +add_definitions(-D_LARGEFILE_SOURCE) +add_definitions(-D_LARGEFILE64_SOURCE) +add_definitions(-D_GNU_SOURCE) +add_definitions(-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_NONE) +add_definitions(-D_GLIBCXX_ASSERTIONS=1) +add_definitions(-DCR_CLANG_REVISION=\"llvmorg-19-init-8091-gab037c4f-1\") +add_definitions(-DCR_SYSROOT_KEY=20230611T210420Z-2) +add_definitions(-DDYNAMIC_ANNOTATIONS_ENABLED=1) +add_definitions(-DWEBRTC_ENABLE_PROTOBUF=0) +add_definitions(-DWEBRTC_STRICT_FIELD_TRIALS=0) +add_definitions(-DWEBRTC_INCLUDE_INTERNAL_AUDIO_DEVICE) +add_definitions(-DRTC_USE_LIBAOM_AV1_ENCODER) +add_definitions(-DRTC_ENABLE_VP9) +add_definitions(-DRTC_DAV1D_IN_INTERNAL_DECODER_FACTORY) +add_definitions(-DWEBRTC_HAVE_SCTP) +add_definitions(-DWEBRTC_USE_H264) +add_definitions(-DWEBRTC_ENABLE_LIBEVENT) +add_definitions(-DWEBRTC_LIBRARY_IMPL) +add_definitions(-DWEBRTC_ENABLE_AVX2) + +include_directories( + "${CMAKE_CURRENT_SOURCE_DIR}/../libwebrtc/linux-x64-release/include" + "${CMAKE_CURRENT_SOURCE_DIR}/../libwebrtc/linux-x64-release/include/third_party/abseil-cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/../libwebrtc/linux-x64-release/include/third_party/libyuv/include" + "${CMAKE_CURRENT_SOURCE_DIR}/../src/nvidia/NvCodec/include" + "${CMAKE_CURRENT_SOURCE_DIR}/../src/nvidia/NvCodec/NvCodec" + "${CMAKE_CURRENT_SOURCE_DIR}/../src" +) + +link_libraries( + "${CMAKE_CURRENT_SOURCE_DIR}/../libwebrtc/linux-x64-release/lib/libwebrtc.a" +) + +find_package (Threads) + +add_executable(${BINARY_NAME} + "test_main.cc" + "benchmark.cc" + "benchmark_openh264.cc" + "video_source.cc" + "fileutils.cc" + "cpu/cpu_linux.cc" + + #"benchmark_nvidia.cc" + #"../src/nvidia/NvCodec/NvCodec/NvDecoder/NvDecoder.cpp" + #"../src/nvidia/NvCodec/NvCodec/NvEncoder/NvEncoder.cpp" + #"../src/nvidia/NvCodec/NvCodec/NvEncoder/NvEncoderCuda.cpp" + #"../src/nvidia/h264_encoder_impl.cpp" + #"../src/nvidia/h264_decoder_impl.cpp" + #"../src/nvidia/nvidia_decoder_factory.cpp" + #"../src/nvidia/nvidia_encoder_factory.cpp" + #"../src/nvidia/cuda_context.cpp" + #"../src/nvidia/implib/libcuda.so.init.c" + #"../src/nvidia/implib/libcuda.so.tramp.S" + #"../src/nvidia/implib/libnvcuvid.so.init.c" + #"../src/nvidia/implib/libnvcuvid.so.tramp.S" + + "benchmark_vaapi.cc" + "../src/vaapi/vaapi_display_drm.cpp" + "../src/vaapi/vaapi_h264_encoder_wrapper.cpp" + "../src/vaapi/vaapi_encoder_factory.cpp" + "../src/vaapi/h264_encoder_impl.cpp" + "../src/vaapi/implib/libva-drm.so.init.c" + "../src/vaapi/implib/libva-drm.so.tramp.S" + "../src/vaapi/implib/libva.so.init.c" + "../src/vaapi/implib/libva.so.tramp.S" +) + +target_link_libraries(${BINARY_NAME} ${CMAKE_THREAD_LIBS_INIT}) +target_link_libraries(${BINARY_NAME} dl) \ No newline at end of file diff --git a/webrtc-sys/test/benchmark.cc b/webrtc-sys/test/benchmark.cc new file mode 100644 index 000000000..5ae4df619 --- /dev/null +++ b/webrtc-sys/test/benchmark.cc @@ -0,0 +1,448 @@ +/* + * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "benchmark.h" + +#include +#include +#include +#include +#if defined(_WIN32) +#include +#endif + +#include "api/video/i420_buffer.h" +#include "common_video/libyuv/include/webrtc_libyuv.h" +#include "fileutils.h" +#include "modules/video_coding/utility/simulcast_rate_allocator.h" +#include "rtc_base/event.h" +#include "video_source.h" + +#define SSIM_CALC 0 // by default, don't compute SSIM + +using namespace webrtc; + +#define EXPECT_EQ (a, b) + +FrameQueueTuple::~FrameQueueTuple() { + if (_codecSpecificInfo != NULL) { + delete _codecSpecificInfo; + } + if (_frame != NULL) { + delete _frame; + } +} + +void FrameQueue::PushFrame(VideoFrame* frame, + webrtc::CodecSpecificInfo* codecSpecificInfo) { + webrtc::MutexLock cs(&_queueRWLock); + _frameBufferQueue.push(new FrameQueueTuple(frame, codecSpecificInfo)); +} + +FrameQueueTuple* FrameQueue::PopFrame() { + webrtc::MutexLock cs(&_queueRWLock); + if (_frameBufferQueue.empty()) { + return NULL; + } + FrameQueueTuple* tuple = _frameBufferQueue.front(); + _frameBufferQueue.pop(); + return tuple; +} + +bool FrameQueue::Empty() { + webrtc::MutexLock cs(&_queueRWLock); + return _frameBufferQueue.empty(); +} + +uint32_t VideoEncodeCompleteCallback::EncodedBytes() { + return _encodedBytes; +} + +webrtc::EncodedImageCallback::Result +VideoEncodeCompleteCallback::OnEncodedImage( + const webrtc::EncodedImage& encodedImage, + const webrtc::CodecSpecificInfo* codecSpecificInfo) { + _test.UpdateEncodedBytes(encodedImage.GetEncodedData()->size()); + _encodedBytes += encodedImage.GetEncodedData()->size(); + + if (_encodedFile != NULL) { + if (fwrite(encodedImage.GetEncodedData()->data(), 1, + encodedImage.GetEncodedData()->size(), + _encodedFile) != encodedImage.GetEncodedData()->size()) { + fprintf(stderr, "Error writing to encoded file %s\n", + _test._outname.c_str()); + } + } + + return webrtc::EncodedImageCallback::Result( + webrtc::EncodedImageCallback::Result::OK); +} + +Benchmark::Benchmark() + : _resultsFileName(webrtc::test::OutputPath() + "benchmark.txt"), + _codecName("Default") {} + +Benchmark::Benchmark(std::string name, std::string description) + : _name(name), + _description(description), + _resultsFileName(webrtc::test::OutputPath() + "benchmark.txt"), + _codecName("Default") {} + +Benchmark::Benchmark(std::string name, + std::string description, + std::string resultsFileName, + std::string codecName) + : _name(name), + _description(description), + _resultsFileName(resultsFileName), + _codecName(codecName), + _cpu(webrtc::CpuWrapper::CreateCpu()) {} + +void Benchmark::Perform() { + std::vector sources; + std::vector::iterator it; + + // Configuration -------------------------- + sources.push_back(new const VideoSource( + webrtc::test::ProjectRootPath() + "resources/FourPeople_1280x720_30.yuv", + kWHD)); + sources.push_back( + new const VideoSource(webrtc::test::ProjectRootPath() + + "resources/Big_Buck_Bunny_1920x1080_30.yuv", + kWFullHD)); + + const VideoSize size[] = {kWHD, kWFullHD}; + const int frameRate[] = {30}; + // Specifies the framerates for which to perform a speed test. + const bool speedTestMask[] = {true}; + const int bitRate[] = {500, 1000, 2000, 3000, 4000}; + // Determines the number of iterations to perform to arrive at the speed + // result. + enum { kSpeedTestIterations = 8 }; + // ---------------------------------------- + + const int nFrameRates = sizeof(frameRate) / sizeof(*frameRate); + assert(sizeof(speedTestMask) / sizeof(*speedTestMask) == nFrameRates); + const int nBitrates = sizeof(bitRate) / sizeof(*bitRate); + int testIterations = 1; + + double fps[nBitrates]; + uint32_t cpuUsage[nBitrates]; + double totalEncodeTime[nBitrates]; + double totalDecodeTime[nBitrates]; + + _results.open(_resultsFileName.c_str(), std::fstream::out); + _results << GetMagicStr() << std::endl; + _results << _codecName << std::endl; + + for (it = sources.begin(); it < sources.end(); it++) { + int i = 0; + for (int j = 0; j < nFrameRates; j++) { + _target = *it; + _inname = (*it)->GetFileName(); + std::cout << (*it)->GetName() << ", " + << VideoSource::GetSizeString(size[i]) << ", " << frameRate[j] + << " fps" << ", " << _name << std::endl; + _results << (*it)->GetName() << "," << VideoSource::GetSizeString(size[i]) + << "," << frameRate[j] << " fps" << ", " << _name << std::endl + << "Bitrate [kbps]"; + + if (speedTestMask[j]) { + testIterations = kSpeedTestIterations; + } else { + testIterations = 1; + } + + for (int k = 0; k < nBitrates; k++) { + _bitRate = (bitRate[k]); + double avgFps = 0.0; + uint32_t currCpuUsage = 0; + totalEncodeTime[k] = 0; + + std::cout << "TargetBitrate [kbps]:" << " " << _bitRate << std::endl; + + for (int l = 0; l < testIterations; l++) { + PerformNormalTest(); + uint32_t cpuUsage = _cpu->CpuUsage(); + if (cpuUsage > 0) { + currCpuUsage += cpuUsage; + int coreCount = _cpu->GetNumCores(); + std::string str = "CPU Usage[%]: cores "; + str += std::to_string(coreCount); + str += ", usage " + std::to_string(cpuUsage) + "%" + + ", Test Iteration: " + std::to_string(l + 1) + "/" + + std::to_string(testIterations); + std::cout << str << std::flush; + for (int i = 0; i < str.length(); ++i) { + std::cout << "\b"; + } + } + _appendNext = false; + avgFps += _framecnt / (_totalEncodeTime); + totalEncodeTime[k] += _totalEncodeTime; + } + avgFps /= testIterations; + totalEncodeTime[k] /= testIterations; + currCpuUsage /= testIterations; + + double actualBitRate = ActualBitRate(_framecnt) / 1000.0; + std::cout << "ActualBitRate [kbps]:" << " " << actualBitRate + << std::endl; + _results << "," << actualBitRate; + fps[k] = avgFps; + cpuUsage[k] = currCpuUsage; + } + + std::cout << std::endl << "CpuUsage [%]:"; + _results << std::endl << "CpuUsage [%]"; + for (int k = 0; k < nBitrates; k++) { + std::cout << " " << cpuUsage[k] << "%"; + _results << "," << cpuUsage[k] << "%"; + } + std::cout << std::endl << "Encode Time[ms]:"; + _results << std::endl << "Encode Time[ms]"; + for (int k = 0; k < nBitrates; k++) { + std::cout << " " << totalEncodeTime[k]; + _results << "," << totalEncodeTime[k]; + } + + if (speedTestMask[j]) { + std::cout << std::endl << "Speed [fps]:"; + _results << std::endl << "Speed [fps]"; + for (int k = 0; k < nBitrates; k++) { + std::cout << " " << static_cast(fps[k] + 0.5); + _results << "," << static_cast(fps[k] + 0.5); + } + } + std::cout << std::endl << std::endl; + _results << std::endl << std::endl; + } + i++; + delete *it; + } + _results.close(); +} + +void Benchmark::PerformNormalTest() { + _encoder = GetNewEncoder(); + _lengthSourceFrame = _target->GetFrameLength(); + CodecSettings(_target->GetWidth(), _target->GetHeight(), + _target->GetFrameRate(), _bitRate); + Setup(); + std::unique_ptr waitEvent = std::make_unique(); + //_inputVideoBuffer.VerifyAndAllocate(_lengthSourceFrame); + _encoder->InitEncode(&_inst, 4, 1440); + CodecSpecific_InitBitrate(); + //_decoder->InitDecode(&_inst,1); + + FrameQueue frameQueue; + VideoEncodeCompleteCallback encCallback(_encodedFile, &frameQueue, *this); + + _encoder->RegisterEncodeCompleteCallback(&encCallback); + + _totalEncodeTime = _totalDecodeTime = 0; + _totalEncodePipeTime = _totalDecodePipeTime = 0; + bool complete = false; + _framecnt = 0; + _encFrameCnt = 0; + _sumEncBytes = 0; + _lengthEncFrame = 0; + while (!complete) { + complete = Encode(); + _framecnt++; + _encFrameCnt++; + /* + if (!frameQueue.Empty() || complete) { + while (!frameQueue.Empty()) { + _frameToDecode = static_cast(frameQueue.PopFrame()); + int ret = Decode(); + delete _frameToDecode; + _frameToDecode = NULL; + if (ret < 0) { + fprintf(stderr, "\n\nError in decoder: %d\n\n", ret); + exit(EXIT_FAILURE); + } else if (ret == 0) { + _framecnt++; + } else { + fprintf(stderr, "\n\nPositive return value from decode!\n\n"); + } + } + }*/ + // waitEvent->Wait(webrtc::TimeDelta::Seconds(5)); + } + + //_inputVideoBuffer.Free(); + //_encodedVideoBuffer.Free(); + //_decodedVideoBuffer.Free(); + + Teardown(); +} + +void Benchmark::Teardown() { + // Use _sourceFile as a check to prevent multiple Teardown() calls. + if (_sourceFile == NULL) { + return; + } + + _encoder->Release(); + + fclose(_sourceFile); + _sourceFile = NULL; + + delete[] _sourceBuffer; + _sourceBuffer = NULL; +} + +void Benchmark::CodecSpecific_InitBitrate() { + webrtc::SimulcastRateAllocator init_allocator(_inst); + + if (_bitRate == 0) { + VideoBitrateAllocation allocation = + init_allocator.Allocate(VideoBitrateAllocationParameters( + DataRate::KilobitsPerSec(600), _inst.maxFramerate)); + _encoder->SetRates(webrtc::VideoEncoder::RateControlParameters( + allocation, _inst.maxFramerate)); + } else { + VideoBitrateAllocation allocation = + init_allocator.Allocate(VideoBitrateAllocationParameters( + DataRate::BitsPerSec(_bitRate), _inst.maxFramerate)); + _encoder->SetRates(webrtc::VideoEncoder::RateControlParameters( + allocation, _inst.maxFramerate)); + } +} + +bool Benchmark::Encode() { + _lengthEncFrame = 0; + if (_sourceBuffer == NULL) { + _sourceBuffer = new unsigned char[_lengthSourceFrame]; + } + auto size = fread(_sourceBuffer, 1, _lengthSourceFrame, _sourceFile); + if (size <= 0) { + return true; + } + // TODO: build video frame from buffer ptr. + rtc::scoped_refptr buffer( + webrtc::I420Buffer::Create(_inst.width, _inst.height)); + + buffer->InitializeData(); + + memcpy(buffer->MutableDataY(), _sourceBuffer, _lengthSourceFrame); + + webrtc::VideoFrame inputVideoBuffer = + webrtc::VideoFrame::Builder() + .set_video_frame_buffer(buffer) + .set_rtp_timestamp( + (unsigned int)(_encFrameCnt * 9e4 / _inst.maxFramerate)) + .build(); + + if (feof(_sourceFile) != 0) { + return true; + } + _encodeCompleteTime = 0; + _encodeTimes[inputVideoBuffer.timestamp()] = tGetTime(); + std::vector frame_types(1, VideoFrameType::kVideoFrameDelta); + + // check SLI queue + _hasReceivedSLI = false; + while (!_signalSLI.empty() && _signalSLI.front().delay == 0) { + // SLI message has arrived at sender side + _hasReceivedSLI = true; + _pictureIdSLI = _signalSLI.front().id; + _signalSLI.pop_front(); + } + // decrement SLI queue times + for (std::list::iterator it = _signalSLI.begin(); + it != _signalSLI.end(); it++) { + (*it).delay--; + } + + // check PLI queue + _hasReceivedPLI = false; + while (!_signalPLI.empty() && _signalPLI.front().delay == 0) { + // PLI message has arrived at sender side + _hasReceivedPLI = true; + _signalPLI.pop_front(); + } + // decrement PLI queue times + for (std::list::iterator it = _signalPLI.begin(); + it != _signalPLI.end(); it++) { + (*it).delay--; + } + + if (_hasReceivedPLI) { + // respond to PLI by encoding a key frame + frame_types[0] = VideoFrameType::kVideoFrameKey; + _hasReceivedPLI = false; + _hasReceivedSLI = false; // don't trigger both at once + } + + int ret = _encoder->Encode(inputVideoBuffer, &frame_types); + + if (_encodeCompleteTime > 0) { + _totalEncodeTime += + _encodeCompleteTime - _encodeTimes[inputVideoBuffer.timestamp()]; + } else { + _totalEncodeTime += tGetTime() - _encodeTimes[inputVideoBuffer.timestamp()]; + } + assert(ret >= 0); + return false; +} + +webrtc::CodecSpecificInfo* Benchmark::CopyCodecSpecificInfo( + const webrtc::CodecSpecificInfo* codecSpecificInfo) const { + webrtc::CodecSpecificInfo* info = new webrtc::CodecSpecificInfo; + *info = *codecSpecificInfo; + return info; +} + +void Benchmark::Setup() { + // Use _sourceFile as a check to prevent multiple Setup() calls. + if (_sourceFile != NULL) { + return; + } + + std::stringstream ss; + std::string strTestNo; + ss << "0"; + ss >> strTestNo; + + // Check if settings exist. Otherwise use defaults. + if (_outname == "") { + _outname = + webrtc::test::OutputPath() + "out_normaltest" + strTestNo + ".yuv"; + } + + if (_codecName == "") { + _codecName = + webrtc::test::OutputPath() + "encoded_normaltest" + strTestNo + ".yuv"; + } + + if ((_sourceFile = fopen(_inname.c_str(), "rb")) == NULL) { + printf("Cannot read file %s.\n", _inname.c_str()); + exit(1); + } + + if ((_encodedFile = fopen(_codecName.c_str(), "wb")) == NULL) { + printf("Cannot write encoded file.\n"); + exit(1); + } + + char mode[3] = "wb"; + if (_appendNext) { + strncpy(mode, "ab", 3); + } + + // if ((_decodedFile = fopen(_outname.c_str(), mode)) == NULL) { + // printf("Cannot write file %s.\n", _outname.c_str()); + // exit(1); + // } + + _appendNext = true; +} diff --git a/webrtc-sys/test/benchmark.h b/webrtc-sys/test/benchmark.h new file mode 100644 index 000000000..a86f6b737 --- /dev/null +++ b/webrtc-sys/test/benchmark.h @@ -0,0 +1,212 @@ +; /* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_VIDEO_CODING_CODECS_TEST_FRAWEWORK_BENCHMARK_H_ +#define WEBRTC_MODULES_VIDEO_CODING_CODECS_TEST_FRAWEWORK_BENCHMARK_H_ + +#include +#include +#include +#include +#include + +#include "cpu/cpu_linux.h" +#include "modules/include/module_common_types.h" +#include "modules/video_coding/include/video_codec_interface.h" +#include "rtc_base/synchronization/mutex.h" +#include "system_wrappers/include/clock.h" + + +class VideoSource; +class Benchmark; + +// feedback signal to encoder +struct fbSignal { + fbSignal(int d, uint8_t pid) : delay(d), id(pid) {}; + int delay; + uint8_t id; +}; + +class FrameQueueTuple { + public: + FrameQueueTuple(webrtc::VideoFrame* frame, + const webrtc::CodecSpecificInfo* codecSpecificInfo = NULL) + : _frame(frame), _codecSpecificInfo(codecSpecificInfo) {}; + ~FrameQueueTuple(); + webrtc::VideoFrame* _frame; + const webrtc::CodecSpecificInfo* _codecSpecificInfo; +}; + +class FrameQueue { + public: + FrameQueue() {} + + ~FrameQueue() {} + + void PushFrame(webrtc::VideoFrame* frame, + webrtc::CodecSpecificInfo* codecSpecificInfo = NULL); + FrameQueueTuple* PopFrame(); + bool Empty(); + + private: + webrtc::Mutex _queueRWLock; + std::queue _frameBufferQueue; +}; + +class VideoEncodeCompleteCallback : public webrtc::EncodedImageCallback { + public: + VideoEncodeCompleteCallback(FILE* encodedFile, + FrameQueue* frameQueue, + Benchmark& test) + : _encodedFile(encodedFile), + _frameQueue(frameQueue), + _test(test), + _encodedBytes(0) {} + + webrtc::EncodedImageCallback::Result OnEncodedImage( + const webrtc::EncodedImage& encoded_image, + const webrtc::CodecSpecificInfo* codec_specific_info) override; + + uint32_t EncodedBytes(); + + private: + FILE* _encodedFile; + FrameQueue* _frameQueue; + Benchmark& _test; + uint32_t _encodedBytes; +}; + +class Benchmark { + public: + friend class VideoEncodeCompleteCallback; + + public: + Benchmark(); + virtual void Perform(); + virtual bool IsSupported() = 0; + + protected: + Benchmark(std::string name, std::string description); + Benchmark(std::string name, + std::string description, + std::string resultsFileName, + std::string codecName); + virtual webrtc::VideoEncoder* GetNewEncoder() = 0; + virtual void PerformNormalTest(); + virtual void CodecSpecific_InitBitrate(); + static const char* GetMagicStr() { return "#!benchmark1.0"; } + + double ActualBitRate(int nFrames) { + return 8.0 * _sumEncBytes / (nFrames / _inst.maxFramerate); + } + + webrtc::CodecSpecificInfo* CopyCodecSpecificInfo( + const webrtc::CodecSpecificInfo* codecSpecificInfo) const; + + bool Encode(); + + void Setup(); + + void Teardown(); + + void CodecSettings(int width, + int height, + uint32_t frameRate /*=30*/, + uint32_t bitRate /*=0*/) { + if (bitRate > 0) { + _bitRate = bitRate; + } else if (_bitRate == 0) { + _bitRate = 600; + } + _inst.codecType = webrtc::kVideoCodecH264; + _inst.maxFramerate = (unsigned char)frameRate; + _inst.minBitrate = (unsigned char)frameRate; + _inst.startBitrate = (int)_bitRate; + _inst.maxBitrate = 8000; + _inst.width = width; + _inst.height = height; + _inst.numberOfSimulcastStreams = 1; + _inst.simulcastStream[0].width = width; + _inst.simulcastStream[0].height = height; + _inst.simulcastStream[0].maxBitrate = 8000; + _inst.simulcastStream[0].minBitrate = _bitRate; + _inst.simulcastStream[0].targetBitrate = _bitRate; + _inst.simulcastStream[0].maxFramerate = frameRate; + _inst.simulcastStream[0].active = true; + _inst.SetScalabilityMode(webrtc::ScalabilityMode::kL1T1); + _inst.mode = webrtc::VideoCodecMode::kRealtimeVideo; + _inst.qpMax = 56; + _inst.SetFrameDropEnabled(true); + } + + double tGetTime() { + // return time in sec + return ((double)(webrtc::Clock::GetRealTimeClock()->TimeInMilliseconds()) / + 1000); + } + + virtual webrtc::CodecSpecificInfo* CreateEncoderSpecificInfo() const { + return NULL; + }; + + void UpdateEncodedBytes(int encodedBytes) { _sumEncBytes += encodedBytes; } + + const VideoSource* _target; + std::string _resultsFileName; + std::ofstream _results; + std::string _name; + std::string _description; + std::string _codecName; + std::string _inname; + std::string _outname; + webrtc::VideoEncoder* _encoder; + webrtc::VideoDecoder* _decoder; + uint32_t _bitRate; + bool _appendNext = false; + int _framecnt; + int _encFrameCnt; + double _totalEncodeTime; + double _totalDecodeTime; + double _decodeCompleteTime; + double _encodeCompleteTime; + double _totalEncodePipeTime; + double _totalDecodePipeTime; + webrtc::VideoCodec _inst; + int _sumEncBytes; + + unsigned int _lengthSourceFrame = 0; + unsigned char* _sourceBuffer = nullptr; + + FILE* _encodedFile = nullptr; + unsigned int _lengthEncFrame = 0; + FrameQueueTuple* _frameToDecode = nullptr; + + FILE* _sourceFile = nullptr; + FILE* _decodedFile = nullptr; + + bool _hasReceivedPLI = false; + bool _waitForKey = false; + std::map _encodeTimes; + std::map _decodeTimes; + + bool _missingFrames = false; + std::list _signalSLI; + int _rttFrames = 0; + mutable bool _hasReceivedSLI = false; + mutable bool _hasReceivedRPSI = false; + uint8_t _pictureIdSLI = 0; + uint16_t _pictureIdRPSI = 0; + uint64_t _lastDecRefPictureId = 0; + uint64_t _lastDecPictureId = 0; + std::list _signalPLI; + webrtc::CpuWrapper* _cpu; +}; + +#endif // WEBRTC_MODULES_VIDEO_CODING_CODECS_TEST_FRAWEWORK_BENCHMARK_H_ diff --git a/webrtc-sys/test/benchmark_nvidia.cc b/webrtc-sys/test/benchmark_nvidia.cc new file mode 100644 index 000000000..c5e5dbef2 --- /dev/null +++ b/webrtc-sys/test/benchmark_nvidia.cc @@ -0,0 +1,51 @@ +#include "benchmark_nvidia.h" + +#include "api/environment/environment_factory.h" +#include "modules/video_coding/codecs/h264/include/h264.h" +#include "fileutils.h" + +using namespace webrtc; + +NvidiaBenchmark::NvidiaBenchmark() + : Benchmark("NvidiaBenchmark", + "Nvidia benchmark over a range of test cases", + webrtc::test::OutputPath() + "NvidiaBenchmark.txt", + "nvidia_bitstream_output.h264") {} + +NvidiaBenchmark::NvidiaBenchmark(std::string name, std::string description) + : Benchmark(name, + description, + webrtc::test::OutputPath() + "NvidiaBenchmark.txt", + "nvidia_bitstream_output.h264") {} + +NvidiaBenchmark::NvidiaBenchmark(std::string name, + std::string description, + std::string resultsFileName) + : Benchmark(name, description, resultsFileName, "nvidia_bitstream_output.h264") {} + + +VideoEncoder* NvidiaBenchmark::GetNewEncoder() { + if (!NvidiaVideoEncoderFactory::IsSupported()) { + fprintf(stderr, "NVIDIA is not supported on this system.\n"); + return nullptr; + } + + if (!_factory) { + _factory = std::make_unique(); + } + std::map baselineParameters = { + {"profile-level-id", "4d0032"}, + {"level-asymmetry-allowed", "1"}, + {"packetization-mode", "1"}, + }; + auto format = SdpVideoFormat("H264", baselineParameters); + + auto enc = _factory->Create(webrtc::CreateEnvironment(), format); + if (!enc) { + fprintf(stderr, "Failed to create H264 encoder.\n"); + return nullptr; + } + _encoder = std::move(enc); + + return _encoder.get(); +} \ No newline at end of file diff --git a/webrtc-sys/test/benchmark_nvidia.h b/webrtc-sys/test/benchmark_nvidia.h new file mode 100644 index 000000000..167fb383f --- /dev/null +++ b/webrtc-sys/test/benchmark_nvidia.h @@ -0,0 +1,24 @@ +#include "benchmark.h" +#include "nvidia/nvidia_encoder_factory.h" + +class NvidiaBenchmark : public Benchmark { + public: + NvidiaBenchmark(); + NvidiaBenchmark(std::string name, std::string description); + NvidiaBenchmark(std::string name, + std::string description, + std::string resultsFileName); + + ~NvidiaBenchmark() {} + + bool IsSupported() override { + return webrtc::NvidiaVideoEncoderFactory::IsSupported(); + } + + protected: + webrtc::VideoEncoder* GetNewEncoder() override; + + private: + std::unique_ptr _encoder; + std::unique_ptr _factory; +}; \ No newline at end of file diff --git a/webrtc-sys/test/benchmark_openh264.cc b/webrtc-sys/test/benchmark_openh264.cc new file mode 100644 index 000000000..59a2adbcd --- /dev/null +++ b/webrtc-sys/test/benchmark_openh264.cc @@ -0,0 +1,35 @@ +#include "benchmark_openh264.h" + +#include "api/environment/environment_factory.h" +#include "modules/video_coding/codecs/h264/include/h264.h" +#include "fileutils.h" + +using namespace webrtc; + +OpenH264Benchmark::OpenH264Benchmark() + : Benchmark("OpenH264Benchmark", + "OpenH264 benchmark over a range of test cases", + webrtc::test::OutputPath() + "OpenH264Benchmark.txt", + "openh264_bitstream_output.h264") {} + +OpenH264Benchmark::OpenH264Benchmark(std::string name, std::string description) + : Benchmark(name, + description, + webrtc::test::OutputPath() + "OpenH264Benchmark.txt", + "openh264_bitstream_output.h264") {} + +OpenH264Benchmark::OpenH264Benchmark(std::string name, + std::string description, + std::string resultsFileName) + : Benchmark(name, description, resultsFileName, "openh264_bitstream_output.h264") {} + +VideoEncoder* OpenH264Benchmark::GetNewEncoder() { + auto enc = CreateH264Encoder(webrtc::CreateEnvironment()); + if (!enc) { + fprintf(stderr, "Failed to create H264 encoder.\n"); + return nullptr; + } + _encoder = std::move(enc); + + return _encoder.get(); +} \ No newline at end of file diff --git a/webrtc-sys/test/benchmark_openh264.h b/webrtc-sys/test/benchmark_openh264.h new file mode 100644 index 000000000..36caed5dd --- /dev/null +++ b/webrtc-sys/test/benchmark_openh264.h @@ -0,0 +1,22 @@ +#include "benchmark.h" + +class OpenH264Benchmark : public Benchmark { + public: + OpenH264Benchmark(); + OpenH264Benchmark(std::string name, std::string description); + OpenH264Benchmark(std::string name, + std::string description, + std::string resultsFileName); + + ~OpenH264Benchmark() {} + + bool IsSupported() override { + return true; + } + + protected: + webrtc::VideoEncoder* GetNewEncoder() override; + + private: + std::unique_ptr _encoder; +}; \ No newline at end of file diff --git a/webrtc-sys/test/benchmark_vaapi.cc b/webrtc-sys/test/benchmark_vaapi.cc new file mode 100644 index 000000000..030e734cb --- /dev/null +++ b/webrtc-sys/test/benchmark_vaapi.cc @@ -0,0 +1,49 @@ +#include "benchmark_vaapi.h" + +#include "api/environment/environment_factory.h" +#include "modules/video_coding/codecs/h264/include/h264.h" +#include "fileutils.h" + +using namespace webrtc; + +VaapiBenchmark::VaapiBenchmark() + : Benchmark("VaapiBenchmark", + "VAAPI benchmark over a range of test cases", + webrtc::test::OutputPath() + "VaapiBenchmark.txt", + "vaapi_bitstream_output.h264") {} + +VaapiBenchmark::VaapiBenchmark(std::string name, std::string description) + : Benchmark(name, + description, + webrtc::test::OutputPath() + "VaapiBenchmark.txt", + "vaapi_bitstream_output.h264") {} + +VaapiBenchmark::VaapiBenchmark(std::string name, + std::string description, + std::string resultsFileName) + : Benchmark(name, description, resultsFileName, "vaapi_bitstream_output.h264") {} + +VideoEncoder* VaapiBenchmark::GetNewEncoder() { + if (!VAAPIVideoEncoderFactory::IsSupported()) { + fprintf(stderr, "VAAPI is not supported on this system.\n"); + return nullptr; + } + if (!_factory) { + _factory = std::make_unique(); + } + std::map baselineParameters = { + {"profile-level-id", "4d0032"}, + {"level-asymmetry-allowed", "1"}, + {"packetization-mode", "1"}, + }; + auto format = SdpVideoFormat("H264", baselineParameters); + + auto enc = _factory->Create(webrtc::CreateEnvironment(), format); + if (!enc) { + fprintf(stderr, "Failed to create H264 encoder.\n"); + return nullptr; + } + _encoder = std::move(enc); + + return _encoder.get(); +} \ No newline at end of file diff --git a/webrtc-sys/test/benchmark_vaapi.h b/webrtc-sys/test/benchmark_vaapi.h new file mode 100644 index 000000000..e72a44964 --- /dev/null +++ b/webrtc-sys/test/benchmark_vaapi.h @@ -0,0 +1,24 @@ +#include "benchmark.h" +#include "vaapi/vaapi_encoder_factory.h" + +class VaapiBenchmark : public Benchmark { + public: + VaapiBenchmark(); + VaapiBenchmark(std::string name, std::string description); + VaapiBenchmark(std::string name, + std::string description, + std::string resultsFileName); + + ~VaapiBenchmark() {} + + bool IsSupported() override { + return webrtc::VAAPIVideoEncoderFactory::IsSupported(); + } + + protected: + webrtc::VideoEncoder* GetNewEncoder() override; + + private: + std::unique_ptr _encoder; + std::unique_ptr _factory; +}; \ No newline at end of file diff --git a/webrtc-sys/test/cpu/cpu_linux.cc b/webrtc-sys/test/cpu/cpu_linux.cc new file mode 100644 index 000000000..6d25b3d41 --- /dev/null +++ b/webrtc-sys/test/cpu/cpu_linux.cc @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "cpu_linux.h" + +#include +#include +#include +#include + +namespace webrtc { +CpuLinux::CpuLinux() + : m_oldBusyTime(0), + m_oldIdleTime(0), + m_oldBusyTimeMulti(NULL), + m_oldIdleTimeMulti(NULL), + m_idleArray(NULL), + m_busyArray(NULL), + m_resultArray(NULL), + m_numCores(0) { + const int result = GetNumCores(); + if (result != -1) { + m_numCores = result; + m_oldBusyTimeMulti = new long long[m_numCores]; + memset(m_oldBusyTimeMulti, 0, sizeof(long long) * m_numCores); + m_oldIdleTimeMulti = new long long[m_numCores]; + memset(m_oldIdleTimeMulti, 0, sizeof(long long) * m_numCores); + m_idleArray = new long long[m_numCores]; + memset(m_idleArray, 0, sizeof(long long) * m_numCores); + m_busyArray = new long long[m_numCores]; + memset(m_busyArray, 0, sizeof(long long) * m_numCores); + m_resultArray = new uint32_t[m_numCores]; + + GetData(m_oldBusyTime, m_oldIdleTime, m_busyArray, m_idleArray); + } +} + +CpuLinux::~CpuLinux() +{ + delete [] m_oldBusyTimeMulti; + delete [] m_oldIdleTimeMulti; + delete [] m_idleArray; + delete [] m_busyArray; + delete [] m_resultArray; +} + +int32_t CpuLinux::CpuUsage() +{ + uint32_t dummy = 0; + uint32_t* dummyArray = NULL; + return CpuUsageMultiCore(dummy, dummyArray); +} + +int32_t CpuLinux::CpuUsageMultiCore(uint32_t& numCores, + uint32_t*& coreArray) +{ + coreArray = m_resultArray; + numCores = m_numCores; + long long busy = 0; + long long idle = 0; + if (GetData(busy, idle, m_busyArray, m_idleArray) != 0) + return -1; + + long long deltaBusy = busy - m_oldBusyTime; + long long deltaIdle = idle - m_oldIdleTime; + m_oldBusyTime = busy; + m_oldIdleTime = idle; + + int retVal = -1; + if (deltaBusy + deltaIdle == 0) + { + retVal = 0; + } + else + { + retVal = (int)(100 * (deltaBusy) / (deltaBusy + deltaIdle)); + } + + if (coreArray == NULL) + { + return retVal; + } + + for (int32_t i = 0; i < m_numCores; i++) + { + deltaBusy = m_busyArray[i] - m_oldBusyTimeMulti[i]; + deltaIdle = m_idleArray[i] - m_oldIdleTimeMulti[i]; + m_oldBusyTimeMulti[i] = m_busyArray[i]; + m_oldIdleTimeMulti[i] = m_idleArray[i]; + if(deltaBusy + deltaIdle == 0) + { + coreArray[i] = 0; + } + else + { + coreArray[i] = (int)(100 * (deltaBusy) / (deltaBusy+deltaIdle)); + } + } + return retVal; +} + + +int CpuLinux::GetData(long long& busy, long long& idle, long long*& busyArray, + long long*& idleArray) +{ + FILE* fp = fopen("/proc/stat", "r"); + if (!fp) + { + return -1; + } + + char line[100]; + if (fgets(line, 100, fp) == NULL) { + fclose(fp); + return -1; + } + char firstWord[100]; + if (sscanf(line, "%s ", firstWord) != 1) { + fclose(fp); + return -1; + } + if (strncmp(firstWord, "cpu", 3) != 0) { + fclose(fp); + return -1; + } + char sUser[100]; + char sNice[100]; + char sSystem[100]; + char sIdle[100]; + if (sscanf(line, "%s %s %s %s %s ", + firstWord, sUser, sNice, sSystem, sIdle) != 5) { + fclose(fp); + return -1; + } + long long luser = atoll(sUser); + long long lnice = atoll(sNice); + long long lsystem = atoll(sSystem); + long long lidle = atoll (sIdle); + + busy = luser + lnice + lsystem; + idle = lidle; + for (int32_t i = 0; i < m_numCores; i++) + { + if (fgets(line, 100, fp) == NULL) { + fclose(fp); + return -1; + } + if (sscanf(line, "%s %s %s %s %s ", firstWord, sUser, sNice, sSystem, + sIdle) != 5) { + fclose(fp); + return -1; + } + luser = atoll(sUser); + lnice = atoll(sNice); + lsystem = atoll(sSystem); + lidle = atoll (sIdle); + busyArray[i] = luser + lnice + lsystem; + idleArray[i] = lidle; + } + fclose(fp); + return 0; +} + +int CpuLinux::GetNumCores() +{ + FILE* fp = fopen("/proc/stat", "r"); + if (!fp) + { + return -1; + } + // Skip first line + char line[100]; + if (!fgets(line, 100, fp)) + { + fclose(fp); + return -1; + } + int numCores = -1; + char firstWord[100]; + do + { + numCores++; + if (fgets(line, 100, fp)) + { + if (sscanf(line, "%s ", firstWord) != 1) { + firstWord[0] = '\0'; + } + } else { + break; + } + } while (strncmp(firstWord, "cpu", 3) == 0); + fclose(fp); + return numCores; +} + +CpuWrapper* CpuWrapper::CreateCpu() +{ + return new CpuLinux(); +} + +} // namespace webrtc diff --git a/webrtc-sys/test/cpu/cpu_linux.h b/webrtc-sys/test/cpu/cpu_linux.h new file mode 100644 index 000000000..369cc31b0 --- /dev/null +++ b/webrtc-sys/test/cpu/cpu_linux.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_SYSTEM_WRAPPERS_SOURCE_CPU_LINUX_H_ +#define WEBRTC_SYSTEM_WRAPPERS_SOURCE_CPU_LINUX_H_ + +#include "cpu_wrapper.h" + +namespace webrtc { +class CpuLinux : public CpuWrapper { + public: + CpuLinux(); + virtual ~CpuLinux(); + + int32_t CpuUsage() override; + int32_t CpuUsage(int8_t* pProcessName, uint32_t length) override { return 0; } + int32_t CpuUsage(uint32_t dwProcessID) override { return 0; } + + int32_t CpuUsageMultiCore(uint32_t& numCores, uint32_t*& array) override; + + void Reset() override { return; } + void Stop() override { return; } + + int GetNumCores() override; + + private: + int GetData(long long& busy, + long long& idle, + long long*& busyArray, + long long*& idleArray); + + + long long m_oldBusyTime; + long long m_oldIdleTime; + + long long* m_oldBusyTimeMulti; + long long* m_oldIdleTimeMulti; + + long long* m_idleArray; + long long* m_busyArray; + uint32_t* m_resultArray; + uint32_t m_numCores; +}; +} // namespace webrtc + +#endif // WEBRTC_SYSTEM_WRAPPERS_SOURCE_CPU_LINUX_H_ diff --git a/webrtc-sys/test/cpu/cpu_wrapper.h b/webrtc-sys/test/cpu/cpu_wrapper.h new file mode 100644 index 000000000..df3c20953 --- /dev/null +++ b/webrtc-sys/test/cpu/cpu_wrapper.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_SYSTEM_WRAPPERS_INTERFACE_CPU_WRAPPER_H_ +#define WEBRTC_SYSTEM_WRAPPERS_INTERFACE_CPU_WRAPPER_H_ + +#include + +namespace webrtc { +class CpuWrapper +{ +public: + static CpuWrapper* CreateCpu(); + virtual ~CpuWrapper() {} + + // Returns the average CPU usage for all processors. The CPU usage can be + // between and including 0 to 100 (%) + virtual int32_t CpuUsage() = 0; + virtual int32_t CpuUsage(int8_t* processName, + uint32_t length) = 0; + virtual int32_t CpuUsage(uint32_t dwProcessID) = 0; + + // The CPU usage per core is returned in cpu_usage. The CPU can be between + // and including 0 to 100 (%) + // Note that the pointer passed as cpu_usage is redirected to a local member + // of the CPU wrapper. + // numCores is the number of cores in the cpu_usage array. + // The return value is -1 for failure or 0-100, indicating the average + // CPU usage across all cores. + // Note: on some OSs this class is initialized lazy. This means that it + // might not yet be possible to retrieve any CPU metrics. When this happens + // the return value will be zero (indicating that there is not a failure), + // numCores will be 0 and cpu_usage will be set to NULL (indicating that + // no metrics are available yet). Once the initialization is completed, + // which can take in the order of seconds, CPU metrics can be retrieved. + virtual int32_t CpuUsageMultiCore(uint32_t& numCores, + uint32_t*& cpu_usage) = 0; + + virtual void Reset() = 0; + virtual void Stop() = 0; + + virtual int GetNumCores() = 0; + +protected: + CpuWrapper() {} +}; +} // namespace webrtc +#endif // WEBRTC_SYSTEM_WRAPPERS_INTERFACE_CPU_WRAPPER_H_ diff --git a/webrtc-sys/test/fileutils.cc b/webrtc-sys/test/fileutils.cc new file mode 100644 index 000000000..4e49980e5 --- /dev/null +++ b/webrtc-sys/test/fileutils.cc @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "fileutils.h" + +#ifdef WIN32 +#include +#define GET_CURRENT_DIR _getcwd +#else +#include +#define GET_CURRENT_DIR getcwd +#endif + +#include // To check for directory existence. +#ifndef S_ISDIR // Not defined in stat.h on Windows. +#define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) +#endif + +#include +#include + + +namespace webrtc { +namespace test { + +#ifdef WIN32 +static const char* kPathDelimiter = "\\"; +#else +static const char* kPathDelimiter = "/"; +#endif + +#ifdef WEBRTC_ANDROID +static const char* kRootDirName = "/sdcard/"; +static const char* kResourcesDirName = "resources"; +#else +// The file we're looking for to identify the project root dir. +static const char* kProjectRootFileName = "h264_benchmark"; +static const char* kOutputDirName = "out"; +static const char* kFallbackPath = "./"; +static const char* kResourcesDirName = "resources"; +#endif +const char* kCannotFindProjectRootDir = "ERROR_CANNOT_FIND_PROJECT_ROOT_DIR"; + +namespace { +char relative_dir_path[FILENAME_MAX]; +bool relative_dir_path_set = false; +} + +void SetExecutablePath(const std::string& path) { + std::string working_dir = WorkingDir(); + std::string temp_path = path; + + // Handle absolute paths; convert them to relative paths to the working dir. + if (path.find(working_dir) != std::string::npos) { + temp_path = path.substr(working_dir.length() + 1); + } + // Trim away the executable name; only store the relative dir path. + temp_path = temp_path.substr(0, temp_path.find_last_of(kPathDelimiter)); + strncpy(relative_dir_path, temp_path.c_str(), FILENAME_MAX); + relative_dir_path_set = true; +} + +bool FileExists(std::string& file_name) { + struct stat file_info = {0}; + return stat(file_name.c_str(), &file_info) == 0; +} + +#ifdef WEBRTC_ANDROID + +std::string ProjectRootPath() { + return kRootDirName; +} + +std::string OutputPath() { + return kRootDirName; +} + +std::string WorkingDir() { + return kRootDirName; +} + +#else // WEBRTC_ANDROID + +std::string ProjectRootPath() { + std::string path = WorkingDir(); + if (path == kFallbackPath) { + return kCannotFindProjectRootDir; + } + if (relative_dir_path_set) { + path = path + kPathDelimiter + relative_dir_path; + } + // Check for our file that verifies the root dir. + size_t path_delimiter_index = path.find_last_of(kPathDelimiter); + while (path_delimiter_index != std::string::npos) { + std::string root_filename = path + kPathDelimiter + kProjectRootFileName; + if (FileExists(root_filename)) { + return path + kPathDelimiter; + } + // Move up one directory in the directory tree. + path = path.substr(0, path_delimiter_index); + path_delimiter_index = path.find_last_of(kPathDelimiter); + } + // Reached the root directory. + fprintf(stderr, "Cannot find project root directory!\n"); + return kCannotFindProjectRootDir; +} + +std::string OutputPath() { + std::string path = ProjectRootPath(); + if (path == kCannotFindProjectRootDir) { + return kFallbackPath; + } + path += kOutputDirName; + if (!CreateDirectory(path)) { + return kFallbackPath; + } + return path + kPathDelimiter; +} + +std::string WorkingDir() { + char path_buffer[FILENAME_MAX]; + if (!GET_CURRENT_DIR(path_buffer, sizeof(path_buffer))) { + fprintf(stderr, "Cannot get current directory!\n"); + return kFallbackPath; + } else { + return std::string(path_buffer); + } +} + +#endif // !WEBRTC_ANDROID + +bool CreateDirectory(std::string directory_name) { + struct stat path_info = {0}; + // Check if the path exists already: + if (stat(directory_name.c_str(), &path_info) == 0) { + if (!S_ISDIR(path_info.st_mode)) { + fprintf(stderr, "Path %s exists but is not a directory! Remove this " + "file and re-run to create the directory.\n", + directory_name.c_str()); + return false; + } + } else { +#ifdef WIN32 + return _mkdir(directory_name.c_str()) == 0; +#else + return mkdir(directory_name.c_str(), S_IRWXU | S_IRWXG | S_IRWXO) == 0; +#endif + } + return true; +} + +std::string ResourcePath(std::string name, std::string extension) { + std::string platform = "win"; +#ifdef WEBRTC_LINUX + platform = "linux"; +#endif // WEBRTC_LINUX +#ifdef WEBRTC_MAC + platform = "mac"; +#endif // WEBRTC_MAC + +#ifdef WEBRTC_ARCH_64_BITS + std::string architecture = "64"; +#else + std::string architecture = "32"; +#endif // WEBRTC_ARCH_64_BITS + + std::string resources_path = ProjectRootPath() + kResourcesDirName + + kPathDelimiter; + std::string resource_file = resources_path + name + "_" + platform + "_" + + architecture + "." + extension; + if (FileExists(resource_file)) { + return resource_file; + } + // Try without architecture. + resource_file = resources_path + name + "_" + platform + "." + extension; + if (FileExists(resource_file)) { + return resource_file; + } + // Try without platform. + resource_file = resources_path + name + "_" + architecture + "." + extension; + if (FileExists(resource_file)) { + return resource_file; + } + + // Fall back on name without architecture or platform. + return resources_path + name + "." + extension; +} + +size_t GetFileSize(std::string filename) { + FILE* f = fopen(filename.c_str(), "rb"); + size_t size = 0; + if (f != NULL) { + if (fseek(f, 0, SEEK_END) == 0) { + size = ftell(f); + } + fclose(f); + } + return size; +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc-sys/test/fileutils.h b/webrtc-sys/test/fileutils.h new file mode 100644 index 000000000..e642a5f03 --- /dev/null +++ b/webrtc-sys/test/fileutils.h @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +// File utilities for testing purposes. +// +// The ProjectRootPath() method is a convenient way of getting an absolute +// path to the project source tree root directory. Using this, it is easy to +// refer to test resource files in a portable way. +// +// Notice that even if Windows platforms use backslash as path delimiter, it is +// also supported to use slash, so there's no need for #ifdef checks in test +// code for setting up the paths to the resource files. +// +// Example use: +// Assume we have the following code being used in a test source file: +// const std::string kInputFile = webrtc::test::ProjectRootPath() + +// "test/data/voice_engine/audio_long16.wav"; +// // Use the kInputFile for the tests... +// +// Then here's some example outputs for different platforms: +// Linux: +// * Source tree located in /home/user/webrtc/trunk +// * Test project located in /home/user/webrtc/trunk/src/testproject +// * Test binary compiled as: +// /home/user/webrtc/trunk/out/Debug/testproject_unittests +// Then ProjectRootPath() will return /home/user/webrtc/trunk/ no matter if +// the test binary is executed from standing in either of: +// /home/user/webrtc/trunk +// or +// /home/user/webrtc/trunk/out/Debug +// (or any other directory below the trunk for that matter). +// +// Windows: +// * Source tree located in C:\Users\user\webrtc\trunk +// * Test project located in C:\Users\user\webrtc\trunk\src\testproject +// * Test binary compiled as: +// C:\Users\user\webrtc\trunk\src\testproject\Debug\testproject_unittests.exe +// Then ProjectRootPath() will return C:\Users\user\webrtc\trunk\ when the +// test binary is executed from inside Visual Studio. +// It will also return the same path if the test is executed from a command +// prompt standing in C:\Users\user\webrtc\trunk\src\testproject\Debug +// +// Mac: +// * Source tree located in /Users/user/webrtc/trunk +// * Test project located in /Users/user/webrtc/trunk/src/testproject +// * Test binary compiled as: +// /Users/user/webrtc/trunk/xcodebuild/Debug/testproject_unittests +// Then ProjectRootPath() will return /Users/user/webrtc/trunk/ no matter if +// the test binary is executed from standing in either of: +// /Users/user/webrtc/trunk +// or +// /Users/user/webrtc/trunk/out/Debug +// (or any other directory below the trunk for that matter). + +#ifndef WEBRTC_TEST_TESTSUPPORT_FILEUTILS_H_ +#define WEBRTC_TEST_TESTSUPPORT_FILEUTILS_H_ + +#include + +namespace webrtc { +namespace test { + +// This is the "directory" returned if the ProjectPath() function fails +// to find the project root. +extern const char* kCannotFindProjectRootDir; + +// Finds the root dir of the project, to be able to set correct paths to +// resource files used by tests. +// The implementation is simple: it just looks for the file defined by +// kProjectRootFileName, starting in the current directory (the working +// directory) and then steps upward until it is found (or it is at the root of +// the file system). +// If the current working directory is above the project root dir, it will not +// be found. +// +// If symbolic links occur in the path they will be resolved and the actual +// directory will be returned. +// +// Returns the absolute path to the project root dir (usually the trunk dir) +// WITH a trailing path delimiter. +// If the project root is not found, the string specified by +// kCannotFindProjectRootDir is returned. +std::string ProjectRootPath(); + +// Creates and returns the absolute path to the output directory where log files +// and other test artifacts should be put. The output directory is generally a +// directory named "out" at the top-level of the project, i.e. a subfolder to +// the path returned by ProjectRootPath(). The exception is Android where we use +// /sdcard/ instead. +// +// Details described for ProjectRootPath() apply here too. +// +// Returns the path WITH a trailing path delimiter. If the project root is not +// found, the current working directory ("./") is returned as a fallback. +std::string OutputPath(); + +// Returns a path to a resource file for the currently executing platform. +// Adapts to what filenames are currently present in the +// [project-root]/resources/ dir. +// Returns an absolute path according to this priority list (the directory +// part of the path is left out for readability): +// 1. [name]_[platform]_[architecture].[extension] +// 2. [name]_[platform].[extension] +// 3. [name]_[architecture].[extension] +// 4. [name].[extension] +// Where +// * platform is either of "win", "mac" or "linux". +// * architecture is either of "32" or "64". +// +// Arguments: +// name - Name of the resource file. If a plain filename (no directory path) +// is supplied, the file is assumed to be located in resources/ +// If a directory path is prepended to the filename, a subdirectory +// hierarchy reflecting that path is assumed to be present. +// extension - File extension, without the dot, i.e. "bmp" or "yuv". +std::string ResourcePath(std::string name, std::string extension); + +// Gets the current working directory for the executing program. +// Returns "./" if for some reason it is not possible to find the working +// directory. +std::string WorkingDir(); + +// Creates a directory if it not already exists. +// Returns true if successful. Will print an error message to stderr and return +// false if a file with the same name already exists. +bool CreateDirectory(std::string directory_name); + +// File size of the supplied file in bytes. Will return 0 if the file is +// empty or if the file does not exist/is readable. +size_t GetFileSize(std::string filename); + +// Sets the executable path, i.e. the path to the executable that is being used +// when launching it. This is usually the path relative to the working directory +// but can also be an absolute path. The intention with this function is to pass +// the argv[0] being sent into the main function to make it possible for +// fileutils.h to find the correct project paths even when the working directory +// is outside the project tree (which happens in some cases). +void SetExecutablePath(const std::string& path_to_executable); + +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_TEST_TESTSUPPORT_FILEUTILS_H_ diff --git a/webrtc-sys/test/test_main.cc b/webrtc-sys/test/test_main.cc new file mode 100644 index 000000000..1623e6903 --- /dev/null +++ b/webrtc-sys/test/test_main.cc @@ -0,0 +1,20 @@ +//#include "benchmark_nvidia.h" +#include "benchmark_openh264.h" +#include "benchmark_vaapi.h" +#include "stdio.h" + +int main(int argc, char** argv) { + + std::vector benchmarks; + //benchmarks.push_back(new NvidiaBenchmark()); + benchmarks.push_back(new VaapiBenchmark()); + benchmarks.push_back(new OpenH264Benchmark()); + + for (auto benchmark : benchmarks) { + if (benchmark->IsSupported()) { + benchmark->Perform(); + } + } + + return 0; +} diff --git a/webrtc-sys/test/video_source.cc b/webrtc-sys/test/video_source.cc new file mode 100644 index 000000000..8273c4d85 --- /dev/null +++ b/webrtc-sys/test/video_source.cc @@ -0,0 +1,432 @@ +/* + * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "video_source.h" + +#include + +#include "fileutils.h" + +#define ASSERT_TRUE(condition) \ + do { \ + if (!(condition)) { \ + fprintf(stderr, "Assertion failed: %s\n", #condition); \ + abort(); \ + } \ + } while (0) + +VideoSource::VideoSource() +: +_fileName(webrtc::test::ProjectRootPath() + "resources/foreman_cif.yuv"), +_width(352), +_height(288), +_type(webrtc::VideoType::kI420), +_frameRate(30) +{ +} + +VideoSource::VideoSource(std::string fileName, VideoSize size, + int frameRate /*= 30*/, webrtc::VideoType type /*= webrtc::kI420*/) +: +_fileName(fileName), +_type(type), +_frameRate(frameRate) +{ + assert(size != kUndefined && size != kNumberOfVideoSizes); + assert(type != webrtc::VideoType::kUnknown); + assert(frameRate > 0); + if (GetWidthHeight(size, _width, _height) != 0) { + assert(false); + } +} + +VideoSource::VideoSource(std::string fileName, int width, int height, + int frameRate /*= 30*/, webrtc::VideoType type /*= webrtc::kI420*/) +: +_fileName(fileName), +_width(width), +_height(height), +_type(type), +_frameRate(frameRate) +{ + assert(width > 0); + assert(height > 0); + assert(type != webrtc::VideoType::kUnknown); + assert(frameRate > 0); +} + +VideoSize +VideoSource::GetSize() const +{ + return GetSize(_width, _height); +} + +VideoSize +VideoSource::GetSize(uint16_t width, uint16_t height) +{ + if(width == 128 && height == 96) + { + return kSQCIF; + }else if(width == 160 && height == 120) + { + return kQQVGA; + }else if(width == 176 && height == 144) + { + return kQCIF; + }else if(width == 320 && height == 240) + { + return kQVGA; + }else if(width == 352 && height == 288) + { + return kCIF; + }else if(width == 640 && height == 480) + { + return kVGA; + }else if(width == 720 && height == 480) + { + return kNTSC; + }else if(width == 704 && height == 576) + { + return k4CIF; + }else if(width == 800 && height == 600) + { + return kSVGA; + }else if(width == 960 && height == 720) + { + return kHD; + }else if(width == 1024 && height == 768) + { + return kXGA; + }else if(width == 1440 && height == 1080) + { + return kFullHD; + }else if(width == 400 && height == 240) + { + return kWQVGA; + }else if(width == 800 && height == 480) + { + return kWVGA; + }else if(width == 1280 && height == 720) + { + return kWHD; + }else if(width == 1920 && height == 1080) + { + return kWFullHD; + } + return kUndefined; +} + +unsigned int +VideoSource::GetFrameLength() const +{ + return webrtc::CalcBufferSize(_type, _width, _height); +} + +const char* +VideoSource::GetMySizeString() const +{ + return VideoSource::GetSizeString(GetSize()); +} + +const char* +VideoSource::GetSizeString(VideoSize size) +{ + switch (size) + { + case kSQCIF: + return "SQCIF"; + case kQQVGA: + return "QQVGA"; + case kQCIF: + return "QCIF"; + case kQVGA: + return "QVGA"; + case kCIF: + return "CIF"; + case kVGA: + return "VGA"; + case kNTSC: + return "NTSC"; + case k4CIF: + return "4CIF"; + case kSVGA: + return "SVGA"; + case kHD: + return "HD"; + case kXGA: + return "XGA"; + case kFullHD: + return "Full_HD"; + case kWQVGA: + return "WQVGA"; + case kWHD: + return "WHD"; + case kWFullHD: + return "WFull_HD"; + default: + return "Undefined"; + } +} + +std::string +VideoSource::GetFilePath() const +{ + size_t slashPos = _fileName.find_last_of("/\\"); + if (slashPos == std::string::npos) + { + return "."; + } + + return _fileName.substr(0, slashPos); +} + +std::string +VideoSource::GetName() const +{ + // Remove path. + size_t slashPos = _fileName.find_last_of("/\\"); + if (slashPos == std::string::npos) + { + slashPos = 0; + } + else + { + slashPos++; + } + + // Remove extension and underscored suffix if it exists. + return _fileName.substr(slashPos, std::min(_fileName.find_last_of("_"), + _fileName.find_last_of(".")) - slashPos); +} + +void +VideoSource::Convert(const VideoSource &target, bool force /* = false */) const +{ + // Ensure target rate is less than or equal to source + // (i.e. we are only temporally downsampling). + ASSERT_TRUE(target.GetFrameRate() <= _frameRate); + // Only supports YUV420 currently. + ASSERT_TRUE(_type == webrtc::VideoType::kI420 && target.GetType() == webrtc::VideoType::kI420); + if (!force && (FileExists(target.GetFileName().c_str()) || + (target.GetWidth() == _width && target.GetHeight() == _height && target.GetFrameRate() == _frameRate))) + { + // Assume that the filename uniquely defines the content. + // If the file already exists, it is the correct file. + return; + } + FILE *inFile = NULL; + FILE *outFile = NULL; + + inFile = fopen(_fileName.c_str(), "rb"); + ASSERT_TRUE(inFile != NULL); + + outFile = fopen(target.GetFileName().c_str(), "wb"); + ASSERT_TRUE(outFile != NULL); + + FrameDropper fd; + fd.SetFrameRate(target.GetFrameRate(), _frameRate); + + const size_t lengthOutFrame = webrtc::CalcBufferSize(target.GetType(), + target.GetWidth(), target.GetHeight()); + ASSERT_TRUE(lengthOutFrame > 0); + unsigned char *outFrame = new unsigned char[lengthOutFrame]; + + const size_t lengthInFrame = webrtc::CalcBufferSize(_type, _width, _height); + ASSERT_TRUE(lengthInFrame > 0); + unsigned char *inFrame = new unsigned char[lengthInFrame]; + + while (fread(inFrame, 1, lengthInFrame, inFile) == lengthInFrame) + { + if (!fd.DropFrame()) + { + ASSERT_TRUE(target.GetWidth() == _width && + target.GetHeight() == _height); + // Add video interpolator here! + if (fwrite(outFrame, 1, lengthOutFrame, + outFile) != lengthOutFrame) { + return; + } + } + } + + delete inFrame; + delete outFrame; + fclose(inFile); + fclose(outFile); +} + +bool VideoSource::FileExists(const char* fileName) +{ + FILE* fp = NULL; + fp = fopen(fileName, "rb"); + if(fp != NULL) + { + fclose(fp); + return true; + } + return false; +} + + +int +VideoSource::GetWidthHeight( VideoSize size, int & width, int& height) +{ + switch(size) + { + case kSQCIF: + width = 128; + height = 96; + return 0; + case kQQVGA: + width = 160; + height = 120; + return 0; + case kQCIF: + width = 176; + height = 144; + return 0; + case kCGA: + width = 320; + height = 200; + return 0; + case kQVGA: + width = 320; + height = 240; + return 0; + case kSIF: + width = 352; + height = 240; + return 0; + case kWQVGA: + width = 400; + height = 240; + return 0; + case kCIF: + width = 352; + height = 288; + return 0; + case kW288p: + width = 512; + height = 288; + return 0; + case k448p: + width = 576; + height = 448; + return 0; + case kVGA: + width = 640; + height = 480; + return 0; + case k432p: + width = 720; + height = 432; + return 0; + case kW432p: + width = 768; + height = 432; + return 0; + case k4SIF: + width = 704; + height = 480; + return 0; + case kW448p: + width = 768; + height = 448; + return 0; + case kNTSC: + width = 720; + height = 480; + return 0; + case kFW448p: + width = 800; + height = 448; + return 0; + case kWVGA: + width = 800; + height = 480; + return 0; + case k4CIF: + width = 704; + height = 576; + return 0; + case kSVGA: + width = 800; + height = 600; + return 0; + case kW544p: + width = 960; + height = 544; + return 0; + case kW576p: + width = 1024; + height = 576; + return 0; + case kHD: + width = 960; + height = 720; + return 0; + case kXGA: + width = 1024; + height = 768; + return 0; + case kFullHD: + width = 1440; + height = 1080; + return 0; + case kWHD: + width = 1280; + height = 720; + return 0; + case kWFullHD: + width = 1920; + height = 1080; + return 0; + default: + return -1; + } +} + +FrameDropper::FrameDropper() +: +_dropsBetweenRenders(0), +_frameCounter(0) +{ +} + +bool +FrameDropper::DropFrame() +{ + _frameCounter++; + if (_frameCounter > _dropsBetweenRenders) + { + _frameCounter = 0; + return false; + } + return true; +} + +unsigned int +FrameDropper::DropsBetweenRenders() +{ + return _dropsBetweenRenders; +} + +void +FrameDropper::SetFrameRate(double frameRate, double maxFrameRate) +{ + if (frameRate >= 1.0) + { + _dropsBetweenRenders = static_cast(maxFrameRate / frameRate + 0.5) - 1; + } + else + { + _dropsBetweenRenders = 0; + } +} diff --git a/webrtc-sys/test/video_source.h b/webrtc-sys/test/video_source.h new file mode 100644 index 000000000..c9da346fc --- /dev/null +++ b/webrtc-sys/test/video_source.h @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_VIDEO_CODING_CODECS_TEST_FRAMEWORK_VIDEO_SOURCE_H_ +#define WEBRTC_MODULES_VIDEO_CODING_CODECS_TEST_FRAMEWORK_VIDEO_SOURCE_H_ + +#include +#include "common_video/libyuv/include/webrtc_libyuv.h" + +enum VideoSize + { + kUndefined, + kSQCIF, // 128*96 = 12 288 + kQQVGA, // 160*120 = 19 200 + kQCIF, // 176*144 = 25 344 + kCGA, // 320*200 = 64 000 + kQVGA, // 320*240 = 76 800 + kSIF, // 352*240 = 84 480 + kWQVGA, // 400*240 = 96 000 + kCIF, // 352*288 = 101 376 + kW288p, // 512*288 = 147 456 (WCIF) + k448p, // 576*448 = 281 088 + kVGA, // 640*480 = 307 200 + k432p, // 720*432 = 311 040 + kW432p, // 768*432 = 331 776 + k4SIF, // 704*480 = 337 920 + kW448p, // 768*448 = 344 064 + kNTSC, // 720*480 = 345 600 + kFW448p, // 800*448 = 358 400 + kWVGA, // 800*480 = 384 000 + k4CIF, // 704�576 = 405 504 + kSVGA, // 800*600 = 480 000 + kW544p, // 960*544 = 522 240 + kW576p, // 1024*576 = 589 824 (W4CIF) + kHD, // 960*720 = 691 200 + kXGA, // 1024*768 = 786 432 + kWHD, // 1280*720 = 921 600 + kFullHD, // 1440*1080 = 1 555 200 + kWFullHD, // 1920*1080 = 2 073 600 + + kNumberOfVideoSizes + }; + +class VideoSource +{ +public: + VideoSource(); + VideoSource(std::string fileName, VideoSize size, int frameRate = 30, + webrtc::VideoType type = webrtc::VideoType::kI420); + VideoSource(std::string fileName, int width, int height, int frameRate = 30, + webrtc::VideoType type = webrtc::VideoType::kI420); + + std::string GetFileName() const { return _fileName; } + int GetWidth() const { return _width; } + int GetHeight() const { return _height; } + webrtc::VideoType GetType() const { return _type; } + int GetFrameRate() const { return _frameRate; } + + // Returns the file path without a trailing slash. + std::string GetFilePath() const; + + // Returns the filename with the path (including the leading slash) removed. + std::string GetName() const; + + VideoSize GetSize() const; + static VideoSize GetSize(uint16_t width, uint16_t height); + unsigned int GetFrameLength() const; + + // Returns a human-readable size string. + static const char* GetSizeString(VideoSize size); + const char* GetMySizeString() const; + + // Opens the video source, converting and writing to the specified target. + // If force is true, the conversion will be done even if the target file + // already exists. + void Convert(const VideoSource& target, bool force = false) const; + static bool FileExists(const char* fileName); +private: + static int GetWidthHeight( VideoSize size, int& width, int& height); + std::string _fileName; + int _width; + int _height; + webrtc::VideoType _type; + int _frameRate; +}; + +class FrameDropper +{ +public: + FrameDropper(); + bool DropFrame(); + unsigned int DropsBetweenRenders(); + void SetFrameRate(double frameRate, double maxFrameRate); + +private: + unsigned int _dropsBetweenRenders; + unsigned int _frameCounter; +}; + + +#endif // WEBRTC_MODULES_VIDEO_CODING_CODECS_TEST_FRAMEWORK_VIDEO_SOURCE_H_ + From 5f065216ac69df7307216473de568e667233d548 Mon Sep 17 00:00:00 2001 From: Spencer Bartholomew <38776747+spencerbart@users.noreply.github.com> Date: Mon, 25 Aug 2025 12:11:37 -0600 Subject: [PATCH 233/274] add attributes to Claims and AccessToken (#693) * add attributes to Claims and AccessToken * fix lint * make with_attributes more flexible * fix lint --- livekit-api/src/access_token.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/livekit-api/src/access_token.rs b/livekit-api/src/access_token.rs index 70cb73e93..1c9d18d0c 100644 --- a/livekit-api/src/access_token.rs +++ b/livekit-api/src/access_token.rs @@ -13,6 +13,7 @@ // limitations under the License. use std::{ + collections::HashMap, env, fmt::Debug, ops::Add, @@ -146,6 +147,7 @@ pub struct Claims { pub sip: SIPGrants, pub sha256: String, // Used to verify the integrity of the message body pub metadata: String, + pub attributes: HashMap, pub room_config: Option, } @@ -182,6 +184,7 @@ impl AccessToken { sip: SIPGrants::default(), sha256: Default::default(), metadata: Default::default(), + attributes: HashMap::new(), room_config: Default::default(), }, } @@ -229,6 +232,17 @@ impl AccessToken { self } + pub fn with_attributes(mut self, attributes: I) -> Self + where + I: IntoIterator, + K: Into, + V: Into, + { + self.claims.attributes = + attributes.into_iter().map(|(k, v)| (k.into(), v.into())).collect::>(); + self + } + pub fn with_sha256(mut self, sha256: &str) -> Self { self.claims.sha256 = sha256.to_owned(); self From e6ae54356a923137f62bf7925ab093751a7612f7 Mon Sep 17 00:00:00 2001 From: victor atasie <52110451+cs50victor@users.noreply.github.com> Date: Wed, 27 Aug 2025 13:36:36 -0700 Subject: [PATCH 234/274] fix: hardware rendering (#695) --- webrtc-sys/build.rs | 2 +- webrtc-sys/src/nvidia/h264_encoder_impl.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/webrtc-sys/build.rs b/webrtc-sys/build.rs index d95f519d0..4b897fd3f 100644 --- a/webrtc-sys/build.rs +++ b/webrtc-sys/build.rs @@ -166,7 +166,7 @@ fn main() { .file("src/vaapi/implib/libva.so.tramp.S"); builder - .flag("-I/usr/local/cuda-12.3/targets/x86_64-linux/include") + .flag("-I/usr/local/cuda/include") .flag("-Isrc/nvidia/NvCodec/include") .flag("-Isrc/nvidia/NvCodec/NvCodec") .file("src/nvidia/NvCodec/NvCodec/NvDecoder/NvDecoder.cpp") diff --git a/webrtc-sys/src/nvidia/h264_encoder_impl.cpp b/webrtc-sys/src/nvidia/h264_encoder_impl.cpp index 17ad27e92..f32e72a42 100644 --- a/webrtc-sys/src/nvidia/h264_encoder_impl.cpp +++ b/webrtc-sys/src/nvidia/h264_encoder_impl.cpp @@ -322,10 +322,10 @@ int32_t NvidiaH264EncoderImpl::Encode( if (cu_memory_type_ == CU_MEMORYTYPE_DEVICE) { NvEncoderCuda::CopyToDeviceFrame( - cu_context_, (void*)frame_buffer->DataY(), 0, + cu_context_, (void*)frame_buffer->DataY(), frame_buffer->StrideY(), reinterpret_cast(nv_enc_input_frame->inputPtr), nv_enc_input_frame->pitch, input_frame.width(), input_frame.height(), - CU_MEMORYTYPE_DEVICE, nv_enc_input_frame->bufferFormat, + CU_MEMORYTYPE_HOST, nv_enc_input_frame->bufferFormat, nv_enc_input_frame->chromaOffsets, nv_enc_input_frame->numChromaPlanes); } From 59eda4ac4a7d357ae29d85954c29d931ab5f89d2 Mon Sep 17 00:00:00 2001 From: David Chen Date: Tue, 2 Sep 2025 17:53:18 -0700 Subject: [PATCH 235/274] Add example for local audio streaming (#676) * initial commit * add params for identity and room * set default for playback * fix remote participant playback * update readme * fix * update readme * adding local audio example * cleanup readme * debug output * remove log * add ability to select channel * add room db meter and clean up * hooking up APM * Update cargo lock * Update cargo lock --------- Co-authored-by: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> --- examples/Cargo.lock | 2630 ++++++++++++-------- examples/Cargo.toml | 1 + examples/local_audio/Cargo.toml | 16 + examples/local_audio/README.md | 164 ++ examples/local_audio/src/audio_capture.rs | 117 + examples/local_audio/src/audio_mixer.rs | 124 + examples/local_audio/src/audio_playback.rs | 103 + examples/local_audio/src/db_meter.rs | 223 ++ examples/local_audio/src/main.rs | 753 ++++++ 9 files changed, 3064 insertions(+), 1067 deletions(-) create mode 100644 examples/local_audio/Cargo.toml create mode 100644 examples/local_audio/README.md create mode 100644 examples/local_audio/src/audio_capture.rs create mode 100644 examples/local_audio/src/audio_mixer.rs create mode 100644 examples/local_audio/src/audio_playback.rs create mode 100644 examples/local_audio/src/db_meter.rs create mode 100644 examples/local_audio/src/main.rs diff --git a/examples/Cargo.lock b/examples/Cargo.lock index 40cca5727..eab9084ad 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "ab_glyph" -version = "0.2.23" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80179d7dd5d7e8c285d67c4a1e652972a92de7475beddfb92028c76463b13225" +checksum = "e074464580a518d16a7126262fffaaa47af89d4099d4cb403f8ed938ba12ee7d" dependencies = [ "ab_glyph_rasterizer", "owned_ttf_parser", @@ -14,9 +14,9 @@ dependencies = [ [[package]] name = "ab_glyph_rasterizer" -version = "0.1.8" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" +checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" [[package]] name = "accesskit" @@ -30,19 +30,13 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.21.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "adler2" version = "2.0.1" @@ -51,9 +45,9 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aes" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", @@ -67,7 +61,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom 0.3.2", + "getrandom 0.3.3", "once_cell", "serde", "version_check", @@ -76,13 +70,35 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] +[[package]] +name = "alsa" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43" +dependencies = [ + "alsa-sys", + "bitflags 2.9.4", + "cfg-if", + "libc", +] + +[[package]] +name = "alsa-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527" +dependencies = [ + "libc", + "pkg-config", +] + [[package]] name = "android-activity" version = "0.6.0" @@ -90,18 +106,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" dependencies = [ "android-properties", - "bitflags 2.9.1", + "bitflags 2.9.4", "cc", "cesu8", "jni", "jni-sys", "libc", "log", - "ndk", + "ndk 0.9.0", "ndk-context", "ndk-sys 0.6.0+11769913", "num_enum", - "thiserror 1.0.51", + "thiserror 1.0.69", ] [[package]] @@ -118,9 +134,9 @@ checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" [[package]] name = "android_log-sys" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ecc8056bf6ab9892dcd53216c83d1597487d7dacac16c8df6b877d127df9937" +checksum = "84521a3cf562bc62942e294181d9eef17eb38ceb8c68677bc49f144e4c3d4f8d" [[package]] name = "android_logger" @@ -143,11 +159,61 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +dependencies = [ + "windows-sys 0.60.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.60.2", +] + [[package]] name = "anyhow" -version = "1.0.75" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" [[package]] name = "api" @@ -160,28 +226,29 @@ dependencies = [ [[package]] name = "arboard" -version = "3.3.0" +version = "3.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aafb29b107435aa276664c1db8954ac27a6e105cdad3c88287a199eb0e313c08" +checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf" dependencies = [ "clipboard-win", - "core-graphics 0.22.3", - "image 0.24.7", + "image 0.25.6", "log", - "objc", - "objc-foundation", - "objc_id", + "objc2 0.6.2", + "objc2-app-kit 0.3.1", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.1", "parking_lot", - "thiserror 1.0.51", - "winapi", - "x11rb 0.12.0", + "percent-encoding", + "windows-sys 0.60.2", + "x11rb", ] [[package]] name = "arrayref" -version = "0.3.7" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] name = "arrayvec" @@ -217,28 +284,27 @@ dependencies = [ [[package]] name = "async-channel" -version = "2.2.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" dependencies = [ "concurrent-queue", - "event-listener 5.2.0", - "event-listener-strategy 0.5.0", + "event-listener-strategy", "futures-core", "pin-project-lite", ] [[package]] name = "async-executor" -version = "1.8.0" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ae5ebefcc48e7452b4987947920dac9450be1110cadf34d1b8c116bdbaf97c" +checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8" dependencies = [ - "async-lock 3.3.0", "async-task", "concurrent-queue", - "fastrand 2.0.1", - "futures-lite 2.2.0", + "fastrand", + "futures-lite", + "pin-project-lite", "slab", ] @@ -248,71 +314,41 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" dependencies = [ - "async-channel 2.2.0", + "async-channel 2.5.0", "async-executor", - "async-io 2.3.1", - "async-lock 3.3.0", + "async-io", + "async-lock", "blocking", - "futures-lite 2.2.0", + "futures-lite", "once_cell", ] [[package]] name = "async-io" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" -dependencies = [ - "async-lock 2.8.0", - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-lite 1.13.0", - "log", - "parking", - "polling 2.8.0", - "rustix 0.37.27", - "slab", - "socket2 0.4.10", - "waker-fn", -] - -[[package]] -name = "async-io" -version = "2.3.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f97ab0c5b00a7cdbe5a371b9a782ee7be1316095885c8a4ea1daf490eb0ef65" +checksum = "19634d6336019ef220f09fd31168ce5c184b295cbf80345437cc36094ef223ca" dependencies = [ - "async-lock 3.3.0", + "async-lock", "cfg-if", "concurrent-queue", "futures-io", - "futures-lite 2.2.0", + "futures-lite", "parking", - "polling 3.4.0", - "rustix 0.38.44", + "polling", + "rustix 1.0.8", "slab", - "tracing", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] name = "async-lock" -version = "2.8.0" +version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" dependencies = [ - "event-listener 2.5.3", -] - -[[package]] -name = "async-lock" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" -dependencies = [ - "event-listener 4.0.3", - "event-listener-strategy 0.4.0", + "event-listener 5.4.1", + "event-listener-strategy", "pin-project-lite", ] @@ -324,25 +360,25 @@ checksum = "9343dc5acf07e79ff82d0c37899f079db3534d99f189a1837c8e549c99405bec" dependencies = [ "futures-util", "native-tls", - "thiserror 1.0.51", + "thiserror 1.0.69", "url", ] [[package]] name = "async-std" -version = "1.12.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" +checksum = "2c8e079a4ab67ae52b7403632e4618815d6db36d2a010cfe41b02c1b1578f93b" dependencies = [ "async-channel 1.9.0", "async-global-executor", - "async-io 1.13.0", - "async-lock 2.8.0", + "async-io", + "async-lock", "crossbeam-utils", "futures-channel", "futures-core", "futures-io", - "futures-lite 1.13.0", + "futures-lite", "gloo-timers", "kv-log-macro", "log", @@ -356,26 +392,26 @@ dependencies = [ [[package]] name = "async-task" -version = "4.7.0" +version = "4.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.74" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.106", ] [[package]] name = "async-tungstenite" -version = "0.25.0" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef0f8d64ef9351752fbe5462f242c625d9c4910d2bc3f7ec44c43857ca123f5d" +checksum = "2cca750b12e02c389c1694d35c16539f88b8bbaa5945934fdc1b41a776688589" dependencies = [ "async-native-tls", "async-std", @@ -394,9 +430,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.1.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "axum" @@ -409,7 +445,7 @@ dependencies = [ "bitflags 1.3.2", "bytes", "futures-util", - "http 0.2.11", + "http 0.2.12", "http-body", "hyper", "itoa", @@ -435,7 +471,7 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http 0.2.11", + "http 0.2.12", "http-body", "mime", "rustversion", @@ -445,30 +481,36 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", - "miniz_oxide 0.7.1", + "miniz_oxide", "object", "rustc-demangle", + "windows-targets 0.52.6", ] [[package]] name = "base64" -version = "0.21.5" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" [[package]] name = "basic_room" @@ -481,6 +523,24 @@ dependencies = [ "tokio", ] +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.9.4", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "proc-macro2", + "quote", + "regex", + "rustc-hash 2.1.1", + "shlex", + "syn 2.0.106", +] + [[package]] name = "bit-set" version = "0.8.0" @@ -498,9 +558,9 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bit_field" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" [[package]] name = "bitflags" @@ -510,9 +570,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" dependencies = [ "serde", ] @@ -543,18 +603,15 @@ dependencies = [ [[package]] name = "blocking" -version = "1.5.1" +version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" dependencies = [ - "async-channel 2.2.0", - "async-lock 3.3.0", + "async-channel 2.5.0", "async-task", - "fastrand 2.0.1", "futures-io", - "futures-lite 2.2.0", + "futures-lite", "piper", - "tracing", ] [[package]] @@ -569,28 +626,28 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytemuck" -version = "1.23.1" +version = "1.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" +checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.5.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1" +checksum = "4f154e572231cb6ba2bd1176980827e3d5dc04cc183a75dea38109fbdd672d29" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.106", ] [[package]] @@ -623,12 +680,11 @@ dependencies = [ [[package]] name = "bzip2-sys" -version = "0.1.11+1.0.8" +version = "0.1.13+1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" dependencies = [ "cc", - "libc", "pkg-config", ] @@ -638,12 +694,12 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "log", - "polling 3.4.0", + "polling", "rustix 0.38.44", "slab", - "thiserror 1.0.51", + "thiserror 1.0.69", ] [[package]] @@ -660,12 +716,14 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.83" +version = "1.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "590f9024a68a8c40351881787f1934dc11afd69090f5edb6831464694d836ea3" dependencies = [ + "find-msvc-tools", "jobserver", "libc", + "shlex", ] [[package]] @@ -674,11 +732,20 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "cfg_aliases" @@ -688,16 +755,16 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "wasm-bindgen", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -710,15 +777,64 @@ dependencies = [ "inout", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "4.5.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "clap_lex" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" + [[package]] name = "clipboard-win" -version = "4.5.0" +version = "5.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362" +checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" dependencies = [ "error-code", - "str-buf", - "winapi", ] [[package]] @@ -728,7 +844,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" dependencies = [ "termcolor", - "unicode-width", + "unicode-width 0.1.14", ] [[package]] @@ -739,7 +855,7 @@ checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81" dependencies = [ "serde", "termcolor", - "unicode-width", + "unicode-width 0.2.1", ] [[package]] @@ -748,11 +864,17 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + [[package]] name = "combine" -version = "4.6.6" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" dependencies = [ "bytes", "memchr", @@ -760,9 +882,9 @@ dependencies = [ [[package]] name = "concurrent-queue" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ "crossbeam-utils", ] @@ -832,112 +954,134 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "core-graphics" -version = "0.22.3" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" dependencies = [ "bitflags 1.3.2", "core-foundation 0.9.4", "core-graphics-types", - "foreign-types 0.3.2", + "foreign-types 0.5.0", "libc", ] [[package]] -name = "core-graphics" -version = "0.23.2" +name = "core-graphics-types" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" dependencies = [ "bitflags 1.3.2", "core-foundation 0.9.4", - "core-graphics-types", - "foreign-types 0.5.0", "libc", ] [[package]] -name = "core-graphics-types" -version = "0.1.3" +name = "coreaudio-rs" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +checksum = "321077172d79c662f64f5071a03120748d5bb652f5231570141be24cfcd2bace" dependencies = [ "bitflags 1.3.2", - "core-foundation 0.9.4", + "core-foundation-sys", + "coreaudio-sys", +] + +[[package]] +name = "coreaudio-sys" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ceec7a6067e62d6f931a2baf6f3a751f4a892595bcec1461a3c94ef9949864b6" +dependencies = [ + "bindgen", +] + +[[package]] +name = "cpal" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "873dab07c8f743075e57f524c583985fbaf745602acbe916a01539364369a779" +dependencies = [ + "alsa", + "core-foundation-sys", + "coreaudio-rs", + "dasp_sample", + "jni", + "js-sys", "libc", + "mach2", + "ndk 0.8.0", + "ndk-context", + "oboe", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows 0.54.0", ] [[package]] name = "cpufeatures" -version = "0.2.11" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] name = "crc32fast" -version = "1.3.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam-channel" -version = "0.5.9" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c3242926edf34aec4ac3a77108ad4854bffaa2e4ddc1824124ce59231302d5" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" dependencies = [ - "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fca89a0e215bab21874660c67903c5f143333cab1da83d041c7ded6053774751" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ - "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.16" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d2fe95351b870527a5d09bf563ed3c97c0cffb87cf1c78a591bf48bb218d9aa" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "autocfg", - "cfg-if", "crossbeam-utils", - "memoffset 0.9.0", ] [[package]] name = "crossbeam-utils" -version = "0.8.17" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d96137f14f244c37f989d9fff8f95e6c18b918e71f36638f8c49112e4c78f" -dependencies = [ - "cfg-if", -] +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-common" @@ -957,59 +1101,83 @@ checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" [[package]] name = "cxx" -version = "1.0.111" +version = "1.0.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9fc0c733f71e58dedf4f034cd2a266f80b94cc9ed512729e1798651b68c2cba" +checksum = "e5ba77f286ce5c44c7ba02de894b057bc0a605a210e3d81fa83b92d94586c0e1" dependencies = [ "cc", + "cxxbridge-cmd", "cxxbridge-flags", "cxxbridge-macro", + "foldhash 0.2.0", "link-cplusplus", ] [[package]] name = "cxx-build" -version = "1.0.111" +version = "1.0.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51bc81d2664db24cf1d35405f66e18a85cffd4d49ab930c71a5c6342a410f38c" +checksum = "0c56fdf6fba27288d1fda3384062692e66dc40ca41bafd15f616dd4e8b0ac909" dependencies = [ "cc", - "codespan-reporting 0.11.1", - "once_cell", + "codespan-reporting 0.12.0", + "indexmap 2.11.0", "proc-macro2", "quote", "scratch", - "syn 2.0.100", + "syn 2.0.106", +] + +[[package]] +name = "cxxbridge-cmd" +version = "1.0.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ade5eb6d6e6ef9c5631eff7e4f74e0e7109140e775f124d76904c0e5e6a202" +dependencies = [ + "clap", + "codespan-reporting 0.12.0", + "indexmap 2.11.0", + "proc-macro2", + "quote", + "syn 2.0.106", ] [[package]] name = "cxxbridge-flags" -version = "1.0.111" +version = "1.0.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8511afbe34ea242697784da5cb2c5d4a0afb224ca8b136bdf93bfe180cbe5884" +checksum = "99f99fe2f3f76a2ba40c5431f854efe3725c19a89f4d59966bca3ec561be940e" [[package]] name = "cxxbridge-macro" -version = "1.0.111" +version = "1.0.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6888cd161769d65134846d4d4981d5a6654307cc46ec83fb917e530aea5f84" +checksum = "2b6e5fa0545804d2d8d398a1e995203a1f2403a9f0651d50546462e61a28340e" dependencies = [ + "indexmap 2.11.0", "proc-macro2", "quote", - "syn 2.0.100", + "rustversion", + "syn 2.0.106", ] +[[package]] +name = "dasp_sample" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" + [[package]] name = "data-encoding" -version = "2.5.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" [[package]] name = "deranged" -version = "0.3.10" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eb30d70a07a3b04884d2677f06bec33509dc67ca60d92949e5535352d3191dc" +checksum = "d630bccd429a5bb5a64b5e94f693bfc48c9f8566418fda4c494cc94f911f87cc" dependencies = [ "powerfmt", ] @@ -1031,6 +1199,27 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" +[[package]] +name = "dispatch2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +dependencies = [ + "bitflags 2.9.4", + "objc2 0.6.2", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "dlib" version = "0.5.2" @@ -1051,9 +1240,9 @@ dependencies = [ [[package]] name = "downcast-rs" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] name = "dpi" @@ -1089,7 +1278,7 @@ dependencies = [ "js-sys", "log", "objc2 0.5.2", - "objc2-app-kit", + "objc2-app-kit 0.2.2", "objc2-foundation 0.2.2", "parking_lot", "percent-encoding", @@ -1117,7 +1306,7 @@ checksum = "25dd34cec49ab55d85ebf70139cb1ccd29c977ef6b6ba4fe85489d6877ee9ef3" dependencies = [ "accesskit", "ahash", - "bitflags 2.9.1", + "bitflags 2.9.4", "emath", "epaint", "log", @@ -1140,7 +1329,7 @@ dependencies = [ "epaint", "log", "profiling", - "thiserror 1.0.51", + "thiserror 1.0.69", "type-map", "web-time", "wgpu 24.0.5", @@ -1169,9 +1358,9 @@ dependencies = [ [[package]] name = "either" -version = "1.9.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "emath" @@ -1185,29 +1374,29 @@ dependencies = [ [[package]] name = "encoding_rs" -version = "0.8.33" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] [[package]] name = "enumn" -version = "0.1.12" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ad8cef1d801a4686bfd8919f0b30eac4c8e48968c437a6405ded4fb5272d2b" +checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.106", ] [[package]] name = "env_logger" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" dependencies = [ "humantime", "is-terminal", @@ -1243,9 +1432,9 @@ checksum = "fc7e7a64c02cf7a5b51e745a9e45f60660a286f151c238b9d397b3e923f5082f" [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" @@ -1254,18 +1443,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] name = "error-code" -version = "2.3.1" +version = "3.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21" -dependencies = [ - "libc", - "str-buf", -] +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" [[package]] name = "event-listener" @@ -1275,20 +1460,9 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "event-listener" -version = "4.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener" -version = "5.2.0" +version = "5.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b5fb89194fa3cad959b833185b3063ba881dbfc7030680b314250779fb4cc91" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" dependencies = [ "concurrent-queue", "parking", @@ -1297,21 +1471,11 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.4.0" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ - "event-listener 4.0.3", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feedafcaa9b749175d5ac357452a9d41ea2911da598fde46ce1fe02c37751291" -dependencies = [ - "event-listener 5.2.0", + "event-listener 5.4.1", "pin-project-lite", ] @@ -1324,7 +1488,7 @@ dependencies = [ "bit_field", "half", "lebe", - "miniz_oxide 0.8.9", + "miniz_oxide", "rayon-core", "smallvec", "zune-inflate", @@ -1332,18 +1496,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] - -[[package]] -name = "fastrand" -version = "2.0.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fdeflate" @@ -1354,6 +1509,12 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e178e4fba8a2726903f6ba98a6d221e76f9c12c650d5dc0e6afdc50677b49650" + [[package]] name = "fixedbitset" version = "0.4.2" @@ -1362,12 +1523,12 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.28" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" dependencies = [ "crc32fast", - "miniz_oxide 0.7.1", + "miniz_oxide", ] [[package]] @@ -1382,6 +1543,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "foreign-types" version = "0.3.2" @@ -1409,7 +1576,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.106", ] [[package]] @@ -1426,9 +1593,9 @@ checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] @@ -1445,9 +1612,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.29" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -1460,9 +1627,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.29" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -1470,15 +1637,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.29" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.29" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -1487,32 +1654,17 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" - -[[package]] -name = "futures-lite" -version = "1.13.0" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" -dependencies = [ - "fastrand 1.9.0", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" -version = "2.2.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445ba825b27408685aaecefd65178908c36c6e96aaf6d8599419d46e624192ba" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" dependencies = [ - "fastrand 2.0.1", + "fastrand", "futures-core", "futures-io", "parking", @@ -1521,32 +1673,32 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.29" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.106", ] [[package]] name = "futures-sink" -version = "0.3.29" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.29" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.29" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -1572,54 +1724,44 @@ dependencies = [ [[package]] name = "gethostname" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb65d4ba3173c56a500b555b532f72c42e8d1fe64962b518897f8959fae2c177" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "gethostname" -version = "0.4.3" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" +checksum = "fc257fdb4038301ce4b9cd1b3b51704509692bb3ff716a410cbd07925d9dae55" dependencies = [ - "libc", - "windows-targets 0.48.5", + "rustix 1.0.8", + "windows-targets 0.52.6", ] [[package]] name = "getrandom" -version = "0.2.11" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "wasi 0.14.3+wasi-0.2.4", ] [[package]] name = "gif" -version = "0.12.0" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" +checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b" dependencies = [ "color_quant", "weezl", @@ -1627,9 +1769,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.1" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "gl_generator" @@ -1644,15 +1786,15 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "gloo-timers" -version = "0.2.6" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" dependencies = [ "futures-channel", "futures-core", @@ -1687,7 +1829,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "gpu-alloc-types", ] @@ -1697,7 +1839,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", ] [[package]] @@ -1708,8 +1850,8 @@ checksum = "c151a2a5ef800297b4e79efa4f4bec035c5f51d5ae587287c9b952bdf734cacd" dependencies = [ "log", "presser", - "thiserror 1.0.51", - "windows", + "thiserror 1.0.69", + "windows 0.58.0", ] [[package]] @@ -1718,9 +1860,9 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "gpu-descriptor-types", - "hashbrown 0.15.4", + "hashbrown 0.15.5", ] [[package]] @@ -1729,22 +1871,22 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", ] [[package]] name = "h2" -version = "0.3.22" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" dependencies = [ "bytes", "fnv", "futures-core", "futures-sink", "futures-util", - "http 0.2.11", - "indexmap 2.10.0", + "http 0.2.12", + "indexmap 2.11.0", "slab", "tokio", "tokio-util", @@ -1770,11 +1912,11 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.15.4" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "foldhash", + "foldhash 0.1.5", ] [[package]] @@ -1783,7 +1925,7 @@ version = "7.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d" dependencies = [ - "base64", + "base64 0.21.7", "byteorder", "flate2", "nom", @@ -1804,9 +1946,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.3.3" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "hexf-parse" @@ -1825,18 +1967,18 @@ dependencies = [ [[package]] name = "home" -version = "0.5.9" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "http" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes", "fnv", @@ -1845,9 +1987,9 @@ dependencies = [ [[package]] name = "http" -version = "1.1.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" dependencies = [ "bytes", "fnv", @@ -1861,15 +2003,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", - "http 0.2.11", + "http 0.2.12", "pin-project-lite", ] [[package]] name = "httparse" -version = "1.8.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "httpdate" @@ -1879,28 +2021,28 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "humantime" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" [[package]] name = "hyper" -version = "0.14.28" +version = "0.14.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" dependencies = [ "bytes", "futures-channel", "futures-core", "futures-util", "h2", - "http 0.2.11", + "http 0.2.12", "http-body", "httparse", "httpdate", "itoa", "pin-project-lite", - "socket2 0.5.5", + "socket2 0.5.10", "tokio", "tower-service", "tracing", @@ -1914,7 +2056,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", - "http 0.2.11", + "http 0.2.12", "hyper", "rustls", "tokio", @@ -1948,16 +2090,17 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.61" +version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", + "log", "wasm-bindgen", - "windows-core 0.52.0", + "windows-core 0.61.2", ] [[package]] @@ -1969,21 +2112,118 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + [[package]] name = "idna" -version = "0.5.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", ] [[package]] name = "image" -version = "0.24.7" +version = "0.24.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f3dfdbdd72063086ff443e297b61695500514b1e41095b6fb9a5ab48a70a711" +checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" dependencies = [ "bytemuck", "byteorder", @@ -1991,7 +2231,6 @@ dependencies = [ "exr", "gif", "jpeg-decoder", - "num-rational", "num-traits", "png", "qoi", @@ -2008,6 +2247,7 @@ dependencies = [ "byteorder-lite", "num-traits", "png", + "tiff", ] [[package]] @@ -2022,60 +2262,57 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" dependencies = [ "equivalent", - "hashbrown 0.15.4", + "hashbrown 0.15.5", ] [[package]] name = "inout" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ "generic-array", ] [[package]] -name = "instant" -version = "0.1.12" +name = "io-uring" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" dependencies = [ + "bitflags 2.9.4", "cfg-if", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi", "libc", - "windows-sys 0.48.0", ] [[package]] name = "ipnet" -version = "2.9.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "is-terminal" -version = "0.4.9" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ "hermit-abi", - "rustix 0.38.44", - "windows-sys 0.48.0", + "libc", + "windows-sys 0.59.0", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itertools" version = "0.10.5" @@ -2094,11 +2331,29 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jni" @@ -2111,7 +2366,7 @@ dependencies = [ "combine", "jni-sys", "log", - "thiserror 1.0.51", + "thiserror 1.0.69", "walkdir", "windows-sys 0.45.0", ] @@ -2124,18 +2379,19 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.27" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ + "getrandom 0.3.3", "libc", ] [[package]] name = "jpeg-decoder" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" +checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07" dependencies = [ "rayon", ] @@ -2152,11 +2408,11 @@ dependencies = [ [[package]] name = "jsonwebtoken" -version = "9.2.0" +version = "9.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7ea04a7c5c055c175f189b6dc6ba036fd62306b58c66c9f6389036c503a3f4" +checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" dependencies = [ - "base64", + "base64 0.22.1", "js-sys", "ring", "serde", @@ -2191,9 +2447,9 @@ dependencies = [ [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "lebe" @@ -2203,18 +2459,18 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "libc" -version = "0.2.171" +version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "libloading" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.53.3", ] [[package]] @@ -2225,13 +2481,13 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" -version = "0.0.2" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607" +checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "libc", - "redox_syscall", + "redox_syscall 0.5.17", ] [[package]] @@ -2248,7 +2504,7 @@ dependencies = [ "parking_lot", "serde", "serde_json", - "thiserror 1.0.51", + "thiserror 1.0.69", "tokio", "wasm-bindgen", "wasm-bindgen-futures", @@ -2258,34 +2514,40 @@ dependencies = [ [[package]] name = "link-cplusplus" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d240c6f7e1ba3a28b0249f774e6a9dd0175054b52dfbb61b16eb8505c3785c9" +checksum = "8c349c75e1ab4a03bd6b33fe6cbd3c479c5dd443e44ad732664d72cb0e755475" dependencies = [ "cc", ] [[package]] name = "linux-raw-sys" -version = "0.3.8" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.4.15" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "litrs" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" +checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" [[package]] name = "livekit" -version = "0.7.15" +version = "0.7.16" dependencies = [ "bmrng", "bytes", @@ -2299,37 +2561,37 @@ dependencies = [ "livekit-runtime", "log", "parking_lot", - "prost 0.12.3", + "prost 0.12.6", "semver", "serde", "serde_json", - "thiserror 1.0.51", + "thiserror 1.0.69", "tokio", ] [[package]] name = "livekit-api" -version = "0.4.4" +version = "0.4.5" dependencies = [ "async-tungstenite", - "base64", + "base64 0.21.7", "futures-util", - "http 0.2.11", + "http 0.2.12", "jsonwebtoken", "livekit-protocol", "livekit-runtime", "log", "parking_lot", "pbjson-types", - "prost 0.12.3", - "rand 0.9.0", + "prost 0.12.6", + "rand 0.9.2", "reqwest", "rustls-native-certs", "scopeguard", "serde", "serde_json", "sha2", - "thiserror 1.0.51", + "thiserror 1.0.69", "tokio", "tokio-rustls", "tokio-tungstenite", @@ -2345,10 +2607,10 @@ dependencies = [ "parking_lot", "pbjson", "pbjson-types", - "prost 0.12.3", - "prost-types 0.12.3", + "prost 0.12.6", + "prost-types 0.12.6", "serde", - "thiserror 1.0.51", + "thiserror 1.0.69", "tokio", ] @@ -2360,11 +2622,27 @@ dependencies = [ "tokio-stream", ] +[[package]] +name = "local_audio" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "cpal", + "env_logger", + "futures-util", + "libwebrtc", + "livekit", + "livekit-api", + "log", + "tokio", +] + [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", @@ -2372,13 +2650,22 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" dependencies = [ "value-bag", ] +[[package]] +name = "mach2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +dependencies = [ + "libc", +] + [[package]] name = "malloc_buf" version = "0.0.6" @@ -2390,11 +2677,11 @@ dependencies = [ [[package]] name = "matchers" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" dependencies = [ - "regex-automata 0.1.10", + "regex-automata", ] [[package]] @@ -2405,44 +2692,26 @@ checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] name = "memchr" -version = "2.6.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "memmap2" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "483758ad303d734cec05e5c12b41d7e93e6a6390c5e9dae6bdeb7c1259012d28" +checksum = "843a98750cd611cc2965a8213b53b43e715f13c37a9e096c6408e69990961db7" dependencies = [ "libc", ] -[[package]] -name = "memoffset" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" -dependencies = [ - "autocfg", -] - -[[package]] -name = "memoffset" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" -dependencies = [ - "autocfg", -] - [[package]] name = "metal" version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f569fb946490b5743ad69813cb19629130ce9374034abe31614a36402d18f99e" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "block", "core-graphics-types", "foreign-types 0.5.0", @@ -2463,15 +2732,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "miniz_oxide" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" -dependencies = [ - "adler", -] - [[package]] name = "miniz_oxide" version = "0.8.9" @@ -2484,13 +2744,13 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.10" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.48.0", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", ] [[package]] @@ -2508,9 +2768,9 @@ dependencies = [ [[package]] name = "multimap" -version = "0.8.3" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" [[package]] name = "naga" @@ -2520,17 +2780,17 @@ checksum = "e380993072e52eef724eddfcde0ed013b0c023c3f0417336ed041aa9f076994e" dependencies = [ "arrayvec", "bit-set", - "bitflags 2.9.1", + "bitflags 2.9.4", "cfg_aliases", "codespan-reporting 0.11.1", "hexf-parse", - "indexmap 2.10.0", + "indexmap 2.11.0", "log", - "rustc-hash", + "rustc-hash 1.1.0", "spirv", "strum", "termcolor", - "thiserror 2.0.12", + "thiserror 2.0.16", "unicode-xid", ] @@ -2542,30 +2802,29 @@ checksum = "2b977c445f26e49757f9aca3631c3b8b836942cb278d69a92e7b80d3b24da632" dependencies = [ "arrayvec", "bit-set", - "bitflags 2.9.1", + "bitflags 2.9.4", "cfg_aliases", "codespan-reporting 0.12.0", "half", - "hashbrown 0.15.4", + "hashbrown 0.15.5", "hexf-parse", - "indexmap 2.10.0", + "indexmap 2.11.0", "log", "num-traits", "once_cell", - "rustc-hash", + "rustc-hash 1.1.0", "spirv", "strum", - "thiserror 2.0.12", + "thiserror 2.0.16", "unicode-ident", ] [[package]] name = "native-tls" -version = "0.2.11" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" dependencies = [ - "lazy_static", "libc", "log", "openssl", @@ -2577,19 +2836,33 @@ dependencies = [ "tempfile", ] +[[package]] +name = "ndk" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" +dependencies = [ + "bitflags 2.9.4", + "jni-sys", + "log", + "ndk-sys 0.5.0+25.2.9519653", + "num_enum", + "thiserror 1.0.69", +] + [[package]] name = "ndk" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "jni-sys", "log", "ndk-sys 0.6.0+11769913", "num_enum", "raw-window-handle", - "thiserror 1.0.51", + "thiserror 1.0.69", ] [[package]] @@ -2616,18 +2889,6 @@ dependencies = [ "jni-sys", ] -[[package]] -name = "nix" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" -dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "libc", - "memoffset 0.7.1", -] - [[package]] name = "nohash-hasher" version = "0.2.0" @@ -2645,46 +2906,32 @@ dependencies = [ ] [[package]] -name = "num-integer" -version = "0.1.45" +name = "num-conv" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" -dependencies = [ - "autocfg", - "num-traits", -] +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] -name = "num-rational" -version = "0.4.1" +name = "num-derive" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ - "autocfg", - "num-integer", - "num-traits", + "proc-macro2", + "quote", + "syn 2.0.106", ] [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", ] -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "num_enum" version = "0.7.4" @@ -2704,7 +2951,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.106", ] [[package]] @@ -2716,17 +2963,6 @@ dependencies = [ "malloc_buf", ] -[[package]] -name = "objc-foundation" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" -dependencies = [ - "block", - "objc", - "objc_id", -] - [[package]] name = "objc-sys" version = "0.3.5" @@ -2745,9 +2981,9 @@ dependencies = [ [[package]] name = "objc2" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88c6597e14493ab2e44ce58f2fdecf095a51f12ca57bec060a11c57332520551" +checksum = "561f357ba7f3a2a61563a186a163d0a3a5247e1089524a3981d49adb775078bc" dependencies = [ "objc2-encode", ] @@ -2758,7 +2994,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "block2", "libc", "objc2 0.5.2", @@ -2768,13 +3004,25 @@ dependencies = [ "objc2-quartz-core", ] +[[package]] +name = "objc2-app-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" +dependencies = [ + "bitflags 2.9.4", + "objc2 0.6.2", + "objc2-core-graphics", + "objc2-foundation 0.3.1", +] + [[package]] name = "objc2-cloud-kit" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "block2", "objc2 0.5.2", "objc2-core-location", @@ -2798,12 +3046,36 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "block2", "objc2 0.5.2", "objc2-foundation 0.2.2", ] +[[package]] +name = "objc2-core-foundation" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" +dependencies = [ + "bitflags 2.9.4", + "dispatch2", + "objc2 0.6.2", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4" +dependencies = [ + "bitflags 2.9.4", + "dispatch2", + "objc2 0.6.2", + "objc2-core-foundation", + "objc2-io-surface", +] + [[package]] name = "objc2-core-image" version = "0.2.2" @@ -2840,7 +3112,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "block2", "dispatch", "libc", @@ -2853,8 +3125,20 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" dependencies = [ - "bitflags 2.9.1", - "objc2 0.6.1", + "bitflags 2.9.4", + "objc2 0.6.2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c" +dependencies = [ + "bitflags 2.9.4", + "objc2 0.6.2", + "objc2-core-foundation", ] [[package]] @@ -2865,7 +3149,7 @@ checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" dependencies = [ "block2", "objc2 0.5.2", - "objc2-app-kit", + "objc2-app-kit 0.2.2", "objc2-foundation 0.2.2", ] @@ -2875,7 +3159,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "block2", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -2887,7 +3171,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "block2", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -2910,7 +3194,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "block2", "objc2 0.5.2", "objc2-cloud-kit", @@ -2942,7 +3226,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "block2", "objc2 0.5.2", "objc2-core-location", @@ -2950,21 +3234,35 @@ dependencies = [ ] [[package]] -name = "objc_id" -version = "0.1.1" +name = "object" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ - "objc", + "memchr", ] [[package]] -name = "object" -version = "0.32.1" +name = "oboe" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +checksum = "e8b61bebd49e5d43f5f8cc7ee2891c16e0f41ec7954d36bcb6c14c5e0de867fb" dependencies = [ - "memchr", + "jni", + "ndk 0.8.0", + "ndk-context", + "num-derive", + "num-traits", + "oboe-sys", +] + +[[package]] +name = "oboe-sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8bb09a4a2b1d668170cfe0a7d5bc103f8999fb316c98099b6a9939c9f2e79d" +dependencies = [ + "cc", ] [[package]] @@ -2973,13 +3271,19 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + [[package]] name = "openssl" -version = "0.10.61" +version = "0.10.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b8419dc8cc6d866deb801274bba2e6f8f6108c1bb7fcc10ee5ab864931dbb45" +checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "cfg-if", "foreign-types 0.3.2", "libc", @@ -2996,20 +3300,20 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.106", ] [[package]] name = "openssl-probe" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.97" +version = "0.9.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3eaad34cdd97d81de97964fc7f29e2d104f483840d906ef56daa1912338460b" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" dependencies = [ "cc", "libc", @@ -3019,9 +3323,9 @@ dependencies = [ [[package]] name = "orbclient" -version = "0.3.47" +version = "0.3.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f0d54bde9774d3a51dcf281a5def240c71996bc6ca05d2c847ec8b2b216166" +checksum = "ba0b26cec2e24f08ed8bb31519a9333140a6599b867dac464bb150bdb796fd43" dependencies = [ "libredox", ] @@ -3037,24 +3341,24 @@ dependencies = [ [[package]] name = "owned_ttf_parser" -version = "0.20.0" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4586edfe4c648c71797a74c84bacb32b52b212eff5dfe2bb9f2c599844023e7" +checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b" dependencies = [ "ttf-parser", ] [[package]] name = "parking" -version = "2.2.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", @@ -3062,18 +3366,18 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "backtrace", "cfg-if", "libc", "petgraph", - "redox_syscall", + "redox_syscall 0.5.17", "smallvec", "thread-id", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -3099,7 +3403,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1030c719b0ec2a2d25a5df729d6cff1acf3cc230bf766f4f97833591f7577b90" dependencies = [ - "base64", + "base64 0.21.7", "serde", ] @@ -3111,8 +3415,8 @@ checksum = "2580e33f2292d34be285c5bc3dba5259542b083cfad6037b6d70345f24dcb735" dependencies = [ "heck 0.4.1", "itertools 0.11.0", - "prost 0.12.3", - "prost-types 0.12.3", + "prost 0.12.6", + "prost-types 0.12.6", ] [[package]] @@ -3125,7 +3429,7 @@ dependencies = [ "chrono", "pbjson", "pbjson-build", - "prost 0.12.3", + "prost 0.12.6", "prost-build", "serde", ] @@ -3144,45 +3448,45 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "petgraph" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.10.0", + "indexmap 2.11.0", ] [[package]] name = "pin-project" -version = "1.1.3" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.3" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.106", ] [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -3192,20 +3496,20 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "piper" -version = "0.2.1" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" dependencies = [ "atomic-waker", - "fastrand 2.0.1", + "fastrand", "futures-io", ] [[package]] name = "pkg-config" -version = "0.3.27" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "png" @@ -3217,37 +3521,21 @@ dependencies = [ "crc32fast", "fdeflate", "flate2", - "miniz_oxide 0.8.9", + "miniz_oxide", ] [[package]] name = "polling" -version = "2.8.0" +version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" -dependencies = [ - "autocfg", - "bitflags 1.3.2", - "cfg-if", - "concurrent-queue", - "libc", - "log", - "pin-project-lite", - "windows-sys 0.48.0", -] - -[[package]] -name = "polling" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30054e72317ab98eddd8561db0f6524df3367636884b7b21b703e4b280a84a14" +checksum = "b5bd19146350fe804f7cb2669c851c03d69da628803dab0d98018142aaa5d829" dependencies = [ "cfg-if", "concurrent-queue", + "hermit-abi", "pin-project-lite", - "rustix 0.38.44", - "tracing", - "windows-sys 0.52.0", + "rustix 1.0.8", + "windows-sys 0.60.2", ] [[package]] @@ -3262,6 +3550,15 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +[[package]] +name = "potential_utf" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" +dependencies = [ + "zerovec", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -3270,9 +3567,12 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] [[package]] name = "presser" @@ -3282,29 +3582,28 @@ checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" [[package]] name = "prettyplease" -version = "0.2.15" +version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.100", + "syn 2.0.106", ] [[package]] name = "proc-macro-crate" -version = "1.3.1" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" dependencies = [ - "once_cell", "toml_edit", ] [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] @@ -3327,34 +3626,33 @@ dependencies = [ [[package]] name = "prost" -version = "0.12.3" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" dependencies = [ "bytes", - "prost-derive 0.12.3", + "prost-derive 0.12.6", ] [[package]] name = "prost-build" -version = "0.12.3" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c55e02e35260070b6f716a2423c2ff1c3bb1642ddca6f99e1f26d06268a0e2d2" +checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" dependencies = [ "bytes", - "heck 0.4.1", - "itertools 0.11.0", + "heck 0.5.0", + "itertools 0.12.1", "log", "multimap", "once_cell", "petgraph", "prettyplease", - "prost 0.12.3", - "prost-types 0.12.3", + "prost 0.12.6", + "prost-types 0.12.6", "regex", - "syn 2.0.100", + "syn 2.0.106", "tempfile", - "which", ] [[package]] @@ -3372,15 +3670,15 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.12.3" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", - "itertools 0.11.0", + "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.106", ] [[package]] @@ -3394,11 +3692,11 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.12.3" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "193898f59edcf43c26227dcd4c8427f00d99d61e95dcde58dabd49fa291d470e" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" dependencies = [ - "prost 0.12.3", + "prost 0.12.6", ] [[package]] @@ -3430,9 +3728,9 @@ dependencies = [ [[package]] name = "r-efi" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rand" @@ -3447,13 +3745,12 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.0" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", - "zerocopy", ] [[package]] @@ -3482,7 +3779,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.11", + "getrandom 0.2.16", ] [[package]] @@ -3491,14 +3788,14 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", ] [[package]] name = "range-alloc" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8a99fddc9f0ba0a85884b8d14e3592853e787d581ca1816c91349b10e4eeab" +checksum = "c3d6831663a5098ea164f89cff59c6284e95f4e3c76ce9848d4529f5ccca9bde" [[package]] name = "raw-window-handle" @@ -3508,9 +3805,9 @@ checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" [[package]] name = "rayon" -version = "1.8.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" dependencies = [ "either", "rayon-core", @@ -3518,9 +3815,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -3536,48 +3833,42 @@ dependencies = [ ] [[package]] -name = "regex" -version = "1.10.2" +name = "redox_syscall" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ - "aho-corasick", - "memchr", - "regex-automata 0.4.3", - "regex-syntax 0.8.2", + "bitflags 2.9.4", ] [[package]] -name = "regex-automata" -version = "0.1.10" +name = "regex" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" dependencies = [ - "regex-syntax 0.6.29", + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", ] [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.2", + "regex-syntax", ] [[package]] name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - -[[package]] -name = "regex-syntax" -version = "0.8.2" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" [[package]] name = "renderdoc-sys" @@ -3587,17 +3878,17 @@ checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" [[package]] name = "reqwest" -version = "0.11.23" +version = "0.11.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" dependencies = [ - "base64", + "base64 0.21.7", "bytes", "encoding_rs", "futures-core", "futures-util", "h2", - "http 0.2.11", + "http 0.2.12", "http-body", "hyper", "hyper-rustls", @@ -3616,6 +3907,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", + "sync_wrapper", "system-configuration", "tokio", "tokio-native-tls", @@ -3631,16 +3923,16 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.7" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", - "getrandom 0.2.11", + "cfg-if", + "getrandom 0.2.16", "libc", - "spin", "untrusted", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -3649,8 +3941,8 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ - "base64", - "bitflags 2.9.1", + "base64 0.21.7", + "bitflags 2.9.4", "serde", "serde_derive", ] @@ -3670,9 +3962,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustc-hash" @@ -3680,38 +3972,43 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustix" -version = "0.37.27" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.9.4", "errno", - "io-lifetimes", "libc", - "linux-raw-sys 0.3.8", - "windows-sys 0.48.0", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", ] [[package]] name = "rustix" -version = "0.38.44" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "errno", "libc", - "linux-raw-sys 0.4.15", - "windows-sys 0.52.0", + "linux-raw-sys 0.9.4", + "windows-sys 0.60.2", ] [[package]] name = "rustls" -version = "0.21.10" +version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", "ring", @@ -3737,7 +4034,7 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64", + "base64 0.21.7", ] [[package]] @@ -3752,15 +4049,15 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.14" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.16" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "same-file" @@ -3784,11 +4081,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.22" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -3805,9 +4102,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "scratch" -version = "1.0.7" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152" +checksum = "d68f2ec51b097e4c1a75b681a8bec621909b5e91f15bb7b840c4f2f7b01148b2" [[package]] name = "sct" @@ -3834,11 +4131,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.9.2" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.9.4", "core-foundation 0.9.4", "core-foundation-sys", "libc", @@ -3847,9 +4144,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.1" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" dependencies = [ "core-foundation-sys", "libc", @@ -3857,37 +4154,38 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.20" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" [[package]] name = "serde" -version = "1.0.193" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.193" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.106", ] [[package]] name = "serde_json" -version = "1.0.108" +version = "1.0.143" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] @@ -3917,9 +4215,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", @@ -3935,11 +4233,17 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" dependencies = [ "libc", ] @@ -3952,12 +4256,9 @@ checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "slab" -version = "0.4.9" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "slotmap" @@ -3970,9 +4271,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.2" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "smithay-client-toolkit" @@ -3980,7 +4281,7 @@ version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "calloop", "calloop-wayland-source", "cursor-icon", @@ -3988,7 +4289,7 @@ dependencies = [ "log", "memmap2", "rustix 0.38.44", - "thiserror 1.0.51", + "thiserror 1.0.69", "wayland-backend", "wayland-client", "wayland-csd-frame", @@ -4021,50 +4322,44 @@ dependencies = [ [[package]] name = "socket2" -version = "0.4.10" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ "libc", - "winapi", + "windows-sys 0.52.0", ] [[package]] name = "socket2" -version = "0.5.5" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - [[package]] name = "spirv" version = "0.3.0+sdk-1.3.268.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", ] [[package]] -name = "static_assertions" -version = "1.1.0" +name = "stable_deref_trait" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] -name = "str-buf" -version = "1.0.6" +name = "static_assertions" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "strict-num" @@ -4072,6 +4367,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "strum" version = "0.26.3" @@ -4091,14 +4392,14 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.100", + "syn 2.0.106", ] [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" @@ -4113,9 +4414,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.100" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -4128,6 +4429,17 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "system-configuration" version = "0.5.1" @@ -4151,15 +4463,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.8.1" +version = "3.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e" dependencies = [ - "cfg-if", - "fastrand 2.0.1", - "redox_syscall", - "rustix 0.38.44", - "windows-sys 0.48.0", + "fastrand", + "getrandom 0.3.3", + "once_cell", + "rustix 1.0.8", + "windows-sys 0.60.2", ] [[package]] @@ -4173,49 +4485,49 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.51" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl 1.0.51", + "thiserror-impl 1.0.69", ] [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl 2.0.16", ] [[package]] name = "thiserror-impl" -version = "1.0.51" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.106", ] [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.106", ] [[package]] name = "thread-id" -version = "4.2.1" +version = "4.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0ec81c46e9eb50deaa257be2f148adf052d1fb7701cfd55ccfab2525280b70b" +checksum = "cfe8f25bbdd100db7e1d34acf7fd2dc59c4bf8f7483f505eaa7d4f12f76cc0ea" dependencies = [ "libc", "winapi", @@ -4223,19 +4535,18 @@ dependencies = [ [[package]] name = "thread_local" -version = "1.1.7" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ "cfg-if", - "once_cell", ] [[package]] name = "tiff" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d172b0f4d3fba17ba89811858b9d3d97f928aece846475bbda076ca46736211" +checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" dependencies = [ "flate2", "jpeg-decoder", @@ -4244,11 +4555,12 @@ dependencies = [ [[package]] name = "time" -version = "0.3.31" +version = "0.3.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" +checksum = "83bde6f1ec10e72d583d91623c939f623002284ef622b87de38cfd546cbf2031" dependencies = [ "deranged", + "num-conv", "powerfmt", "serde", "time-core", @@ -4256,9 +4568,9 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" [[package]] name = "tiny-skia" @@ -4286,45 +4598,41 @@ dependencies = [ ] [[package]] -name = "tinyvec" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" +name = "tinystr" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] [[package]] name = "tokio" -version = "1.35.1" +version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", "bytes", + "io-uring", "libc", "mio", - "num_cpus", "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.5", + "slab", + "socket2 0.6.0", "tokio-macros", "tracing", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] name = "tokio-io-timeout" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +checksum = "0bd86198d9ee903fedd2f9a2e72014287c0d9167e4ae43b5853007205dda1b76" dependencies = [ "pin-project-lite", "tokio", @@ -4332,13 +4640,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.106", ] [[package]] @@ -4363,9 +4671,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.14" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" dependencies = [ "futures-core", "pin-project-lite", @@ -4392,31 +4700,30 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.10" +version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", - "tracing", ] [[package]] name = "toml_datetime" -version = "0.6.5" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" [[package]] name = "toml_edit" -version = "0.19.15" +version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.10.0", + "indexmap 2.11.0", "toml_datetime", "winnow", ] @@ -4429,12 +4736,12 @@ checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a" dependencies = [ "async-trait", "axum", - "base64", + "base64 0.21.7", "bytes", "futures-core", "futures-util", "h2", - "http 0.2.11", + "http 0.2.12", "http-body", "hyper", "hyper-timeout", @@ -4471,21 +4778,21 @@ dependencies = [ [[package]] name = "tower-layer" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -4494,20 +4801,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.106", ] [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", "valuable", @@ -4515,14 +4822,14 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" dependencies = [ "matchers", "once_cell", "parking_lot", - "regex", + "regex-automata", "sharded-slab", "thread_local", "tracing", @@ -4537,9 +4844,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "ttf-parser" -version = "0.20.0" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" [[package]] name = "tungstenite" @@ -4550,14 +4857,14 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http 0.2.11", + "http 0.2.12", "httparse", "log", "native-tls", "rand 0.8.5", "rustls", "sha1", - "thiserror 1.0.51", + "thiserror 1.0.69", "url", "utf-8", ] @@ -4571,52 +4878,37 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http 1.1.0", + "http 1.3.1", "httparse", "log", "native-tls", "rand 0.8.5", "sha1", - "thiserror 1.0.51", + "thiserror 1.0.69", "url", "utf-8", ] [[package]] name = "type-map" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deb68604048ff8fa93347f02441e4487594adc20bb8a084f9e564d2b827a0a9f" +checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90" dependencies = [ - "rustc-hash", + "rustc-hash 2.1.1", ] [[package]] name = "typenum" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" - -[[package]] -name = "unicode-bidi" -version = "0.3.14" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "unicode-ident" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" - -[[package]] -name = "unicode-normalization" -version = "0.1.22" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" -dependencies = [ - "tinyvec", -] +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-segmentation" @@ -4626,9 +4918,15 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-width" -version = "0.1.11" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" [[package]] name = "unicode-xid" @@ -4644,13 +4942,14 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.0" +version = "2.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] [[package]] @@ -4659,17 +4958,29 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "valuable" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "value-bag" -version = "1.7.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "126e423afe2dd9ac52142e7e9d5ce4135d7e13776c529d27fd6bc49f19e3280b" +checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5" [[package]] name = "vcpkg" @@ -4679,21 +4990,15 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "waker-fn" -version = "1.1.1" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "walkdir" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", @@ -4710,17 +5015,17 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" -version = "0.14.2+wasi-0.2.4" +version = "0.14.3+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "6a51ae83037bdd272a9e28ce236db8c07016dd0d50c27038b3f407533c030c95" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen", ] [[package]] @@ -4745,7 +5050,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.106", "wasm-bindgen-shared", ] @@ -4780,7 +5085,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.106", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4796,13 +5101,13 @@ dependencies = [ [[package]] name = "wayland-backend" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe770181423e5fc79d3e2a7f4410b7799d5aab1de4372853de3c6aa13ca24121" +checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35" dependencies = [ "cc", "downcast-rs", - "rustix 0.38.44", + "rustix 1.0.8", "scoped-tls", "smallvec", "wayland-sys", @@ -4810,12 +5115,12 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.31.10" +version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978fa7c67b0847dbd6a9f350ca2569174974cd4082737054dbb7fbb79d7d9a61" +checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d" dependencies = [ - "bitflags 2.9.1", - "rustix 0.38.44", + "bitflags 2.9.4", + "rustix 1.0.8", "wayland-backend", "wayland-scanner", ] @@ -4826,29 +5131,29 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "cursor-icon", "wayland-backend", ] [[package]] name = "wayland-cursor" -version = "0.31.10" +version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a65317158dec28d00416cb16705934070aef4f8393353d41126c54264ae0f182" +checksum = "447ccc440a881271b19e9989f75726d60faa09b95b0200a9b7eb5cc47c3eeb29" dependencies = [ - "rustix 0.38.44", + "rustix 1.0.8", "wayland-client", "xcursor", ] [[package]] name = "wayland-protocols" -version = "0.32.8" +version = "0.32.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "779075454e1e9a521794fed15886323ea0feda3f8b0fc1390f5398141310422a" +checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "wayland-backend", "wayland-client", "wayland-scanner", @@ -4856,11 +5161,11 @@ dependencies = [ [[package]] name = "wayland-protocols-plasma" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fd38cdad69b56ace413c6bcc1fbf5acc5e2ef4af9d5f8f1f9570c0c83eae175" +checksum = "a07a14257c077ab3279987c4f8bb987851bf57081b93710381daea94f2c2c032" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "wayland-backend", "wayland-client", "wayland-protocols", @@ -4869,11 +5174,11 @@ dependencies = [ [[package]] name = "wayland-protocols-wlr" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cb6cdc73399c0e06504c437fe3cf886f25568dd5454473d565085b36d6a8bbf" +checksum = "efd94963ed43cf9938a090ca4f7da58eb55325ec8200c3848963e98dc25b78ec" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "wayland-backend", "wayland-client", "wayland-protocols", @@ -4882,9 +5187,9 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.31.6" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "896fdafd5d28145fce7958917d69f2fd44469b1d4e861cb5961bcbeebc6d1484" +checksum = "54cb1e9dc49da91950bdfd8b848c49330536d9d1fb03d4bfec8cae50caa50ae3" dependencies = [ "proc-macro2", "quick-xml", @@ -4893,9 +5198,9 @@ dependencies = [ [[package]] name = "wayland-sys" -version = "0.31.6" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbcebb399c77d5aa9fa5db874806ee7b4eba4e73650948e8f93963f128896615" +checksum = "34949b42822155826b41db8e5d0c1be3a2bd296c747577a43a3e6daefc296142" dependencies = [ "dlib", "log", @@ -4933,7 +5238,7 @@ dependencies = [ "jni", "log", "ndk-context", - "objc2 0.6.1", + "objc2 0.6.2", "objc2-foundation 0.3.1", "url", "web-sys", @@ -4950,9 +5255,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.25.3" +version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "webrtc-sys" @@ -4981,9 +5286,9 @@ dependencies = [ [[package]] name = "weezl" -version = "0.1.7" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" +checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" [[package]] name = "wgpu" @@ -4992,7 +5297,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b0b3436f0729f6cdf2e6e9201f3d39dc95813fad61d826c1ed07918b4539353" dependencies = [ "arrayvec", - "bitflags 2.9.1", + "bitflags 2.9.4", "cfg_aliases", "document-features", "js-sys", @@ -5018,10 +5323,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec8fb398f119472be4d80bc3647339f56eb63b2a331f6a3d16e25d8144197dd9" dependencies = [ "arrayvec", - "bitflags 2.9.1", + "bitflags 2.9.4", "cfg_aliases", "document-features", - "hashbrown 0.15.4", + "hashbrown 0.15.5", "js-sys", "log", "naga 25.0.1", @@ -5047,19 +5352,19 @@ checksum = "7f0aa306497a238d169b9dc70659105b4a096859a34894544ca81719242e1499" dependencies = [ "arrayvec", "bit-vec", - "bitflags 2.9.1", + "bitflags 2.9.4", "cfg_aliases", "document-features", - "indexmap 2.10.0", + "indexmap 2.11.0", "log", "naga 24.0.0", "once_cell", "parking_lot", "profiling", "raw-window-handle", - "rustc-hash", + "rustc-hash 1.1.0", "smallvec", - "thiserror 2.0.12", + "thiserror 2.0.16", "wgpu-hal 24.0.4", "wgpu-types 24.0.0", ] @@ -5073,11 +5378,11 @@ dependencies = [ "arrayvec", "bit-set", "bit-vec", - "bitflags 2.9.1", + "bitflags 2.9.4", "cfg_aliases", "document-features", - "hashbrown 0.15.4", - "indexmap 2.10.0", + "hashbrown 0.15.5", + "indexmap 2.11.0", "log", "naga 25.0.1", "once_cell", @@ -5085,9 +5390,9 @@ dependencies = [ "portable-atomic", "profiling", "raw-window-handle", - "rustc-hash", + "rustc-hash 1.1.0", "smallvec", - "thiserror 2.0.12", + "thiserror 2.0.16", "wgpu-core-deps-apple", "wgpu-core-deps-emscripten", "wgpu-core-deps-windows-linux-android", @@ -5131,7 +5436,7 @@ dependencies = [ "android_system_properties", "arrayvec", "ash", - "bitflags 2.9.1", + "bitflags 2.9.4", "block", "bytemuck", "cfg_aliases", @@ -5155,13 +5460,13 @@ dependencies = [ "profiling", "raw-window-handle", "renderdoc-sys", - "rustc-hash", + "rustc-hash 1.1.0", "smallvec", - "thiserror 2.0.12", + "thiserror 2.0.16", "wasm-bindgen", "web-sys", "wgpu-types 24.0.0", - "windows", + "windows 0.58.0", ] [[package]] @@ -5174,7 +5479,7 @@ dependencies = [ "arrayvec", "ash", "bit-set", - "bitflags 2.9.1", + "bitflags 2.9.4", "block", "bytemuck", "cfg-if", @@ -5185,7 +5490,7 @@ dependencies = [ "gpu-alloc", "gpu-allocator", "gpu-descriptor", - "hashbrown 0.15.4", + "hashbrown 0.15.5", "js-sys", "khronos-egl", "libc", @@ -5203,11 +5508,11 @@ dependencies = [ "raw-window-handle", "renderdoc-sys", "smallvec", - "thiserror 2.0.12", + "thiserror 2.0.16", "wasm-bindgen", "web-sys", "wgpu-types 25.0.0", - "windows", + "windows 0.58.0", "windows-core 0.58.0", ] @@ -5217,7 +5522,7 @@ version = "24.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50ac044c0e76c03a0378e7786ac505d010a873665e2d51383dcff8dd227dc69c" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "js-sys", "log", "web-sys", @@ -5229,11 +5534,11 @@ version = "25.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2aa49460c2a8ee8edba3fca54325540d904dd85b2e086ada762767e17d06e8bc" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "bytemuck", "js-sys", "log", - "thiserror 2.0.12", + "thiserror 2.0.16", "web-sys", ] @@ -5247,7 +5552,7 @@ dependencies = [ "egui-wgpu", "env_logger", "futures", - "image 0.24.7", + "image 0.24.9", "livekit", "log", "parking_lot", @@ -5257,18 +5562,6 @@ dependencies = [ "winit", ] -[[package]] -name = "which" -version = "4.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" -dependencies = [ - "either", - "home", - "once_cell", - "rustix 0.38.44", -] - [[package]] name = "winapi" version = "0.3.9" @@ -5287,20 +5580,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" -dependencies = [ - "winapi", -] - -[[package]] -name = "winapi-wsapoll" -version = "0.1.1" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c17110f57155602a80dca10be03852116403c9ff3cd25b079d666f2aa3df6e" +checksum = "0978bf7171b3d90bac376700cb56d606feb40f251a475a5d6634613564460b22" dependencies = [ - "winapi", + "windows-sys 0.60.2", ] [[package]] @@ -5309,6 +5593,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" +dependencies = [ + "windows-core 0.54.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows" version = "0.58.0" @@ -5321,10 +5615,11 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.52.0" +version = "0.54.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" dependencies = [ + "windows-result 0.1.2", "windows-targets 0.52.6", ] @@ -5334,13 +5629,26 @@ version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" dependencies = [ - "windows-implement", - "windows-interface", - "windows-result", - "windows-strings", + "windows-implement 0.58.0", + "windows-interface 0.58.0", + "windows-result 0.2.0", + "windows-strings 0.1.0", "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement 0.60.0", + "windows-interface 0.59.1", + "windows-link", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + [[package]] name = "windows-implement" version = "0.58.0" @@ -5349,7 +5657,18 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.106", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", ] [[package]] @@ -5360,7 +5679,33 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.106", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -5372,16 +5717,34 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-strings" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" dependencies = [ - "windows-result", + "windows-result 0.2.0", "windows-targets 0.52.6", ] +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -5418,6 +5781,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.3", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -5457,13 +5829,30 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -5482,6 +5871,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -5500,6 +5895,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -5518,12 +5919,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -5542,6 +5955,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -5560,6 +5979,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -5578,6 +6003,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -5596,38 +6027,44 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "winit" -version = "0.30.11" +version = "0.30.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4409c10174df8779dc29a4788cac85ed84024ccbc1743b776b21a520ee1aaf4" +checksum = "c66d4b9ed69c4009f6321f762d6e61ad8a2389cd431b97cb1e146812e9e6c732" dependencies = [ "ahash", "android-activity", "atomic-waker", - "bitflags 2.9.1", + "bitflags 2.9.4", "block2", "bytemuck", "calloop", "cfg_aliases", "concurrent-queue", "core-foundation 0.9.4", - "core-graphics 0.23.2", + "core-graphics", "cursor-icon", "dpi", "js-sys", "libc", "memmap2", - "ndk", + "ndk 0.9.0", "objc2 0.5.2", - "objc2-app-kit", + "objc2-app-kit 0.2.2", "objc2-foundation 0.2.2", "objc2-ui-kit", "orbclient", "percent-encoding", "pin-project", "raw-window-handle", - "redox_syscall", + "redox_syscall 0.4.1", "rustix 0.38.44", "sctk-adwaita", "smithay-client-toolkit", @@ -5644,15 +6081,15 @@ dependencies = [ "web-time", "windows-sys 0.52.0", "x11-dl", - "x11rb 0.13.1", + "x11rb", "xkbcommon-dl", ] [[package]] name = "winnow" -version = "0.5.30" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b5c3db89721d50d0e2a673f5043fc4722f76dcc352d7b1ab8b8288bed4ed2c5" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" dependencies = [ "memchr", ] @@ -5668,13 +6105,16 @@ dependencies = [ ] [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +name = "wit-bindgen" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags 2.9.1", -] +checksum = "052283831dbae3d879dc7f51f3d92703a316ca49f91540417d38591826127814" + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] name = "x11-dl" @@ -5689,52 +6129,30 @@ dependencies = [ [[package]] name = "x11rb" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1641b26d4dec61337c35a1b1aaf9e3cba8f46f0b43636c609ab0291a648040a" -dependencies = [ - "gethostname 0.3.0", - "nix", - "winapi", - "winapi-wsapoll", - "x11rb-protocol 0.12.0", -] - -[[package]] -name = "x11rb" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" dependencies = [ "as-raw-xcb-connection", - "gethostname 0.4.3", + "gethostname", "libc", "libloading", "once_cell", - "rustix 0.38.44", - "x11rb-protocol 0.13.1", -] - -[[package]] -name = "x11rb-protocol" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82d6c3f9a0fb6701fab8f6cea9b0c0bd5d6876f1f89f7fada07e558077c344bc" -dependencies = [ - "nix", + "rustix 1.0.8", + "x11rb-protocol", ] [[package]] name = "x11rb-protocol" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" [[package]] name = "xcursor" -version = "0.3.5" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a0ccd7b4a5345edfcd0c3535718a4e9ff7798ffc536bb5b5a0e26ff84732911" +checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b" [[package]] name = "xkbcommon-dl" @@ -5742,7 +6160,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.4", "dlib", "log", "once_cell", @@ -5757,28 +6175,106 @@ checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" [[package]] name = "xml-rs" -version = "0.8.19" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fd8403733700263c6eb89f192880191f1b83e332f7a20371ddcf421c4a337c7" + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", + "synstructure", +] [[package]] name = "zerocopy" -version = "0.8.24" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.24" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.106", ] [[package]] @@ -5822,9 +6318,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.9+zstd.1.5.5" +version = "2.0.15+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" dependencies = [ "cc", "pkg-config", diff --git a/examples/Cargo.toml b/examples/Cargo.toml index c1336ef69..200bc0228 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ "basic_room", + "local_audio", "mobile", "save_to_disk", "wgpu_room", diff --git a/examples/local_audio/Cargo.toml b/examples/local_audio/Cargo.toml new file mode 100644 index 000000000..407995b43 --- /dev/null +++ b/examples/local_audio/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "local_audio" +version = "0.1.0" +edition = "2021" + +[dependencies] +tokio = { version = "1", features = ["full"] } +env_logger = "0.10" +livekit = { path = "../../livekit", features = ["rustls-tls-native-roots"]} +livekit-api = { path = "../../livekit-api", features = ["rustls-tls-native-roots"]} +libwebrtc = { path = "../../libwebrtc" } +log = "0.4" +cpal = "0.15" +anyhow = "1.0" +clap = { version = "4.0", features = ["derive"] } +futures-util = "0.3" \ No newline at end of file diff --git a/examples/local_audio/README.md b/examples/local_audio/README.md new file mode 100644 index 000000000..e0b4d496a --- /dev/null +++ b/examples/local_audio/README.md @@ -0,0 +1,164 @@ +# Local Audio Capture Example + +This example demonstrates how to capture audio from a local microphone and stream it to a LiveKit room while simultaneously playing back audio from other participants. It provides a complete bidirectional audio experience with real-time level monitoring. + +## Features + +- **Bidirectional Audio**: Capture from local microphone and play back remote participants +- **Device Selection**: Choose specific input/output devices or use system defaults +- **Real-time Level Meter**: Visual dB meter showing local microphone levels +- **Audio Processing**: Echo cancellation, noise suppression, and auto gain control (enabled by default) +- **Volume Control**: Adjustable playback volume for remote participants +- **Audio Mixing**: Combines audio from multiple remote participants +- **Format Support**: Handles F32, I16, and U16 sample formats +- **Cross-platform**: Works on Windows, macOS, and Linux + +## Prerequisites + +1. **Rust**: Install Rust 1.70+ from [rustup.rs](https://rustup.rs/) +2. **LiveKit Server**: Access to a LiveKit server instance +3. **Audio Devices**: Working microphone and speakers/headphones +4. **System Permissions**: Audio device access permissions + +### Platform-specific Requirements + +- **macOS**: Grant microphone permissions in System Preferences → Privacy & Security → Microphone +- **Windows**: Ensure audio drivers are installed and microphone is not in use by other applications +- **Linux**: May need ALSA or PulseAudio libraries (`sudo apt install libasound2-dev` on Ubuntu/Debian) + +## Setup + +1. **LiveKit Connection Details** (choose one method): + + **Option A: Environment Variables** + ```bash + export LIVEKIT_URL="wss://your-livekit-server.com" + export LIVEKIT_API_KEY="your-api-key" + export LIVEKIT_API_SECRET="your-api-secret" + ``` + + **Option B: CLI Arguments** + Pass connection details directly to the command (see examples below) + + **Note**: CLI arguments take precedence over environment variables. You can mix both methods - for example, set API credentials via environment variables but override the URL via CLI. + +2. **Build the Example**: + +```bash +cd examples/local_audio +cargo build --release +``` + +## Usage + +### List Available Audio Devices + +```bash +cargo run -- --list-devices +``` + +Example output: +``` +Available Input Devices: +─────────────────────────────────────────────────────────────── +1. MacBook Pro Microphone + ├─ Sample Rate: 8000-48000 Hz + ├─ Channels: 1-2 + └─ Formats: F32, I16 + +2. USB Microphone + ├─ Sample Rate: 44100-48000 Hz + ├─ Channels: 1-2 + └─ Formats: F32, I16 + +Default Input Device: MacBook Pro Microphone + +Available Output Devices: +─────────────────────────────────────────────────────────────── +1. MacBook Pro Speakers + ├─ Sample Rate: 8000-48000 Hz + ├─ Channels: 2 + └─ Formats: F32, I16 + +2. USB Headphones + ├─ Sample Rate: 44100-48000 Hz + ├─ Channels: 2 + └─ Formats: F32, I16 + +Default Output Device: MacBook Pro Speakers +``` + +### Basic Usage + +Stream audio with default settings (using environment variables): + +```bash +cargo run +``` + +Using CLI arguments for connection details: + +```bash +cargo run -- \ + --url "wss://your-project.livekit.cloud" \ + --api-key "your-api-key" \ + --api-secret "your-api-secret" +``` + +Join a specific room with custom identity: + +```bash +cargo run -- \ + --url "wss://your-project.livekit.cloud" \ + --api-key "your-api-key" \ + --api-secret "your-api-secret" \ + --room-name "my-meeting" \ + --identity "john-doe" +``` + +### Advanced Configuration + +```bash +cargo run -- \ + --url "wss://your-project.livekit.cloud" \ + --api-key "your-api-key" \ + --api-secret "your-api-secret" \ + --input-device "USB Microphone" \ + --output-device "USB Headphones" \ + --sample-rate 44100 \ + --channels 2 \ + --volume 0.8 \ + --room-name "conference-room" +``` + +### Capture-Only Mode + +Disable audio playback and only capture: + +```bash +cargo run -- \ + --url "wss://your-project.livekit.cloud" \ + --api-key "your-api-key" \ + --api-secret "your-api-secret" \ + --no-playback +``` + +## Command Line Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--list-devices` | List available audio devices and exit | - | +| `--input-device ` | Input device name | System default | +| `--output-device ` | Output device name | System default | +| `--sample-rate ` | Sample rate in Hz | 48000 | +| `--channels ` | Number of channels | 1 | +| `--echo-cancellation` | Enable echo cancellation | true | +| `--noise-suppression` | Enable noise suppression | true | +| `--auto-gain-control` | Enable auto gain control | true | +| `--no-playback` | Disable audio playback (capture only) | false | +| `--volume ` | Playback volume (0.0 to 1.0) | 1.0 | +| `--identity ` | LiveKit participant identity | "rust-audio-streamer" | +| `--room-name ` | LiveKit room name | "audio-room" | +| `--url ` | LiveKit server URL | From LIVEKIT_URL env var | +| `--api-key ` | LiveKit API key | From LIVEKIT_API_KEY env var | +| `--api-secret ` | LiveKit API secret | From LIVEKIT_API_SECRET env var | diff --git a/examples/local_audio/src/audio_capture.rs b/examples/local_audio/src/audio_capture.rs new file mode 100644 index 000000000..e25d1d837 --- /dev/null +++ b/examples/local_audio/src/audio_capture.rs @@ -0,0 +1,117 @@ +use anyhow::{anyhow, Result}; +use cpal::traits::{DeviceTrait, StreamTrait}; +use cpal::{Device, SampleFormat, Stream, StreamConfig, SizedSample}; +use log::{error, info, warn}; +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, +}; +use tokio::sync::mpsc; + +pub struct AudioCapture { + _stream: Stream, + is_running: Arc, +} + +impl AudioCapture { + pub async fn new( + device: Device, + config: StreamConfig, + sample_format: SampleFormat, + audio_tx: mpsc::UnboundedSender>, + db_tx: Option>, + channel_index: u32, // New: Index of the channel to capture + num_input_channels: u32, // New: Total number of channels in input + ) -> Result { + let is_running = Arc::new(AtomicBool::new(true)); + let is_running_clone = is_running.clone(); + + let stream = match sample_format { + SampleFormat::F32 => Self::create_input_stream::(device, config, audio_tx, db_tx, is_running_clone, channel_index, num_input_channels)?, + SampleFormat::I16 => Self::create_input_stream::(device, config, audio_tx, db_tx, is_running_clone, channel_index, num_input_channels)?, + SampleFormat::U16 => Self::create_input_stream::(device, config, audio_tx, db_tx, is_running_clone, channel_index, num_input_channels)?, + sample_format => { + return Err(anyhow!("Unsupported sample format: {:?}", sample_format)); + } + }; + + stream.play()?; + info!("Audio capture stream started"); + + Ok(AudioCapture { + _stream: stream, + is_running, + }) + } + + fn create_input_stream( + device: Device, + config: StreamConfig, + audio_tx: mpsc::UnboundedSender>, + db_tx: Option>, + is_running: Arc, + channel_index: u32, // New: Index of the channel to capture + num_input_channels: u32, // New: Total number of channels in input + ) -> Result + where + T: SizedSample + Send + 'static, + { + let stream = device.build_input_stream( + &config, + move |data: &[T], _: &cpal::InputCallbackInfo| { + if !is_running.load(Ordering::Relaxed) { + return; + } + + // Extract samples from the selected channel (assuming interleaved format) + let converted: Vec = data.iter() + .skip(channel_index as usize) + .step_by(num_input_channels as usize) + .map(|&sample| Self::convert_sample_to_i16(sample)) + .collect(); + + // Calculate and send dB level if channel is available (now on selected channel only) + if let Some(ref db_sender) = db_tx { + let db_level = crate::db_meter::calculate_db_level(&converted); + if let Err(e) = db_sender.send(db_level) { + warn!("Failed to send dB level: {}", e); + } + } + + if let Err(e) = audio_tx.send(converted) { + warn!("Failed to send audio data: {}", e); + } + }, + move |err| { + error!("Audio input stream error: {}", err); + }, + None, + )?; + + Ok(stream) + } + + fn convert_sample_to_i16(sample: T) -> i16 { + if std::mem::size_of::() == std::mem::size_of::() { + let sample_f32 = unsafe { std::mem::transmute_copy::(&sample) }; + (sample_f32.clamp(-1.0, 1.0) * i16::MAX as f32) as i16 + } else if std::mem::size_of::() == std::mem::size_of::() { + unsafe { std::mem::transmute_copy::(&sample) } + } else if std::mem::size_of::() == std::mem::size_of::() { + let sample_u16 = unsafe { std::mem::transmute_copy::(&sample) }; + ((sample_u16 as i32) - (u16::MAX as i32 / 2)) as i16 + } else { + 0 + } + } + + pub fn stop(&self) { + self.is_running.store(false, Ordering::Relaxed); + } +} + +impl Drop for AudioCapture { + fn drop(&mut self) { + self.stop(); + } +} \ No newline at end of file diff --git a/examples/local_audio/src/audio_mixer.rs b/examples/local_audio/src/audio_mixer.rs new file mode 100644 index 000000000..79b542cad --- /dev/null +++ b/examples/local_audio/src/audio_mixer.rs @@ -0,0 +1,124 @@ +use std::sync::{Arc, Mutex}; +use tokio::sync::mpsc; +use crate::db_meter::calculate_db_level; + +#[derive(Clone)] +pub struct AudioMixer { + buffer: Arc>>, + sample_rate: u32, + channels: u32, + volume: f32, + max_buffer_size: usize, + db_tx: Option>, + // Channel to send reference audio for echo cancellation + reference_audio_tx: Option>>, +} + +impl AudioMixer { + pub fn new(sample_rate: u32, channels: u32, volume: f32) -> Self { + // Buffer for 1 second of audio + let max_buffer_size = sample_rate as usize * channels as usize; + + Self { + buffer: Arc::new(Mutex::new(std::collections::VecDeque::with_capacity(max_buffer_size))), + sample_rate, + channels, + volume: volume.clamp(0.0, 1.0), + max_buffer_size, + db_tx: None, + reference_audio_tx: None, + } + } + + pub fn with_db_meter(sample_rate: u32, channels: u32, volume: f32, db_tx: mpsc::UnboundedSender) -> Self { + // Buffer for 1 second of audio + let max_buffer_size = sample_rate as usize * channels as usize; + + Self { + buffer: Arc::new(Mutex::new(std::collections::VecDeque::with_capacity(max_buffer_size))), + sample_rate, + channels, + volume: volume.clamp(0.0, 1.0), + max_buffer_size, + db_tx: Some(db_tx), + reference_audio_tx: None, + } + } + + pub fn with_reference_audio( + sample_rate: u32, + channels: u32, + volume: f32, + db_tx: mpsc::UnboundedSender, + reference_audio_tx: mpsc::UnboundedSender> + ) -> Self { + // Buffer for 1 second of audio + let max_buffer_size = sample_rate as usize * channels as usize; + + Self { + buffer: Arc::new(Mutex::new(std::collections::VecDeque::with_capacity(max_buffer_size))), + sample_rate, + channels, + volume: volume.clamp(0.0, 1.0), + max_buffer_size, + db_tx: Some(db_tx), + reference_audio_tx: Some(reference_audio_tx), + } + } + + pub fn add_audio_data(&self, data: &[i16]) { + let mut buffer = self.buffer.lock().unwrap(); + + // Apply volume scaling and add to buffer + for &sample in data.iter() { + let scaled_sample = (sample as f32 * self.volume) as i16; + buffer.push_back(scaled_sample); + + // Prevent buffer from growing too large + if buffer.len() > self.max_buffer_size { + buffer.pop_front(); + } + } + } + + pub fn get_samples(&self, requested_samples: usize) -> Vec { + let mut buffer = self.buffer.lock().unwrap(); + let mut result = Vec::with_capacity(requested_samples); + + // Fill the requested samples + for _ in 0..requested_samples { + if let Some(sample) = buffer.pop_front() { + result.push(sample); + } else { + result.push(0); // Silence when no data available + } + } + + // Calculate and send dB level if we have a sender + if let Some(ref db_tx) = self.db_tx { + let db_level = calculate_db_level(&result); + let _ = db_tx.send(db_level); // Ignore errors if receiver is closed + } + + // Send copy to reference audio channel for echo cancellation + // Send ALL audio frames (including silence) for proper timing synchronization + if let Some(ref ref_tx) = self.reference_audio_tx { + if let Err(_) = ref_tx.send(result.clone()) { + // Only log occasionally to avoid spam + static mut LOG_COUNTER: u32 = 0; + unsafe { + LOG_COUNTER += 1; + if LOG_COUNTER % 1000 == 0 { + log::warn!("Reference audio channel closed or full (logged every 1000 attempts)"); + } + } + } + } + + result + } + + pub fn buffer_size(&self) -> usize { + self.buffer.lock().unwrap().len() + } +} \ No newline at end of file diff --git a/examples/local_audio/src/audio_playback.rs b/examples/local_audio/src/audio_playback.rs new file mode 100644 index 000000000..d029b2b3d --- /dev/null +++ b/examples/local_audio/src/audio_playback.rs @@ -0,0 +1,103 @@ +use anyhow::{anyhow, Result}; +use cpal::traits::{DeviceTrait, StreamTrait}; +use cpal::{Device, SampleFormat, Stream, StreamConfig, SizedSample, Sample, FromSample}; +use log::{error, info}; +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, +}; +use crate::audio_mixer::AudioMixer; + +pub struct AudioPlayback { + _stream: Stream, + is_running: Arc, +} + +impl AudioPlayback { + pub async fn new( + device: Device, + config: StreamConfig, + sample_format: SampleFormat, + mixer: AudioMixer, + ) -> Result { + let is_running = Arc::new(AtomicBool::new(true)); + let is_running_clone = is_running.clone(); + + let stream = match sample_format { + SampleFormat::F32 => Self::create_output_stream::(device, config, mixer, is_running_clone)?, + SampleFormat::I16 => Self::create_output_stream::(device, config, mixer, is_running_clone)?, + SampleFormat::U16 => Self::create_output_stream::(device, config, mixer, is_running_clone)?, + sample_format => { + return Err(anyhow!("Unsupported sample format: {:?}", sample_format)); + } + }; + + stream.play()?; + info!("Audio playback stream started"); + + Ok(AudioPlayback { + _stream: stream, + is_running, + }) + } + + fn create_output_stream( + device: Device, + config: StreamConfig, + mixer: AudioMixer, + is_running: Arc, + ) -> Result + where + T: SizedSample + Sample + Send + 'static + FromSample, + { + let stream = device.build_output_stream( + &config, + move |data: &mut [T], _: &cpal::OutputCallbackInfo| { + if !is_running.load(Ordering::Relaxed) { + // Fill with silence if not running + for sample in data.iter_mut() { + *sample = Sample::from_sample(0.0f32); + } + return; + } + + let mixed_samples = mixer.get_samples(data.len()); + + // Convert mixed i16 samples to output format + for (i, sample) in data.iter_mut().enumerate() { + *sample = Self::convert_i16_to_sample::(mixed_samples[i]); + } + }, + move |err| { + error!("Audio output stream error: {}", err); + }, + None, + )?; + + Ok(stream) + } + + fn convert_i16_to_sample>(sample: i16) -> T { + if std::mem::size_of::() == std::mem::size_of::() { + let sample_f32 = sample as f32 / i16::MAX as f32; + unsafe { std::mem::transmute_copy::(&sample_f32) } + } else if std::mem::size_of::() == std::mem::size_of::() { + unsafe { std::mem::transmute_copy::(&sample) } + } else if std::mem::size_of::() == std::mem::size_of::() { + let sample_u16 = ((sample as i32) + (u16::MAX as i32 / 2)) as u16; + unsafe { std::mem::transmute_copy::(&sample_u16) } + } else { + Sample::from_sample(0.0f32) + } + } + + pub fn stop(&self) { + self.is_running.store(false, Ordering::Relaxed); + } +} + +impl Drop for AudioPlayback { + fn drop(&mut self) { + self.stop(); + } +} \ No newline at end of file diff --git a/examples/local_audio/src/db_meter.rs b/examples/local_audio/src/db_meter.rs new file mode 100644 index 000000000..f983b7344 --- /dev/null +++ b/examples/local_audio/src/db_meter.rs @@ -0,0 +1,223 @@ +use anyhow::Result; + +use std::io::{self, Write}; +use tokio::sync::mpsc; + +// DB meter related constants +pub const DB_METER_UPDATE_INTERVAL_MS: u64 = 50; // Update every 50ms +const MIC_METER_WIDTH: usize = 25; // Width of the mic dB meter bar +const ROOM_METER_WIDTH: usize = 25; // Width of the room dB meter bar (reduced from 35) + +// ANSI color codes for colorful meters +const COLOR_RESET: &str = "\x1b[0m"; +const COLOR_GREEN: &str = "\x1b[32m"; +const COLOR_YELLOW: &str = "\x1b[33m"; +const COLOR_RED: &str = "\x1b[31m"; +const COLOR_BRIGHT_GREEN: &str = "\x1b[92m"; +const COLOR_BRIGHT_YELLOW: &str = "\x1b[93m"; +const COLOR_BRIGHT_RED: &str = "\x1b[91m"; +const COLOR_DIM: &str = "\x1b[2m"; + +/// Calculate decibel level from audio samples +pub fn calculate_db_level(samples: &[i16]) -> f32 { + if samples.is_empty() { + return -60.0; // Very quiet + } + + // Calculate RMS + let sum_squares: f64 = samples.iter() + .map(|&sample| { + let normalized = sample as f64 / i16::MAX as f64; + normalized * normalized + }) + .sum(); + + let rms = (sum_squares / samples.len() as f64).sqrt(); + + // Convert to dB (20 * log10(rms)) + if rms > 0.0 { + 20.0 * rms.log10() as f32 + } else { + -60.0 // Very quiet + } +} + +/// Get color based on dB level and position in meter +fn get_meter_color(db_level: f32, position_ratio: f32) -> &'static str { + // Determine color based on both dB level and position in the meter + if db_level > -6.0 && position_ratio > 0.85 { + COLOR_BRIGHT_RED // Clipping/very loud + } else if db_level > -12.0 && position_ratio > 0.7 { + COLOR_RED // Loud + } else if db_level > -18.0 && position_ratio > 0.5 { + COLOR_BRIGHT_YELLOW // Medium-loud + } else if db_level > -30.0 && position_ratio > 0.3 { + COLOR_YELLOW // Medium + } else if position_ratio > 0.1 { + COLOR_BRIGHT_GREEN // Low-medium + } else { + COLOR_GREEN // Low + } +} + +/// Format a single dB meter with colors +fn format_single_meter(db_level: f32, meter_width: usize, meter_label: &str) -> String { + let db_clamped = db_level.clamp(-60.0, 0.0); + let normalized = (db_clamped + 60.0) / 60.0; // Normalize to 0.0-1.0 + let filled_width = (normalized * meter_width as f32) as usize; + + let mut meter = String::new(); + meter.push_str(meter_label); + + // Add the dB value with appropriate color + let db_color = if db_level > -6.0 { + COLOR_BRIGHT_RED + } else if db_level > -12.0 { + COLOR_RED + } else if db_level > -24.0 { + COLOR_YELLOW + } else { + COLOR_GREEN + }; + meter.push_str(&format!("{}{:>5.1} dB{} ", db_color, db_level, COLOR_RESET)); + + // Add the visual meter with colors + meter.push('['); + for i in 0..meter_width { + let position_ratio = i as f32 / meter_width as f32; + + if i < filled_width { + let color = get_meter_color(db_level, position_ratio); + meter.push_str(color); + meter.push('█'); // Full block for active levels + meter.push_str(COLOR_RESET); + } else { + meter.push_str(COLOR_DIM); + meter.push('░'); // Light shade for empty + meter.push_str(COLOR_RESET); + } + } + meter.push(']'); + + meter +} + +/// Format both dB meters on the same line +fn format_dual_meters(mic_db: f32, room_db: f32) -> String { + let mic_meter = format_single_meter(mic_db, MIC_METER_WIDTH, "Mic: "); + let room_meter = format_single_meter(room_db, ROOM_METER_WIDTH, " Room: "); + + format!("{}{}", mic_meter, room_meter) +} + +/// Display dual dB meters continuously +pub async fn display_dual_db_meters( + mut mic_db_rx: mpsc::UnboundedReceiver, + mut room_db_rx: mpsc::UnboundedReceiver +) -> Result<()> { + let mut last_update = std::time::Instant::now(); + let mut current_mic_db = -60.0f32; + let mut current_room_db = -60.0f32; + let mut first_display = true; + + loop { + tokio::select! { + db_level = mic_db_rx.recv() => { + if let Some(db) = db_level { + current_mic_db = db; + + // Update display at regular intervals + if last_update.elapsed().as_millis() >= DB_METER_UPDATE_INTERVAL_MS as u128 { + display_meters(current_mic_db, current_room_db, &mut first_display); + last_update = std::time::Instant::now(); + } + } else { + break; + } + } + db_level = room_db_rx.recv() => { + if let Some(db) = db_level { + current_room_db = db; + + // Update display at regular intervals + if last_update.elapsed().as_millis() >= DB_METER_UPDATE_INTERVAL_MS as u128 { + display_meters(current_mic_db, current_room_db, &mut first_display); + last_update = std::time::Instant::now(); + } + } else { + // Room meter channel closed, continue with mic only + current_room_db = -60.0; + } + } + _ = tokio::time::sleep(tokio::time::Duration::from_millis(DB_METER_UPDATE_INTERVAL_MS)) => { + // Update display even if no new data + display_meters(current_mic_db, current_room_db, &mut first_display); + } + } + } + + Ok(()) +} + +/// Display the meters with proper terminal control (no jumping) +fn display_meters(mic_db: f32, room_db: f32, first_display: &mut bool) { + if *first_display { + // Don't clear screen - just show header where we are + println!(); + println!("{}Audio Levels Monitor{}", COLOR_BRIGHT_GREEN, COLOR_RESET); + println!("{}────────────────────────────────────────────────────────────────────────────────{}", COLOR_DIM, COLOR_RESET); + *first_display = false; + } + + // Clear current line and display meters in place + print!("\r\x1B[K"); // Clear current line + print!("{}", format_dual_meters(mic_db, room_db)); + io::stdout().flush().unwrap(); +} + +/// Display the dB meter continuously (legacy single meter function for compatibility) +pub async fn display_db_meter(mut db_rx: mpsc::UnboundedReceiver) -> Result<()> { + let mut last_update = std::time::Instant::now(); + let mut current_db = -60.0f32; + let mut first_display = true; + + loop { + tokio::select! { + db_level = db_rx.recv() => { + if let Some(db) = db_level { + current_db = db; + + // Update display at regular intervals + if last_update.elapsed().as_millis() >= DB_METER_UPDATE_INTERVAL_MS as u128 { + display_single_meter(current_db, &mut first_display); + last_update = std::time::Instant::now(); + } + } else { + break; + } + } + _ = tokio::time::sleep(tokio::time::Duration::from_millis(DB_METER_UPDATE_INTERVAL_MS)) => { + // Update display even if no new data + display_single_meter(current_db, &mut first_display); + } + } + } + + Ok(()) +} + +/// Display a single meter with proper terminal control (no jumping) +fn display_single_meter(db_level: f32, first_display: &mut bool) { + if *first_display { + // Don't clear screen - just show header where we are + println!(); + println!("{}Local Audio Level{}", COLOR_BRIGHT_GREEN, COLOR_RESET); + println!("{}────────────────────────────────────────{}", COLOR_DIM, COLOR_RESET); + *first_display = false; + } + + // Clear current line and display meter in place + print!("\r\x1B[K"); // Clear current line + print!("{}", format_single_meter(db_level, 40, "Mic Level: ")); + io::stdout().flush().unwrap(); +} \ No newline at end of file diff --git a/examples/local_audio/src/main.rs b/examples/local_audio/src/main.rs new file mode 100644 index 000000000..65b28494d --- /dev/null +++ b/examples/local_audio/src/main.rs @@ -0,0 +1,753 @@ +mod db_meter; +mod audio_mixer; +mod audio_capture; +mod audio_playback; + +use anyhow::{anyhow, Result}; +use audio_capture::AudioCapture; +use audio_mixer::AudioMixer; +use audio_playback::AudioPlayback; +use clap::Parser; +use cpal::traits::{DeviceTrait, HostTrait}; +use cpal::{Device, SampleRate, StreamConfig}; +use db_meter::display_dual_db_meters; +use livekit::{ + options::TrackPublishOptions, + track::{LocalAudioTrack, LocalTrack, TrackSource}, + webrtc::{ + audio_frame::AudioFrame, + audio_source::native::NativeAudioSource, + audio_stream::native::NativeAudioStream, + prelude::{AudioSourceOptions, RtcAudioSource}, + }, + Room, RoomEvent, RoomOptions, +}; +use livekit_api::access_token; +use libwebrtc::native::apm::AudioProcessingModule; +use log::{debug, error, info, warn}; +use std::{ + env, + sync::Arc, +}; +use tokio::sync::mpsc; +use futures_util::StreamExt; + +// Echo cancellation processor that coordinates forward and reverse streams +struct EchoCancellationProcessor { + apm: AudioProcessingModule, + sample_rate: u32, + frame_size: usize, // samples per 10ms +} + +impl EchoCancellationProcessor { + fn new( + echo_cancellation: bool, + noise_suppression: bool, + auto_gain_control: bool, + sample_rate: u32, + ) -> Self { + let apm = AudioProcessingModule::new( + echo_cancellation, + auto_gain_control, + false, // high_pass_filter - keep disabled for compatibility + noise_suppression, + ); + + let frame_size = (sample_rate / 100) as usize; // 10ms worth of samples for mono + + Self { + apm, + sample_rate, + frame_size, + } + } + + // Process microphone input (forward stream) with echo cancellation + fn process_microphone_audio(&mut self, audio_data: &mut [i16]) -> Result<()> { + if audio_data.len() != self.frame_size { + return Err(anyhow!("Audio data must be exactly {} samples (10ms)", self.frame_size)); + } + + if let Err(e) = self.apm.process_stream(audio_data, self.sample_rate as i32, 1) { + warn!("APM process_stream failed: {}", e); + } + + Ok(()) + } + + // Process reference audio (reverse stream) - what's being played through speakers + fn process_reference_audio(&mut self, audio_data: &mut [i16]) -> Result<()> { + if audio_data.len() != self.frame_size { + return Err(anyhow!("Reference audio data must be exactly {} samples (10ms)", self.frame_size)); + } + + if let Err(e) = self.apm.process_reverse_stream(audio_data, self.sample_rate as i32, 1) { + warn!("APM process_reverse_stream failed: {}", e); + } + + Ok(()) + } + + // Set the delay between the reverse stream (speakers) and forward stream (microphone) + fn set_stream_delay(&mut self, delay_ms: i32) -> Result<()> { + if let Err(e) = self.apm.set_stream_delay_ms(delay_ms) { + warn!("APM set_stream_delay_ms failed: {}", e); + } + Ok(()) + } +} + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +struct Args { + /// List available audio devices and exit + #[arg(short, long)] + list_devices: bool, + + /// Audio input device name to use (default: system default) + #[arg(short = 'i', long)] + input_device: Option, + + /// Audio output device name to use (default: system default) + #[arg(short = 'o', long)] + output_device: Option, + + /// Sample rate in Hz (default: 48000) + #[arg(short, long, default_value_t = 48000)] + sample_rate: u32, + + /// Number of channels (default: 1) + #[arg(short, long, default_value_t = 1)] + channels: u32, + + /// Enable echo cancellation + #[arg(long, default_value_t = true)] + echo_cancellation: bool, + + /// Enable noise suppression + #[arg(long, default_value_t = true)] + noise_suppression: bool, + + /// Enable auto gain control + #[arg(long, default_value_t = true)] + auto_gain_control: bool, + + /// Disable audio playback (capture only) + #[arg(long, default_value_t = false)] + no_playback: bool, + + /// Master playback volume (0.0 to 1.0, default: 1.0) + #[arg(long, default_value_t = 1.0)] + volume: f32, + + /// Input channel index to capture (default: 0) + #[arg(long, default_value_t = 0)] + channel: u32, + + /// LiveKit participant identity (default: "rust-audio-streamer") + #[arg(long, default_value = "rust-audio-streamer")] + identity: String, + + /// LiveKit room name to join (default: "audio-room") + #[arg(long, default_value = "audio-room")] + room_name: String, + + /// LiveKit server URL (can also be set via LIVEKIT_URL environment variable) + #[arg(long)] + url: Option, + + /// LiveKit API key (can also be set via LIVEKIT_API_KEY environment variable) + #[arg(long)] + api_key: Option, + + /// LiveKit API secret (can also be set via LIVEKIT_API_SECRET environment variable) + #[arg(long)] + api_secret: Option, +} + + + + + + + +fn list_audio_devices() -> Result<()> { + let host = cpal::default_host(); + + println!("Available audio input devices:"); + println!("─────────────────────────────"); + + let input_devices = host.input_devices()?; + + for (i, device) in input_devices.enumerate() { + let name = device.name().unwrap_or_else(|_| "Unknown".to_string()); + println!("{}. {}", i + 1, name); + + if let Ok(config) = device.default_input_config() { + println!(" └─ Sample rate: {} Hz", config.sample_rate().0); + println!(" └─ Channels: {}", config.channels()); + println!(" └─ Sample format: {:?}", config.sample_format()); + } + println!(); + } + + // Show default input device + if let Some(device) = host.default_input_device() { + let name = device.name().unwrap_or_else(|_| "Unknown".to_string()); + println!("Default input device: {}", name); + } + + println!("\nAvailable audio output devices:"); + println!("─────────────────────────────"); + + let output_devices = host.output_devices()?; + + for (i, device) in output_devices.enumerate() { + let name = device.name().unwrap_or_else(|_| "Unknown".to_string()); + println!("{}. {}", i + 1, name); + + if let Ok(config) = device.default_output_config() { + println!(" └─ Sample rate: {} Hz", config.sample_rate().0); + println!(" └─ Channels: {}", config.channels()); + println!(" └─ Sample format: {:?}", config.sample_format()); + } + println!(); + } + + // Show default output device + if let Some(device) = host.default_output_device() { + let name = device.name().unwrap_or_else(|_| "Unknown".to_string()); + println!("Default output device: {}", name); + } + + Ok(()) +} + +fn find_input_device_by_name(name: &str) -> Result { + let host = cpal::default_host(); + let devices = host.input_devices()?; + + for device in devices { + if let Ok(device_name) = device.name() { + if device_name.contains(name) { + return Ok(device); + } + } + } + + Err(anyhow!("Input device '{}' not found", name)) +} + +fn find_output_device_by_name(name: &str) -> Result { + let host = cpal::default_host(); + let devices = host.output_devices()?; + + for device in devices { + if let Ok(device_name) = device.name() { + if device_name.contains(name) { + return Ok(device); + } + } + } + + Err(anyhow!("Output device '{}' not found", name)) +} + +async fn stream_audio_to_livekit( + mut audio_rx: mpsc::UnboundedReceiver>, + livekit_source: NativeAudioSource, + mut echo_processor: EchoCancellationProcessor, + sample_rate: u32, +) -> Result<()> { + let mut buffer = Vec::new(); + let samples_per_10ms = (sample_rate / 100) as usize; // For mono + + info!( + "Starting LiveKit audio streaming with echo cancellation ({}Hz, 1 channel, {} samples per 10ms)", + sample_rate, samples_per_10ms + ); + + // Set initial delay estimate (can be adjusted based on your system) + let _ = echo_processor.set_stream_delay(50); // 50ms initial estimate + + while let Some(audio_data) = audio_rx.recv().await { + buffer.extend_from_slice(&audio_data); + + // Send 10ms chunks to LiveKit + while buffer.len() >= samples_per_10ms { + let mut chunk: Vec = buffer.drain(..samples_per_10ms).collect(); + + // Process through echo cancellation before sending to LiveKit + if let Err(e) = echo_processor.process_microphone_audio(&mut chunk) { + warn!("Echo cancellation processing failed: {}", e); + } + + let audio_frame = AudioFrame { + data: chunk.into(), + sample_rate, + num_channels: 1, // Fixed to mono + samples_per_channel: samples_per_10ms as u32, + }; + + if let Err(e) = livekit_source.capture_frame(&audio_frame).await { + error!("Failed to send audio frame to LiveKit: {}", e); + } + } + } + + Ok(()) +} + +async fn stream_audio_to_livekit_with_shared_apm( + mut audio_rx: mpsc::UnboundedReceiver>, + livekit_source: NativeAudioSource, + echo_processor: Arc>, + sample_rate: u32, +) -> Result<()> { + let mut buffer = Vec::new(); + let samples_per_10ms = (sample_rate / 100) as usize; // For mono + + info!( + "Starting LiveKit audio streaming with SHARED APM echo cancellation ({}Hz, 1 channel, {} samples per 10ms)", + sample_rate, samples_per_10ms + ); + + // Set initial delay estimate (can be adjusted based on your system) + { + let mut processor = echo_processor.lock().await; + let _ = processor.set_stream_delay(50); // 50ms initial estimate + } + + while let Some(audio_data) = audio_rx.recv().await { + buffer.extend_from_slice(&audio_data); + + // Send 10ms chunks to LiveKit + while buffer.len() >= samples_per_10ms { + let mut chunk: Vec = buffer.drain(..samples_per_10ms).collect(); + + // Process through SHARED echo cancellation before sending to LiveKit + { + let mut processor = echo_processor.lock().await; + if let Err(e) = processor.process_microphone_audio(&mut chunk) { + warn!("Shared APM echo cancellation processing failed: {}", e); + } + } + + let audio_frame = AudioFrame { + data: chunk.into(), + sample_rate, + num_channels: 1, // Fixed to mono + samples_per_channel: samples_per_10ms as u32, + }; + + if let Err(e) = livekit_source.capture_frame(&audio_frame).await { + error!("Failed to send audio frame to LiveKit: {}", e); + } + } + } + + Ok(()) +} + +async fn handle_remote_audio_streams( + room: Arc, + mixer: AudioMixer, + sample_rate: u32, +) -> Result<()> { + let mut room_events = room.subscribe(); + + info!("Starting remote audio stream handler"); + + while let Some(event) = room_events.recv().await { + match event { + RoomEvent::ParticipantConnected(participant) => { + info!("Participant connected: {} ({})", participant.identity(), participant.name()); + } + + RoomEvent::TrackPublished { participant, publication } => { + info!("Track published by {}: {} ({:?})", + participant.identity(), publication.name(), publication.kind()); + } + + RoomEvent::TrackSubscribed { track, participant, .. } => { + info!("Track subscribed from {}: {} ({:?})", + participant.identity(), track.name(), track.kind()); + + if let livekit::track::RemoteTrack::Audio(audio_track) = track { + let participant_identity = participant.identity().to_string(); + info!("Setting up audio stream for participant: {}", participant_identity); + + // Create audio stream for this remote track (fixed to 1 channel) + let mut audio_stream = NativeAudioStream::new( + audio_track.rtc_track(), + sample_rate as i32, + 1, // Fixed to mono + ); + + // Start processing audio frames from this participant + let stream_key = participant_identity.clone(); + let mixer_clone = mixer.clone(); + + tokio::spawn(async move { + info!("Starting audio stream processing for participant: {}", stream_key); + + while let Some(audio_frame) = audio_stream.next().await { + // Add this participant's audio to the mixer + mixer_clone.add_audio_data(audio_frame.data.as_ref()); + + debug!("Received audio frame from {}: {} samples, {} channels, {} Hz, buffer size: {}", + stream_key, audio_frame.data.len(), audio_frame.num_channels, + audio_frame.sample_rate, mixer_clone.buffer_size()); + } + + info!("Audio stream ended for participant: {}", stream_key); + }); + } + } + + RoomEvent::TrackUnsubscribed { track, participant, .. } => { + info!("Track unsubscribed from {}: {} ({:?})", + participant.identity(), track.name(), track.kind()); + + if let livekit::track::RemoteTrack::Audio(_) = track { + let participant_identity = participant.identity().to_string(); + info!("Stopping audio stream for participant: {}", participant_identity); + + // Audio stream will be automatically cleaned up when the task ends + } + } + + RoomEvent::ParticipantDisconnected(participant) => { + let participant_identity = participant.identity().to_string(); + info!("Participant disconnected: {}", participant_identity); + + // Audio stream will be automatically cleaned up when the task ends + } + + _ => { + // Handle other room events as needed + debug!("Room event: {:?}", event); + } + } + } + + Ok(()) +} + +#[tokio::main] +async fn main() -> Result<()> { + env_logger::init(); + let args = Args::parse(); + + if args.list_devices { + return list_audio_devices(); + } + + // Validate volume parameter + if args.volume < 0.0 || args.volume > 1.0 { + return Err(anyhow!("Volume must be between 0.0 and 1.0")); + } + + // Get LiveKit connection details from CLI arguments or environment variables + let url = args.url.or_else(|| env::var("LIVEKIT_URL").ok()) + .expect("LiveKit URL must be provided via --url argument or LIVEKIT_URL environment variable"); + let api_key = args.api_key.or_else(|| env::var("LIVEKIT_API_KEY").ok()) + .expect("LiveKit API key must be provided via --api-key argument or LIVEKIT_API_KEY environment variable"); + let api_secret = args.api_secret.or_else(|| env::var("LIVEKIT_API_SECRET").ok()) + .expect("LiveKit API secret must be provided via --api-secret argument or LIVEKIT_API_SECRET environment variable"); + + // Create access token + let token = access_token::AccessToken::with_api_key(&api_key, &api_secret) + .with_identity(&args.identity) + .with_name(&args.identity) + .with_grants(access_token::VideoGrants { + room_join: true, + room: args.room_name.clone(), + ..Default::default() + }) + .to_jwt()?; + + // Connect to LiveKit room with auto-subscribe enabled + info!("Connecting to LiveKit room '{}' as '{}'...", args.room_name, args.identity); + let mut room_options = RoomOptions::default(); + room_options.auto_subscribe = true; + let (room, _) = Room::connect(&url, &token, room_options).await?; + let room = Arc::new(room); + info!("Connected to room: {} - {}", room.name(), room.sid().await); + + // Set up audio input device + let host = cpal::default_host(); + let input_device = if let Some(device_name) = &args.input_device { + info!("Looking for input device: {}", device_name); + find_input_device_by_name(device_name)? + } else { + info!("Using default input device"); + host.default_input_device() + .ok_or_else(|| anyhow!("No default input device available"))? + }; + + let input_device_name = input_device.name().unwrap_or_else(|_| "Unknown".to_string()); + info!("Using audio input device: {}", input_device_name); + + // Set up audio output device (if playback is enabled) + let output_device = if !args.no_playback { + let device = if let Some(device_name) = &args.output_device { + info!("Looking for output device: {}", device_name); + Some(find_output_device_by_name(device_name)?) + } else { + info!("Using default output device"); + host.default_output_device() + }; + + if let Some(ref device) = device { + let output_device_name = device.name().unwrap_or_else(|_| "Unknown".to_string()); + info!("Using audio output device: {}", output_device_name); + } + + device + } else { + info!("Audio playback disabled"); + None + }; + + // Configure audio capture + let input_supported_config = input_device.default_input_config()?; + let supported_channels = input_supported_config.channels() as u32; + if args.channel >= supported_channels { + return Err(anyhow!("Invalid channel index: {}. Device supports {} channels.", args.channel, supported_channels)); + } + + let input_config = StreamConfig { + channels: input_supported_config.channels(), // Capture all available channels to allow channel selection + sample_rate: SampleRate(args.sample_rate), + buffer_size: cpal::BufferSize::Default, + }; + + info!( + "Audio input config: {}Hz, {} channels, {:?}", + input_config.sample_rate.0, + input_config.channels, + input_supported_config.sample_format() + ); + + // Configure audio playback (if enabled) + let output_config = if !args.no_playback { + let config = StreamConfig { + channels: 1, // Fixed to mono + sample_rate: SampleRate(args.sample_rate), + buffer_size: cpal::BufferSize::Default, + }; + + info!( + "Audio output config: {}Hz, 1 channel", + config.sample_rate.0, + ); + + Some(config) + } else { + None + }; + + // Create LiveKit audio source with audio processing options + // Note: We disable echo cancellation here since we're handling it manually with APM + let audio_options = AudioSourceOptions { + echo_cancellation: false, // Disabled - we handle this manually + noise_suppression: false, // Disabled - we handle this manually + auto_gain_control: false, // Disabled - we handle this manually + }; + + info!( + "Audio processing - Manual echo cancellation: {}, Manual noise suppression: {}, Manual auto gain control: {}", + args.echo_cancellation, + args.noise_suppression, + args.auto_gain_control + ); + + let livekit_source = NativeAudioSource::new( + audio_options, + args.sample_rate, + 1, // Fixed to 1 channel + 1000, // 1 second buffer + ); + + // Create and publish audio track + let track = LocalAudioTrack::create_audio_track( + "microphone", + RtcAudioSource::Native(livekit_source.clone()), + ); + + room.local_participant() + .publish_track( + LocalTrack::Audio(track), + TrackPublishOptions { + source: TrackSource::Microphone, + ..Default::default() + }, + ) + .await?; + + info!("Audio track published to LiveKit successfully"); + + // Create shared echo cancellation processor + let echo_processor = Arc::new(tokio::sync::Mutex::new( + EchoCancellationProcessor::new( + args.echo_cancellation, + args.noise_suppression, + args.auto_gain_control, + args.sample_rate, + ) + )); + + info!("✅ Created shared APM with echo_cancellation={}, noise_suppression={}, auto_gain_control={}", + args.echo_cancellation, args.noise_suppression, args.auto_gain_control); + + // Set up audio capture and streaming + let (audio_tx, audio_rx) = mpsc::unbounded_channel(); + + // Set up reference audio channel for echo cancellation + let (reference_audio_tx, reference_audio_rx) = mpsc::unbounded_channel(); + + info!("✅ Set up audio channels: microphone input → forward stream, speaker output → reverse stream"); + + // Set up dB meters + let (mic_db_tx, mic_db_rx) = mpsc::unbounded_channel(); + let (room_db_tx, room_db_rx) = mpsc::unbounded_channel(); + + // Start dual dB meter display + let db_meter_task = tokio::spawn(display_dual_db_meters(mic_db_rx, room_db_rx)); + + // Start audio capture with channel selection + let _audio_capture = AudioCapture::new( + input_device, + input_config, + input_supported_config.sample_format(), + audio_tx, + Some(mic_db_tx), + args.channel, // Pass selected channel index + supported_channels, // Pass number of input channels + ).await?; + + // Start reference audio processing for echo cancellation + let reference_task = tokio::spawn(process_reference_audio( + reference_audio_rx, + echo_processor.clone(), + args.sample_rate, + )); + + info!("✅ Started reference audio processing task (reverse stream for AEC)"); + + // Start streaming to LiveKit with echo cancellation (using shared processor) + let streaming_task = tokio::spawn(stream_audio_to_livekit_with_shared_apm( + audio_rx, + livekit_source, + echo_processor.clone(), + args.sample_rate, + )); + + info!("✅ Started LiveKit streaming task (forward stream for AEC)"); + + // Set up audio playback (if enabled) + let _audio_playback = if let (Some(output_device), Some(output_config)) = (output_device, output_config) { + // Create audio mixer for combining participant audio streams with reference audio for echo cancellation + let mixer = AudioMixer::with_reference_audio( + args.sample_rate, + 1, + args.volume, + room_db_tx, + reference_audio_tx + ); + + // Start handling remote audio streams + let room_clone = room.clone(); + let mixer_clone = mixer.clone(); + let remote_audio_task = tokio::spawn(handle_remote_audio_streams( + room_clone, + mixer_clone, + args.sample_rate, + )); + + // Get output device format + let output_supported_config = output_device.default_output_config()?; + + // Start audio playback + let playback = AudioPlayback::new( + output_device, + output_config, + output_supported_config.sample_format(), + mixer, + ).await?; + + info!("Audio playback enabled with echo cancellation and volume: {:.1}%", args.volume * 100.0); + + // Store the remote audio task handle for proper cleanup + let remote_audio_task_handle = remote_audio_task; + + // Keep the task alive by storing it in a variable that won't be dropped + tokio::spawn(async move { + let _ = remote_audio_task_handle.await; + }); + + Some(playback) + } else { + warn!("⚠️ Audio playback disabled - echo cancellation will NOT work without reference audio from speakers!"); + warn!("⚠️ Enable playback for full AEC functionality"); + None + }; + + info!( + "Audio streaming started with echo cancellation. Microphone: {}, Playback: {}. Press Ctrl+C to stop.", + if args.input_device.is_some() { "Custom" } else { "Default" }, + if _audio_playback.is_some() { "Enabled" } else { "Disabled" } + ); + + // Wait for Ctrl+C + tokio::signal::ctrl_c().await?; + info!("\nShutting down..."); + + // Clean shutdown + streaming_task.abort(); + reference_task.abort(); + db_meter_task.abort(); + room.close().await?; + + Ok(()) +} + +async fn process_reference_audio( + mut reference_rx: mpsc::UnboundedReceiver>, + echo_processor: Arc>, + sample_rate: u32, +) -> Result<()> { + let mut buffer = Vec::new(); + let samples_per_10ms = (sample_rate / 100) as usize; // For mono + let mut frame_count = 0; + + info!("Starting reference audio processing for echo cancellation (expecting {} samples per 10ms)", samples_per_10ms); + + while let Some(audio_data) = reference_rx.recv().await { + buffer.extend_from_slice(&audio_data); + + // Process 10ms chunks for echo cancellation reference + while buffer.len() >= samples_per_10ms { + let mut chunk: Vec = buffer.drain(..samples_per_10ms).collect(); + + // Log every 100 frames (1 second) to confirm we're getting reference audio + frame_count += 1; + if frame_count % 100 == 0 { + let avg_amplitude = chunk.iter().map(|&x| x.abs() as f64).sum::() / chunk.len() as f64; + debug!("Reference audio frame #{}: {} samples, avg amplitude: {:.1}", frame_count, chunk.len(), avg_amplitude); + } + + // Process through echo cancellation reverse stream + { + let mut processor = echo_processor.lock().await; + if let Err(e) = processor.process_reference_audio(&mut chunk) { + warn!("Reference audio processing failed: {}", e); + } + } + } + } + + warn!("Reference audio processing ended - this may impact echo cancellation effectiveness"); + Ok(()) +} \ No newline at end of file From 9d979b32e58827806730a3a2e6e018b7be0601da Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 3 Sep 2025 12:00:14 +1000 Subject: [PATCH 236/274] chore: release (#694) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- Cargo.lock | 10 +++++----- Cargo.toml | 10 +++++----- libwebrtc/CHANGELOG.md | 14 ++++++++++++++ libwebrtc/Cargo.toml | 2 +- livekit-api/CHANGELOG.md | 6 ++++++ livekit-api/Cargo.toml | 2 +- livekit-ffi/CHANGELOG.md | 6 ++++++ livekit-ffi/Cargo.toml | 2 +- livekit/CHANGELOG.md | 6 ++++++ livekit/Cargo.toml | 2 +- webrtc-sys/CHANGELOG.md | 18 ++++++++++++++++++ webrtc-sys/Cargo.toml | 2 +- 12 files changed, 65 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b6c575546..4c49f4098 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1560,7 +1560,7 @@ dependencies = [ [[package]] name = "libwebrtc" -version = "0.3.12" +version = "0.3.13" dependencies = [ "cxx", "env_logger", @@ -1616,7 +1616,7 @@ checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "livekit" -version = "0.7.16" +version = "0.7.17" dependencies = [ "bmrng", "bytes", @@ -1640,7 +1640,7 @@ dependencies = [ [[package]] name = "livekit-api" -version = "0.4.5" +version = "0.4.6" dependencies = [ "async-tungstenite", "base64", @@ -1670,7 +1670,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.12.31" +version = "0.12.32" dependencies = [ "bytes", "console-subscriber", @@ -3367,7 +3367,7 @@ checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" [[package]] name = "webrtc-sys" -version = "0.3.9" +version = "0.3.10" dependencies = [ "cc", "cxx", diff --git a/Cargo.toml b/Cargo.toml index 329837bef..307782683 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,12 +17,12 @@ members = [ [workspace.dependencies] imgproc = { version = "0.3.12", path = "imgproc" } yuv-sys = { version = "0.3.7", path = "yuv-sys" } -libwebrtc = { version = "0.3.12", path = "libwebrtc" } -livekit-api = { version = "0.4.5", path = "livekit-api" } -livekit-ffi = { version = "0.12.31", path = "livekit-ffi" } +libwebrtc = { version = "0.3.13", path = "libwebrtc" } +livekit-api = { version = "0.4.6", path = "livekit-api" } +livekit-ffi = { version = "0.12.32", path = "livekit-ffi" } livekit-protocol = { version = "0.4.0", path = "livekit-protocol" } livekit-runtime = { version = "0.4.0", path = "livekit-runtime" } -livekit = { version = "0.7.16", path = "livekit" } +livekit = { version = "0.7.17", path = "livekit" } soxr-sys = { version = "0.1.0", path = "soxr-sys" } webrtc-sys-build = { version = "0.3.7", path = "webrtc-sys/build" } -webrtc-sys = { version = "0.3.9", path = "webrtc-sys" } +webrtc-sys = { version = "0.3.10", path = "webrtc-sys" } diff --git a/libwebrtc/CHANGELOG.md b/libwebrtc/CHANGELOG.md index a42e1b0cd..5e9b32cdb 100644 --- a/libwebrtc/CHANGELOG.md +++ b/libwebrtc/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.3.13](https://github.com/livekit/rust-sdks/compare/rust-sdks/libwebrtc@0.3.12...rust-sdks/libwebrtc@0.3.13) - 2025-09-03 + +### Other + +- updated the following local packages: webrtc-sys +# Changelog + ## [0.3.12](https://github.com/livekit/rust-sdks/compare/rust-sdks/libwebrtc@0.3.11...rust-sdks/libwebrtc@0.3.12) - 2025-06-17 ### Other diff --git a/libwebrtc/Cargo.toml b/libwebrtc/Cargo.toml index 3cb207e62..0df6c845d 100644 --- a/libwebrtc/Cargo.toml +++ b/libwebrtc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libwebrtc" -version = "0.3.12" +version = "0.3.13" edition = "2021" homepage = "https://livekit.io" license = "Apache-2.0" diff --git a/livekit-api/CHANGELOG.md b/livekit-api/CHANGELOG.md index 3f2c04b5e..64bcc0d93 100644 --- a/livekit-api/CHANGELOG.md +++ b/livekit-api/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.4.6](https://github.com/livekit/rust-sdks/compare/rust-sdks/livekit-api@0.4.5...rust-sdks/livekit-api@0.4.6) - 2025-09-03 + +### Other + +- add attributes to Claims and AccessToken ([#693](https://github.com/livekit/rust-sdks/pull/693)) + ## [0.4.5](https://github.com/livekit/rust-sdks/compare/rust-sdks/livekit-api@0.4.4...rust-sdks/livekit-api@0.4.5) - 2025-07-31 ### Other diff --git a/livekit-api/Cargo.toml b/livekit-api/Cargo.toml index 2c537147c..1caeb6069 100644 --- a/livekit-api/Cargo.toml +++ b/livekit-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-api" -version = "0.4.5" +version = "0.4.6" license = "Apache-2.0" description = "Rust Server SDK for LiveKit" edition = "2021" diff --git a/livekit-ffi/CHANGELOG.md b/livekit-ffi/CHANGELOG.md index 7e939a849..273fa335c 100644 --- a/livekit-ffi/CHANGELOG.md +++ b/livekit-ffi/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.12.32](https://github.com/livekit/rust-sdks/compare/rust-sdks/livekit-ffi@0.12.31...rust-sdks/livekit-ffi@0.12.32) - 2025-09-03 + +### Other + +- updated the following local packages: livekit-api, livekit + ## [0.12.31](https://github.com/livekit/rust-sdks/compare/rust-sdks/livekit-ffi@0.12.30...rust-sdks/livekit-ffi@0.12.31) - 2025-07-31 ### Other diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 3e49dff9d..e3836bd0e 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.12.31" +version = "0.12.32" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" diff --git a/livekit/CHANGELOG.md b/livekit/CHANGELOG.md index ab73617b5..5bb8d3bac 100644 --- a/livekit/CHANGELOG.md +++ b/livekit/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.7.17](https://github.com/livekit/rust-sdks/compare/rust-sdks/livekit@0.7.16...rust-sdks/livekit@0.7.17) - 2025-09-03 + +### Other + +- updated the following local packages: livekit-api, libwebrtc + ## [0.7.16](https://github.com/livekit/rust-sdks/compare/rust-sdks/livekit@0.7.15...rust-sdks/livekit@0.7.16) - 2025-07-31 ### Other diff --git a/livekit/Cargo.toml b/livekit/Cargo.toml index c476164bb..6d638003a 100644 --- a/livekit/Cargo.toml +++ b/livekit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit" -version = "0.7.16" +version = "0.7.17" edition = "2021" license = "Apache-2.0" description = "Rust Client SDK for LiveKit" diff --git a/webrtc-sys/CHANGELOG.md b/webrtc-sys/CHANGELOG.md index 9e77f8c62..7850a6765 100644 --- a/webrtc-sys/CHANGELOG.md +++ b/webrtc-sys/CHANGELOG.md @@ -1,5 +1,23 @@ # Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.3.10](https://github.com/livekit/rust-sdks/compare/rust-sdks/webrtc-sys@0.3.9...rust-sdks/webrtc-sys@0.3.10) - 2025-09-03 + +### Added + +- VA-API support for linux. ([#638](https://github.com/livekit/rust-sdks/pull/638)) + +### Fixed + +- hardware rendering ([#695](https://github.com/livekit/rust-sdks/pull/695)) +# Changelog + ## [0.3.9](https://github.com/livekit/rust-sdks/compare/rust-sdks/webrtc-sys@0.3.8...rust-sdks/webrtc-sys@0.3.9) - 2025-06-17 ### Other diff --git a/webrtc-sys/Cargo.toml b/webrtc-sys/Cargo.toml index 1d13543c8..c04ee5e14 100644 --- a/webrtc-sys/Cargo.toml +++ b/webrtc-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "webrtc-sys" -version = "0.3.9" +version = "0.3.10" edition = "2021" homepage = "https://livekit.io" license = "Apache-2.0" From 68ea1426f825841a5c2870e9cde2ef917227d7d8 Mon Sep 17 00:00:00 2001 From: CloudWebRTC Date: Wed, 3 Sep 2025 11:38:04 +0800 Subject: [PATCH 237/274] fix release-plz CI (#698) * fix release-plz CI * Update installation command for VA-API/NVIDIA drivers --- .github/workflows/release.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ffc22443c..0a5e46ad5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,6 +28,10 @@ jobs: submodules: recursive - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable + - name: Install VA-API/NVIDIA drivers for build + run: | + sudo apt update -y + sudo apt install -y libva-dev libdrm-dev libnvidia-compute-570 libnvidia-decode-570 nvidia-cuda-dev -y - name: Run release-plz uses: release-plz/action@v0.5 id: release-plz From ac1952f3bdabd33ccd56556ae8ee69eab262811c Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Tue, 9 Sep 2025 15:46:46 +1000 Subject: [PATCH 238/274] Data channel reliability (#688) * Create TTL map * Refactor data channel event * Avoid cloning keys * Create TxQueue * Implement reliable retry * Derive debug * Avoid unsafe unwrap * Obtain packet kind from RTC event * Refactor incoming data packet handling * Set participant info on outgoing data packets * Create helpers for E2E testing * Create E2E test for data channel reliability * Put E2E tests behind an internal feature * Enable E2E tests in CI --- .github/workflows/tests.yml | 22 +- Cargo.lock | 5 +- livekit/Cargo.toml | 8 +- livekit/src/room/mod.rs | 20 +- livekit/src/room/utils/mod.rs | 2 + livekit/src/room/utils/ttl_map.rs | 143 +++++++ livekit/src/room/utils/tx_queue.rs | 113 ++++++ livekit/src/rtc_engine/rtc_events.rs | 23 +- livekit/src/rtc_engine/rtc_session.rs | 526 ++++++++++++++++++-------- livekit/tests/common/mod.rs | 56 +++ livekit/tests/data_channel_test.rs | 85 +++++ 11 files changed, 808 insertions(+), 195 deletions(-) create mode 100644 livekit/src/room/utils/ttl_map.rs create mode 100644 livekit/src/room/utils/tx_queue.rs create mode 100644 livekit/tests/common/mod.rs create mode 100644 livekit/tests/data_channel_test.rs diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7f398403f..1e214e666 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -35,8 +35,10 @@ jobs: target: x86_64-pc-windows-msvc - os: macos-latest target: x86_64-apple-darwin + e2e-testing: true - os: ubuntu-latest target: x86_64-unknown-linux-gnu + e2e-testing: true name: Test (${{ matrix.target }}) runs-on: ${{ matrix.os }} @@ -52,11 +54,29 @@ jobs: sudo apt update -y sudo apt install -y libssl-dev libx11-dev libgl1-mesa-dev libxext-dev libva-dev libdrm-dev libnvidia-decode-570 libnvidia-compute-570 nvidia-cuda-dev + - name: Install LiveKit server + if: ${{ matrix.e2e-testing }} + run: | + if [[ "${{ matrix.os }}" == "ubuntu-latest" ]]; then + curl -sSL https://get.livekit.io | bash + elif [[ "${{ matrix.os }}" == "macos-latest" ]]; then + brew install livekit + fi + - uses: actions/checkout@v3 with: submodules: true - - name: Test + - name: Run LiveKit server + if: ${{ matrix.e2e-testing }} + run: livekit-server --dev & + + - name: Test (no E2E) + if: ${{ !matrix.e2e-testing }} run: cargo +nightly test --release --verbose --target ${{ matrix.target }} -- --nocapture + - name: Test (with E2E) + if: ${{ matrix.e2e-testing }} + run: cargo +nightly test --release --verbose --target ${{ matrix.target }} --features __lk-e2e-test -- --nocapture + diff --git a/Cargo.lock b/Cargo.lock index 4c49f4098..234632fdb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -54,9 +54,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.98" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" [[package]] name = "async-channel" @@ -1618,6 +1618,7 @@ checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" name = "livekit" version = "0.7.17" dependencies = [ + "anyhow", "bmrng", "bytes", "chrono", diff --git a/livekit/Cargo.toml b/livekit/Cargo.toml index 6d638003a..8a1cf0b04 100644 --- a/livekit/Cargo.toml +++ b/livekit/Cargo.toml @@ -22,9 +22,8 @@ native-tls-vendored = ["livekit-api/native-tls-vendored"] rustls-tls-native-roots = ["livekit-api/rustls-tls-native-roots"] rustls-tls-webpki-roots = ["livekit-api/rustls-tls-webpki-roots"] __rustls-tls = ["livekit-api/__rustls-tls"] - -# internal features (used by livekit-ffi) -__lk-internal = [] +__lk-internal = [] # internal features (used by livekit-ffi) +__lk-e2e-test = [] # end-to-end testing with a LiveKit server [dependencies] livekit-runtime = { workspace = true, default-features = false } @@ -45,3 +44,6 @@ semver = "1.0" libloading = { version = "0.8.6" } bytes = "1.10.1" bmrng = "0.5.2" + +[dev-dependencies] +anyhow = "1.0.99" \ No newline at end of file diff --git a/livekit/src/room/mod.rs b/livekit/src/room/mod.rs index ee428e595..de72cb808 100644 --- a/livekit/src/room/mod.rs +++ b/livekit/src/room/mod.rs @@ -354,14 +354,6 @@ pub struct RoomOptions { pub rtc_config: RtcConfiguration, pub join_retries: u32, pub sdk_options: RoomSdkOptions, - pub preregistration: Option, -} - -#[derive(Debug, Clone)] -#[non_exhaustive] -pub struct PreRegistration { - text_stream_topics: Vec, - byte_stream_topics: Vec, } impl Default for RoomOptions { @@ -381,7 +373,6 @@ impl Default for RoomOptions { }, join_retries: 3, sdk_options: RoomSdkOptions::default(), - preregistration: None, } } } @@ -577,8 +568,6 @@ impl Room { let (incoming_stream_manager, open_rx) = IncomingStreamManager::new(); let (outgoing_stream_manager, packet_rx) = OutgoingStreamManager::new(); - let identity = local_participant.identity().clone(); - let room_info = join_response.room.unwrap(); let inner = Arc::new(RoomSession { sid_promise: Promise::new(), @@ -671,7 +660,6 @@ impl Room { )); let outgoing_stream_handle = livekit_runtime::spawn(outgoing_data_stream_task( packet_rx, - identity, rtc_engine.clone(), close_rx.resubscribe(), )); @@ -1187,8 +1175,7 @@ impl RoomSession { }), publish_tracks: self.local_participant.published_tracks_info(), data_channels: dcs, - // unimplemented, stubbed for now - datachannel_receive_states: Vec::new(), + datachannel_receive_states: session.data_channel_receive_states(), }; log::debug!("sending sync state {:?}", sync_state); @@ -1737,15 +1724,12 @@ async fn incoming_data_stream_task( /// Receives packets from the outgoing stream manager and send them. async fn outgoing_data_stream_task( mut packet_rx: UnboundedRequestReceiver>, - participant_identity: ParticipantIdentity, engine: Arc, mut close_rx: broadcast::Receiver<()>, ) { loop { tokio::select! { - Ok((mut packet, responder)) = packet_rx.recv() => { - // Set packet's participant identity field - packet.participant_identity = participant_identity.0.clone(); + Ok((packet, responder)) = packet_rx.recv() => { let result = engine.publish_data(packet, DataPacketKind::Reliable).await; let _ = responder.respond(result); }, diff --git a/livekit/src/room/utils/mod.rs b/livekit/src/room/utils/mod.rs index 0cc43b69b..e534425a0 100644 --- a/livekit/src/room/utils/mod.rs +++ b/livekit/src/room/utils/mod.rs @@ -1,6 +1,8 @@ use std::collections::HashMap; pub mod take_cell; +pub(crate) mod ttl_map; +pub(crate) mod tx_queue; pub mod utf8_chunk; pub fn calculate_changed_attributes( diff --git a/livekit/src/room/utils/ttl_map.rs b/livekit/src/room/utils/ttl_map.rs new file mode 100644 index 000000000..bb67a7442 --- /dev/null +++ b/livekit/src/room/utils/ttl_map.rs @@ -0,0 +1,143 @@ +// Copyright 2025 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::{ + collections::HashMap, + fmt::Debug, + hash::Hash, + time::{Duration, SystemTime}, +}; + +/// Time to live (TTL) map +/// +/// Elements older than the TTL duration are automatically removed. +/// +#[derive(Debug)] +pub struct TtlMap { + inner: HashMap>, + last_cleanup: SystemTime, + ttl: Duration, +} + +#[derive(Debug)] +struct Entry { + value: V, + expires_at: SystemTime, +} + +impl TtlMap { + /// Creates an empty `TtlMap`. + pub fn new(ttl: Duration) -> Self { + Self { inner: HashMap::new(), last_cleanup: SystemTime::now(), ttl } + } + + /// Returns the number of elements in the map. + pub fn len(&mut self) -> usize { + self.cleanup(); + self.inner.len() + } + + /// An iterator visiting all key-value pairs in arbitrary order. + /// The iterator element type is `(&'a K, &'a V)`. + pub fn iter(&mut self) -> impl Iterator { + self.cleanup(); + self.inner.iter().map(|(key, entry)| (key, &entry.value)) + } + + /// Removes expired elements. + fn cleanup(&mut self) { + let now = SystemTime::now(); + self.inner.retain(|_, entry| entry.expires_at >= now); + self.last_cleanup = now; + } +} + +impl TtlMap +where + K: Eq + Hash + Clone, +{ + /// Returns a reference to the value corresponding to the key. + pub fn get(&mut self, k: &K) -> Option<&V> { + let expires_at = self.inner.get(k).map(|entry| entry.expires_at)?; + let now = SystemTime::now(); + if expires_at < now { + _ = self.inner.remove(k); + return None; + } + Some(&self.inner.get(k).unwrap().value) + } + + /// Sets the value for the given key. + pub fn set(&mut self, k: &K, v: Option) { + let now = SystemTime::now(); + let Ok(elapsed) = now.duration_since(self.last_cleanup) else { + log::error!("System clock anomaly detected"); + return; + }; + let half_ttl = self.ttl.div_f64(2.0); + if elapsed > half_ttl { + self.cleanup(); + } + + let Some(value) = v else { + _ = self.inner.remove(&k); + return; + }; + let expires_at = now + self.ttl; + let entry = Entry { value, expires_at }; + self.inner.insert(k.clone(), entry); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::HashSet; + use tokio::time::sleep; + + const SHORT_TTL: Duration = Duration::from_millis(100); + + #[tokio::test] + async fn test_expiration() { + let mut map = TtlMap::::new(SHORT_TTL); + map.set(&'a', Some(1)); + map.set(&'b', Some(2)); + map.set(&'c', Some(3)); + + assert_eq!(map.len(), 3); + assert!(map.get(&'a').is_some()); + assert!(map.get(&'b').is_some()); + assert!(map.get(&'c').is_some()); + + sleep(SHORT_TTL).await; + + assert_eq!(map.len(), 0); + assert!(map.get(&'a').is_none()); + assert!(map.get(&'b').is_none()); + assert!(map.get(&'c').is_none()); + } + + #[test] + fn test_iter() { + let mut map = TtlMap::::new(SHORT_TTL); + map.set(&'a', Some(1)); + map.set(&'b', Some(2)); + map.set(&'c', Some(3)); + + let elements: HashSet<_> = map.iter().map(|(k, v)| (*k, *v)).collect(); + assert!(elements.contains(&('a', 1))); + assert!(elements.contains(&('b', 2))); + assert!(elements.contains(&('c', 3))); + } +} diff --git a/livekit/src/room/utils/tx_queue.rs b/livekit/src/room/utils/tx_queue.rs new file mode 100644 index 000000000..5a73dd15a --- /dev/null +++ b/livekit/src/room/utils/tx_queue.rs @@ -0,0 +1,113 @@ +// Copyright 2025 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::collections::VecDeque; + +#[derive(Debug)] +pub struct TxQueue { + inner: VecDeque, + buffered_size: usize, +} + +impl TxQueue { + /// Creates an empty queue. + pub fn new() -> Self { + Self { inner: VecDeque::new(), buffered_size: 0 } + } + + /// Number of elements in the queue. + pub fn len(&self) -> usize { + self.inner.len() + } + + /// Total size in bytes of all items currently in the queue. + pub fn buffered_size(&self) -> usize { + self.buffered_size + } + + /// Provides a reference to the front element, or `None if the queue is empty. + pub fn peek(&self) -> Option<&T> { + self.inner.front() + } + + /// Appends an item to the back of the queue. + pub fn enqueue(&mut self, item: T) { + let size = item.buffered_size(); + self.inner.push_back(item); + self.buffered_size += size; + } + + /// Removes the first item and returns it, or `None` if the queue is empty. + pub fn dequeue(&mut self) -> Option { + let item = self.inner.pop_front()?; + let size = item.buffered_size(); + self.buffered_size -= size; + return Some(item); + } + + /// Dequeue and discard items until the buffered size is less than or + /// equal to the given target. + pub fn trim(&mut self, target_buffer_size: usize) { + while self.buffered_size > target_buffer_size { + _ = self.dequeue() + } + } +} + +/// Item in a [`TxQueue`]. +pub trait TxQueueItem { + /// Amount in bytes this item adds to the [`TxQueue`]'s buffered size. + fn buffered_size(&self) -> usize; +} + +impl TxQueueItem for Vec { + fn buffered_size(&self) -> usize { + self.len() + } +} + +#[cfg(test)] +mod tests { + use super::TxQueue; + + #[test] + fn test_buffered_size() { + let mut queue = TxQueue::new(); + + queue.enqueue(vec![0xFF, 0xFA]); + assert_eq!(queue.buffered_size(), 2); + + queue.enqueue(vec![0x0F, 0xFC, 0xAF]); + assert_eq!(queue.buffered_size(), 5); + + assert_eq!(queue.dequeue(), Some(vec![0xFF, 0xFA])); + assert_eq!(queue.buffered_size, 3); + + assert_eq!(queue.dequeue(), Some(vec![0x0F, 0xFC, 0xAF])); + assert_eq!(queue.buffered_size, 0); + + assert_eq!(queue.dequeue(), None); + } + + #[test] + fn test_trim() { + let mut queue = TxQueue::new(); + queue.enqueue(vec![0xFF, 0xFA]); + queue.enqueue(vec![0x0F, 0xFC, 0xAF]); + queue.enqueue(vec![0xAA]); + + queue.trim(1); + assert_eq!(queue.buffered_size(), 1); + } +} diff --git a/livekit/src/rtc_engine/rtc_events.rs b/livekit/src/rtc_engine/rtc_events.rs index 004fb2292..8206a8bb0 100644 --- a/livekit/src/rtc_engine/rtc_events.rs +++ b/livekit/src/rtc_engine/rtc_events.rs @@ -17,7 +17,10 @@ use livekit_protocol as proto; use tokio::sync::mpsc; use super::peer_transport::PeerTransport; -use crate::{rtc_engine::peer_transport::OnOfferCreated, DataPacketKind}; +use crate::{ + rtc_engine::{peer_transport::OnOfferCreated, rtc_session::RELIABLE_DC_LABEL}, + DataPacketKind, +}; pub type RtcEmitter = mpsc::UnboundedSender; pub type RtcEvents = mpsc::UnboundedReceiver; @@ -50,6 +53,7 @@ pub enum RtcEvent { Data { data: Vec, binary: bool, + kind: DataPacketKind, }, DataChannelBufferedAmountChange { sent: u64, @@ -90,7 +94,12 @@ fn on_data_channel( emitter: RtcEmitter, ) -> rtc::peer_connection::OnDataChannel { Box::new(move |data_channel| { - data_channel.on_message(Some(on_message(emitter.clone()))); + let kind = if data_channel.label() == RELIABLE_DC_LABEL { + DataPacketKind::Reliable + } else { + DataPacketKind::Lossy + }; + data_channel.on_message(Some(on_message(emitter.clone(), kind))); let _ = emitter.send(RtcEvent::DataChannel { data_channel, target }); }) @@ -140,9 +149,13 @@ pub fn forward_pc_events(transport: &mut PeerTransport, rtc_emitter: RtcEmitter) transport.on_offer(Some(on_offer(signal_target, rtc_emitter))); } -fn on_message(emitter: RtcEmitter) -> rtc::data_channel::OnMessage { +fn on_message(emitter: RtcEmitter, kind: DataPacketKind) -> rtc::data_channel::OnMessage { Box::new(move |buffer| { - let _ = emitter.send(RtcEvent::Data { data: buffer.data.to_vec(), binary: buffer.binary }); + let _ = emitter.send(RtcEvent::Data { + data: buffer.data.to_vec(), + binary: buffer.binary, + kind, + }); }) } @@ -158,6 +171,6 @@ fn on_buffered_amount_change( } pub fn forward_dc_events(dc: &mut DataChannel, kind: DataPacketKind, rtc_emitter: RtcEmitter) { - dc.on_message(Some(on_message(rtc_emitter.clone()))); + dc.on_message(Some(on_message(rtc_emitter.clone(), kind))); dc.on_buffered_amount_change(Some(on_buffered_amount_change(rtc_emitter, dc.clone(), kind))); } diff --git a/livekit/src/rtc_engine/rtc_session.rs b/livekit/src/rtc_engine/rtc_session.rs index 9ea670072..a95c3aa27 100644 --- a/livekit/src/rtc_engine/rtc_session.rs +++ b/livekit/src/rtc_engine/rtc_session.rs @@ -16,9 +16,8 @@ use std::{ collections::{HashMap, VecDeque}, convert::TryInto, fmt::Debug, - ops::Not, sync::{ - atomic::{AtomicBool, AtomicU64, Ordering}, + atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering}, Arc, }, time::Duration, @@ -38,7 +37,14 @@ use serde::{Deserialize, Serialize}; use tokio::sync::{mpsc, oneshot, watch, Notify}; use super::{rtc_events, EngineError, EngineOptions, EngineResult, SimulateScenario}; -use crate::{id::ParticipantIdentity, ChatMessage, TranscriptionSegment}; +use crate::{ + id::ParticipantIdentity, + utils::{ + ttl_map::TtlMap, + tx_queue::{TxQueue, TxQueueItem}, + }, + ChatMessage, TranscriptionSegment, +}; use crate::{ id::ParticipantSid, options::TrackPublishOptions, @@ -57,6 +63,7 @@ pub const ICE_CONNECT_TIMEOUT: Duration = Duration::from_secs(15); pub const TRACK_PUBLISH_TIMEOUT: Duration = Duration::from_secs(10); pub const LOSSY_DC_LABEL: &str = "_lossy"; pub const RELIABLE_DC_LABEL: &str = "_reliable"; +pub const RELIABLE_RECEIVED_STATE_TTL: Duration = Duration::from_secs(30); pub const PUBLISHER_NEGOTIATION_FREQUENCY: Duration = Duration::from_millis(150); pub const INITIAL_BUFFERED_AMOUNT_LOW_THRESHOLD: u64 = 2 * 1024 * 1024; @@ -187,9 +194,62 @@ pub enum SessionEvent { } #[derive(Debug)] -enum DataChannelEvent { - PublishData(proto::DataPacket, DataPacketKind, oneshot::Sender>), - BufferedAmountChange(u64, DataPacketKind), +struct DataChannelEvent { + kind: DataPacketKind, + detail: DataChannelEventDetail, +} + +#[derive(Debug)] +enum DataChannelEventDetail { + /// Publish data packet. + PublishPacket(PublishPacketRequest), + /// Publish data packet that has already been encoded. + PublishData(PublishDataRequest), + /// RTC buffered amount changed. + BufferedAmountChange(u64), + /// Enqueue reliable packets for retry starting from the given sequence number. + RetryFrom(u32), +} + +#[derive(Debug)] +struct PublishPacketRequest { + /// Unencoded data packewt. + packet: proto::DataPacket, + + /// Notifies the caller once the request has been fulfilled. + completion_tx: oneshot::Sender>, +} + +#[derive(Debug)] +struct PublishDataRequest { + /// Encoded data packet. + encoded_packet: EncodedPacket, + + /// Notifies the caller once the request has been fulfilled. + /// + /// For retries, this will be `None`. + /// + completion_tx: Option>>, +} + +#[derive(Debug)] +struct EncodedPacket { + /// Encoded packet data. + data: Vec, + /// Packet's sequence number from [`proto::DataPacket::sequence`]. + sequence: u32, +} + +impl Into for proto::DataPacket { + fn into(self) -> EncodedPacket { + EncodedPacket { data: self.encode_to_vec(), sequence: self.sequence } + } +} + +impl TxQueueItem for EncodedPacket { + fn buffered_size(&self) -> usize { + self.data.len() + } } #[derive(Serialize, Deserialize)] @@ -217,6 +277,15 @@ struct SessionInner { lossy_dc_buffered_amount_low_threshold: AtomicU64, reliable_dc: DataChannel, reliable_dc_buffered_amount_low_threshold: AtomicU64, + + /// Next sequence number for reliable packets. + next_packet_sequence: AtomicU32, + + /// Time to live (TTL) map between publisher SID and last sequence number. + packet_rx_state: Mutex>, + + participant_info: SessionParticipantInfo, + dc_emitter: mpsc::UnboundedSender, // Keep a strong reference to the subscriber datachannels, @@ -234,6 +303,24 @@ struct SessionInner { pending_requests: Mutex>>, } +/// Information about the local participant needed for outgoing +/// data packets. +struct SessionParticipantInfo { + sid: ParticipantSid, + identity: ParticipantIdentity, +} + +impl SessionParticipantInfo { + /// Extracts participant info from a join response. + fn from_join(join_response: &proto::JoinResponse) -> Option { + let Some(info) = &join_response.participant else { None? }; + Some(Self { + sid: info.sid.clone().try_into().ok()?, + identity: info.identity.clone().try_into().ok()?, + }) + } +} + /// This struct holds a WebRTC session /// The session changes at every reconnection /// @@ -269,6 +356,10 @@ impl RtcSession { let signal_client = Arc::new(signal_client); log::debug!("received JoinResponse: {:?}", join_response); + let Some(participant_info) = SessionParticipantInfo::from_join(&join_response) else { + Err(EngineError::Internal("Join response missing participant info".into()))? + }; + let (rtc_emitter, rtc_events) = mpsc::unbounded_channel(); let rtc_config = make_rtc_config_join(join_response.clone(), options.rtc_config.clone()); @@ -322,6 +413,9 @@ impl RtcSession { reliable_dc_buffered_amount_low_threshold: AtomicU64::new( INITIAL_BUFFERED_AMOUNT_LOW_THRESHOLD, ), + next_packet_sequence: 1.into(), + packet_rx_state: Mutex::new(TtlMap::new(RELIABLE_RECEIVED_STATE_TTL)), + participant_info, dc_emitter, sub_lossy_dc: Mutex::new(None), sub_reliable_dc: Mutex::new(None), @@ -470,6 +564,10 @@ impl RtcSession { .send(SessionEvent::DataChannelBufferedAmountLowThresholdChanged { kind, threshold }); } + pub fn data_channel_receive_states(&self) -> Vec { + self.inner.data_channel_receive_states() + } + pub async fn get_response(&self, request_id: u32) -> proto::RequestResponse { self.inner.get_response(request_id).await } @@ -574,6 +672,7 @@ impl SessionInner { let mut reliable_buffered_amount = 0; let mut lossy_queue = VecDeque::new(); let mut reliable_queue = VecDeque::new(); + let mut retry_queue = TxQueue::new(); loop { tokio::select! { @@ -583,24 +682,38 @@ impl SessionInner { break; }; - match event { - DataChannelEvent::PublishData(packet, kind, tx) => { - let data = packet.encode_to_vec(); - match kind { + match event.detail { + DataChannelEventDetail::PublishPacket(mut request) => { + if event.kind == DataPacketKind::Reliable { + request.packet.sequence = self.next_packet_sequence.fetch_add(1, Ordering::Relaxed); + } + let ev = DataChannelEvent { + kind: event.kind, + detail: DataChannelEventDetail::PublishData(PublishDataRequest { + encoded_packet: request.packet.into(), + completion_tx: request.completion_tx.into() + }) + }; + if let Err(err) = self.dc_emitter.send(ev) { + log::error!("Failed to enqueue send data request: {}", err) + } + } + DataChannelEventDetail::PublishData(request) => { + match event.kind { DataPacketKind::Lossy => { - lossy_queue.push_back((data, kind, tx)); + lossy_queue.push_back(request); let threshold = self.lossy_dc_buffered_amount_low_threshold.load(Ordering::Relaxed); - self._send_until_threshold(threshold, &mut lossy_buffered_amount, &mut lossy_queue); + self._send_until_threshold(DataPacketKind::Lossy, threshold, &mut lossy_buffered_amount, &mut lossy_queue, &mut retry_queue); } DataPacketKind::Reliable => { - reliable_queue.push_back((data, kind, tx)); + reliable_queue.push_back(request); let threshold = self.reliable_dc_buffered_amount_low_threshold.load(Ordering::Relaxed); - self._send_until_threshold(threshold, &mut reliable_buffered_amount, &mut reliable_queue); + self._send_until_threshold(DataPacketKind::Reliable, threshold, &mut reliable_buffered_amount, &mut reliable_queue, &mut retry_queue); } } } - DataChannelEvent::BufferedAmountChange(sent, kind) => { - match kind { + DataChannelEventDetail::BufferedAmountChange(sent) => { + match event.kind { DataPacketKind::Lossy => { if lossy_buffered_amount < sent { // I believe never reach here but adding logs just in case @@ -610,7 +723,7 @@ impl SessionInner { lossy_buffered_amount -= sent; } let threshold = self.lossy_dc_buffered_amount_low_threshold.load(Ordering::Relaxed); - self._send_until_threshold(threshold, &mut lossy_buffered_amount, &mut lossy_queue); + self._send_until_threshold(DataPacketKind::Lossy, threshold, &mut lossy_buffered_amount, &mut lossy_queue, &mut retry_queue); } DataPacketKind::Reliable => { if reliable_buffered_amount < sent { @@ -620,10 +733,15 @@ impl SessionInner { reliable_buffered_amount -= sent; } let threshold = self.reliable_dc_buffered_amount_low_threshold.load(Ordering::Relaxed); - self._send_until_threshold(threshold, &mut reliable_buffered_amount, &mut reliable_queue); + self._send_until_threshold(DataPacketKind::Reliable, threshold, &mut reliable_buffered_amount, &mut reliable_queue, &mut retry_queue); + retry_queue.trim(sent as usize); } } } + DataChannelEventDetail::RetryFrom(last_sequence) => { + assert!(event.kind == DataPacketKind::Reliable); + self._enqueue_for_retry_from(last_sequence, &mut retry_queue); + } } }, @@ -638,26 +756,80 @@ impl SessionInner { fn _send_until_threshold( self: &Arc, + kind: DataPacketKind, threshold: u64, buffered_amount: &mut u64, - queue: &mut VecDeque<(Vec, DataPacketKind, oneshot::Sender>)>, + request_queue: &mut VecDeque, + retry_queue: &mut TxQueue, ) { while *buffered_amount <= threshold { - let Some((data, kind, tx)) = queue.pop_front() else { + let Some(request) = request_queue.pop_front() else { break; }; - - *buffered_amount += data.len() as u64; + *buffered_amount += request.encoded_packet.data.len() as u64; let result = self .data_channel(SignalTarget::Publisher, kind) .unwrap() - .send(&data, true) + .send(&request.encoded_packet.data, true) .map_err(|err| { EngineError::Internal(format!("failed to send data packet: {:?}", err).into()) }); + if let Some(completion_tx) = request.completion_tx { + _ = completion_tx.send(result); + } + if kind == DataPacketKind::Reliable { + retry_queue.enqueue(request.encoded_packet); + } + } + } + + fn _enqueue_for_retry_from( + self: &Arc, + last_sequence: u32, + retry_queue: &mut TxQueue, + ) { + if let Some(first) = retry_queue.peek() { + if first.sequence > last_sequence + 1 { + log::warn!( + "Wrong packet sequence while retrying: {} > {}, {} packets missing", + first.sequence, + last_sequence + 1, + first.sequence - last_sequence - 1 + ); + } + } + + while let Some(encoded_packet) = retry_queue.dequeue() { + if encoded_packet.sequence <= last_sequence { + continue; + }; + let ev = DataChannelEvent { + kind: DataPacketKind::Reliable, + detail: DataChannelEventDetail::PublishData(PublishDataRequest { + encoded_packet, + completion_tx: None, + }), + }; + if let Err(err) = self.dc_emitter.send(ev) { + log::error!("Failed to enqueue data for retry: {}", err); + } + } + } - let _ = tx.send(result); + /// Updates the packet receive state (TTL map) for reliable packets. + fn update_packet_rx_state(&self, packet: &proto::DataPacket) { + if packet.sequence <= 0 || packet.participant_sid.is_empty() { + return; + }; + let mut rx_state = self.packet_rx_state.lock(); + if rx_state + .get(&packet.participant_sid) + .is_some_and(|&last_sequence| packet.sequence <= last_sequence) + { + log::warn!("Ignoring duplicate/out-of-order reliable data message"); + return; } + rx_state.set(&packet.participant_sid, Some(packet.sequence)); } async fn on_signal_event(&self, event: proto::signal_response::Message) -> EngineResult<()> { @@ -825,149 +997,26 @@ impl SessionInner { log::warn!("Track event with no streams"); } } - RtcEvent::Data { data, binary } => { + RtcEvent::Data { data, binary, kind } => { if !binary { Err(EngineError::Internal("text messages aren't supported".into()))?; } - - let data = proto::DataPacket::decode(&*data).unwrap(); - if let Some(packet) = data.value.as_ref() { - match packet { - proto::data_packet::Value::User(user) => { - let participant_sid = user - .participant_sid - .is_empty() - .not() - .then_some(user.participant_sid.clone()); - - let participant_identity = if !data.participant_identity.is_empty() { - Some(data.participant_identity.clone()) - } else if !user.participant_identity.is_empty() { - Some(user.participant_identity.clone()) - } else { - None - }; - - let _ = self.emitter.send(SessionEvent::Data { - kind: data.kind().into(), - participant_sid: participant_sid.map(|s| s.try_into().unwrap()), - participant_identity: participant_identity - .map(|s| s.try_into().unwrap()), - payload: user.payload.clone(), - topic: user.topic.clone(), - }); - } - proto::data_packet::Value::SipDtmf(dtmf) => { - let participant_identity = data - .participant_identity - .is_empty() - .not() - .then_some(data.participant_identity.clone()); - let digit = dtmf.digit.is_empty().not().then_some(dtmf.digit.clone()); - - let _ = self.emitter.send(SessionEvent::SipDTMF { - participant_identity: participant_identity - .map(|s| s.try_into().unwrap()), - digit: digit.map(|s| s.try_into().unwrap()), - code: dtmf.code, - }); - } - proto::data_packet::Value::Speaker(_) => {} - proto::data_packet::Value::Transcription(transcription) => { - let track_sid = transcription.track_id.clone(); - // let segments = transcription.segments.clone(); - let segments = transcription - .segments - .iter() - .map(|s| TranscriptionSegment { - id: s.id.clone(), - start_time: s.start_time, - end_time: s.end_time, - text: s.text.clone(), - language: s.language.clone(), - r#final: s.r#final, - }) - .collect(); - let participant_identity: ParticipantIdentity = - transcription.transcribed_participant_identity.clone().into(); - let _ = self.emitter.send(SessionEvent::Transcription { - participant_identity, - track_sid, - segments, - }); - } - proto::data_packet::Value::RpcRequest(rpc_request) => { - let caller_identity = data - .participant_identity - .is_empty() - .not() - .then_some(data.participant_identity.clone()) - .map(|s| s.try_into().unwrap()); - let _ = self.emitter.send(SessionEvent::RpcRequest { - caller_identity, - request_id: rpc_request.id.clone(), - method: rpc_request.method.clone(), - payload: rpc_request.payload.clone(), - response_timeout: Duration::from_millis( - rpc_request.response_timeout_ms as u64, - ), - version: rpc_request.version, - }); - } - proto::data_packet::Value::RpcResponse(rpc_response) => { - let _ = self.emitter.send(SessionEvent::RpcResponse { - request_id: rpc_response.request_id.clone(), - payload: rpc_response.value.as_ref().and_then(|v| match v { - proto::rpc_response::Value::Payload(payload) => { - Some(payload.clone()) - } - _ => None, - }), - error: rpc_response.value.as_ref().and_then(|v| match v { - proto::rpc_response::Value::Error(error) => Some(error.clone()), - _ => None, - }), - }); - } - proto::data_packet::Value::RpcAck(rpc_ack) => { - let _ = self.emitter.send(SessionEvent::RpcAck { - request_id: rpc_ack.request_id.clone(), - }); - } - proto::data_packet::Value::ChatMessage(message) => { - let _ = self.emitter.send(SessionEvent::ChatMessage { - participant_identity: ParticipantIdentity( - data.participant_identity, - ), - message: ChatMessage::from(message.clone()), - }); - } - proto::data_packet::Value::StreamHeader(message) => { - let _ = self.emitter.send(SessionEvent::DataStreamHeader { - header: message.clone(), - participant_identity: data.participant_identity.clone(), - }); - } - proto::data_packet::Value::StreamChunk(message) => { - let _ = self.emitter.send(SessionEvent::DataStreamChunk { - chunk: message.clone(), - participant_identity: data.participant_identity.clone(), - }); - } - proto::data_packet::Value::StreamTrailer(message) => { - let _ = self.emitter.send(SessionEvent::DataStreamTrailer { - trailer: message.clone(), - participant_identity: data.participant_identity.clone(), - }); - } - _ => {} - } + let mut packet = proto::DataPacket::decode(&*data).map_err(|err| { + EngineError::Internal(format!("failed to decode data packet: {}", err).into()) + })?; + if kind == DataPacketKind::Reliable { + self.update_packet_rx_state(&packet); + } + if let Some(detail) = packet.value.take() { + self.emit_incoming_packet(kind, packet, detail); } } RtcEvent::DataChannelBufferedAmountChange { sent, amount: _, kind } => { - if let Err(err) = - self.dc_emitter.send(DataChannelEvent::BufferedAmountChange(sent, kind)) - { + let ev = DataChannelEvent { + kind, + detail: DataChannelEventDetail::BufferedAmountChange(sent), + }; + if let Err(err) = self.dc_emitter.send(ev) { log::error!("failed to send dc_event buffer_amount_change: {:?}", err); } } @@ -976,6 +1025,121 @@ impl SessionInner { Ok(()) } + fn emit_incoming_packet( + &self, + kind: DataPacketKind, + packet: proto::DataPacket, + value: proto::data_packet::Value, + ) { + // TODO: Standardize how participant identity is emitted in events; + // Option, ParticipantIdentity, and String are all used. + let participant_sid: Option = packet.participant_sid.try_into().ok(); + let participant_identity: Option = + packet.participant_identity.try_into().ok(); + + let send_result = match value { + proto::data_packet::Value::User(user) => { + // Participant SID and identity used to be defined on user packet, but + // they have been moved to the packet root. For backwards compatibility, + // we take the user packet's values if the top-level fields are not set. + let participant_sid = participant_sid + .is_none() + .then_some(user.participant_sid) + .and_then(|sid| sid.try_into().ok()); + let participant_identity = participant_identity + .is_none() + .then_some(user.participant_identity) + .and_then(|identity| identity.try_into().ok()); + + self.emitter.send(SessionEvent::Data { + kind, + participant_sid, + participant_identity, + payload: user.payload, + topic: user.topic, + }) + } + proto::data_packet::Value::SipDtmf(dtmf) => self.emitter.send(SessionEvent::SipDTMF { + participant_identity, + digit: (!dtmf.digit.is_empty()).then_some(dtmf.digit), + code: dtmf.code, + }), + proto::data_packet::Value::Transcription(transcription) => { + let segments = transcription + .segments + .into_iter() + .map(|s| TranscriptionSegment { + id: s.id, + start_time: s.start_time, + end_time: s.end_time, + text: s.text, + language: s.language, + r#final: s.r#final, + }) + .collect(); + let participant_identity = transcription.transcribed_participant_identity.into(); + + self.emitter.send(SessionEvent::Transcription { + participant_identity, + track_sid: transcription.track_id, + segments, + }) + } + proto::data_packet::Value::RpcRequest(rpc_request) => { + let caller_identity = participant_identity; + self.emitter.send(SessionEvent::RpcRequest { + caller_identity, + request_id: rpc_request.id, + method: rpc_request.method, + payload: rpc_request.payload, + response_timeout: Duration::from_millis(rpc_request.response_timeout_ms as u64), + version: rpc_request.version, + }) + } + proto::data_packet::Value::RpcResponse(rpc_response) => { + let (payload, error) = match rpc_response.value { + None => (None, None), + Some(proto::rpc_response::Value::Payload(payload)) => (Some(payload), None), + Some(proto::rpc_response::Value::Error(err)) => (None, Some(err)), + }; + self.emitter.send(SessionEvent::RpcResponse { + request_id: rpc_response.request_id, + payload, + error, + }) + } + proto::data_packet::Value::RpcAck(rpc_ack) => { + self.emitter.send(SessionEvent::RpcAck { request_id: rpc_ack.request_id }) + } + proto::data_packet::Value::ChatMessage(message) => { + self.emitter.send(SessionEvent::ChatMessage { + participant_identity: participant_identity + .unwrap_or(ParticipantIdentity("".into())), + message: ChatMessage::from(message), + }) + } + proto::data_packet::Value::StreamHeader(header) => { + let participant_identity = + participant_identity.map_or("".into(), |identity| identity.0); + self.emitter.send(SessionEvent::DataStreamHeader { header, participant_identity }) + } + proto::data_packet::Value::StreamChunk(chunk) => { + let participant_identity = + participant_identity.map_or("".into(), |identity| identity.0); + self.emitter.send(SessionEvent::DataStreamChunk { chunk, participant_identity }) + } + proto::data_packet::Value::StreamTrailer(trailer) => { + let participant_identity = + participant_identity.map_or("".into(), |identity| identity.0); + self.emitter.send(SessionEvent::DataStreamTrailer { trailer, participant_identity }) + } + _ => Ok(()), + }; + if let Err(err) = send_result { + log::error!("failed to emit incoming data packet: {:?}", err); + } + } + async fn add_track(&self, req: proto::AddTrackRequest) -> EngineResult { let (tx, rx) = oneshot::channel(); let cid = req.cid.clone(); @@ -1178,18 +1342,29 @@ impl SessionInner { async fn publish_data( self: &Arc, - data: proto::DataPacket, + mut packet: proto::DataPacket, kind: DataPacketKind, ) -> Result<(), EngineError> { self.ensure_publisher_connected(kind).await?; - let (tx, rx) = oneshot::channel(); - if let Err(err) = self.dc_emitter.send(DataChannelEvent::PublishData(data, kind, tx)) { + // Populate local participant info fields + packet.participant_identity = self.participant_info.identity.to_string(); + packet.participant_sid = self.participant_info.sid.to_string(); + + let (completion_tx, completion_rx) = oneshot::channel(); + let ev = DataChannelEvent { + kind, + detail: DataChannelEventDetail::PublishPacket(PublishPacketRequest { + packet, + completion_tx, + }), + }; + if let Err(err) = self.dc_emitter.send(ev) { return Err(EngineError::Internal( - format!("failed to push data into queue: {:?}", err).into(), + format!("Failed to enqueue publish packet request: {:?}", err).into(), )); }; - rx.await.map_err(|e| { + completion_rx.await.map_err(|e| { EngineError::Internal(format!("failed to receive data from dc_task: {:?}", e).into()) })? } @@ -1205,6 +1380,14 @@ impl SessionInner { self.publisher_pc.peer_connection().set_configuration(rtc_config.clone())?; self.subscriber_pc.peer_connection().set_configuration(rtc_config)?; + let ev = DataChannelEvent { + kind: DataPacketKind::Reliable, + detail: DataChannelEventDetail::RetryFrom(reconnect_response.last_message_seq), + }; + if let Err(err) = self.dc_emitter.send(ev) { + log::error!("Failed to request reliable retry: {:?}", err); + } + Ok(reconnect_response) } @@ -1413,6 +1596,17 @@ impl SessionInner { } } + fn data_channel_receive_states(self: &Arc) -> Vec { + let mut state = self.packet_rx_state.lock(); + state + .iter() + .map(|(publisher_sid, last_seq)| proto::DataChannelReceiveState { + publisher_sid: publisher_sid.to_string(), + last_seq: *last_seq, + }) + .collect() + } + async fn get_response(&self, request_id: u32) -> proto::RequestResponse { let (tx, rx) = oneshot::channel(); self.pending_requests.lock().insert(request_id, tx); diff --git a/livekit/tests/common/mod.rs b/livekit/tests/common/mod.rs new file mode 100644 index 000000000..676c758c4 --- /dev/null +++ b/livekit/tests/common/mod.rs @@ -0,0 +1,56 @@ +use anyhow::{Context, Result}; +use futures_util::future::try_join_all; +use libwebrtc::native::create_random_uuid; +use livekit::{Room, RoomEvent, RoomOptions}; +use livekit_api::access_token::{AccessToken, VideoGrants}; +use std::{env, time::Duration}; +use tokio::sync::mpsc::UnboundedReceiver; + +struct TestEnvironment { + api_key: String, + api_secret: String, + server_url: String, +} + +impl TestEnvironment { + /// Reads API key, secret, and server URL from the environment, using the + /// development defaults for values that are not present. + pub fn from_env_or_defaults() -> Self { + Self { + api_key: env::var("LIVEKIT_API_KEY").unwrap_or("devkey".into()), + api_secret: env::var("LIVEKIT_API_SECRET").unwrap_or("secret".into()), + server_url: env::var("LIVEKIT_URL").unwrap_or("http://localhost:7880".into()), + } + } +} + +/// Creates the specified number of connections to a shared room for testing. +pub async fn test_rooms(count: usize) -> Result)>> { + let test_env = TestEnvironment::from_env_or_defaults(); + let room_name = format!("test_room_{}", create_random_uuid()); + + let tokens = (0..count) + .into_iter() + .map(|id| -> Result { + let grants = + VideoGrants { room_join: true, room: room_name.clone(), ..Default::default() }; + Ok(AccessToken::with_api_key(&test_env.api_key, &test_env.api_secret) + .with_ttl(Duration::from_secs(30 * 60)) // 30 minutes + .with_grants(grants) + .with_identity(&format!("p{}", id)) + .to_jwt() + .context("Failed to generate JWT")?) + }) + .collect::>>()?; + + let rooms = try_join_all(tokens.into_iter().map(|token| { + let server_url = test_env.server_url.clone(); + async move { + let options = RoomOptions::default(); + Room::connect(&server_url, &token, options).await.context("Failed to connect to room") + } + })) + .await?; + + Ok(rooms) +} diff --git a/livekit/tests/data_channel_test.rs b/livekit/tests/data_channel_test.rs new file mode 100644 index 000000000..ba155aa8f --- /dev/null +++ b/livekit/tests/data_channel_test.rs @@ -0,0 +1,85 @@ +#![allow(unused_imports)] +use crate::common::test_rooms; +use anyhow::{anyhow, Result}; +use livekit::{DataPacket, RoomEvent, SimulateScenario}; +use std::{sync::Arc, time::Duration}; +use tokio::{sync::oneshot, time}; + +mod common; + +// These tests depend on a LiveKit server, and thus are not enabled by default; +// to run them, start a local LiveKit server in development mode, and enable the +// E2E test feature: +// +// > livekit-server --dev +// > cargo test --features __lk-e2e-test +// + +#[cfg(feature = "__lk-e2e-test")] +#[tokio::test] +async fn test_reliable_retry() -> Result<()> { + const ITERATIONS: usize = 128; + const PAYLOAD_SIZE: usize = 4096; + + // Set up test rooms + let mut rooms = test_rooms(2).await?; + let (sending_room, _) = rooms.pop().unwrap(); + let (receiving_room, mut receiving_event_rx) = rooms.pop().unwrap(); + + let sending_room = Arc::new(sending_room); + let receiving_room = Arc::new(receiving_room); + + let receiving_identity = receiving_room.local_participant().identity(); + let (fulfill, expectation) = oneshot::channel(); + + tokio::spawn({ + let sending_room = sending_room.clone(); + async move { + time::sleep(Duration::from_millis(200)).await; + _ = sending_room.simulate_scenario(SimulateScenario::SignalReconnect).await; + println!("Reconnecting sending room"); + } + }); + tokio::spawn({ + let receiving_room = receiving_room.clone(); + async move { + time::sleep(Duration::from_millis(400)).await; + _ = receiving_room.simulate_scenario(SimulateScenario::SignalReconnect).await; + println!("Reconnecting receiving room"); + } + }); + + tokio::spawn({ + let fulfill = fulfill; + async move { + let mut packets_received = 0; + while let Some(event) = receiving_event_rx.recv().await { + if let RoomEvent::DataReceived { payload, .. } = event { + assert_eq!(payload.len(), PAYLOAD_SIZE); + packets_received += 1; + if packets_received == ITERATIONS { + fulfill.send(()).ok(); + break; + } + } + } + } + }); + + for _ in 0..ITERATIONS { + let packet = DataPacket { + reliable: true, + payload: [0xFA; PAYLOAD_SIZE].to_vec(), + destination_identities: vec![receiving_identity.clone()], + ..Default::default() + }; + sending_room.local_participant().publish_data(packet).await?; + time::sleep(Duration::from_millis(10)).await; + } + + match time::timeout(Duration::from_secs(15), expectation).await { + Ok(Ok(())) => Ok(()), + Ok(Err(_)) => Err(anyhow!("Not all packets were received")), + Err(_) => Err(anyhow!("Timed out waiting for packets")), + } +} From 553047a1d89fe3dca36d29a3c263f44d6c6efc80 Mon Sep 17 00:00:00 2001 From: CloudWebRTC Date: Tue, 9 Sep 2025 16:15:53 +0800 Subject: [PATCH 239/274] chore: Optional flags for video hw codec. (#701) * chore: Optional flags for video hw codec. * Update Cargo.toml * enable hw codec for examples/wgpu_room. --- examples/wgpu_room/Cargo.toml | 1 + livekit-ffi/Cargo.toml | 1 + webrtc-sys/Cargo.toml | 5 +++++ webrtc-sys/build.rs | 8 ++++++-- webrtc-sys/src/video_decoder_factory.cpp | 4 ++-- webrtc-sys/src/video_encoder_factory.cpp | 19 +++++++++++++++---- yuv-sys/Cargo.toml | 2 +- 7 files changed, 31 insertions(+), 9 deletions(-) diff --git a/examples/wgpu_room/Cargo.toml b/examples/wgpu_room/Cargo.toml index 1f76dbdd4..6030d64cd 100644 --- a/examples/wgpu_room/Cargo.toml +++ b/examples/wgpu_room/Cargo.toml @@ -10,6 +10,7 @@ tracing = ["console-subscriber", "tokio/tracing"] [dependencies] tokio = { version = "1", features = ["full", "parking_lot"] } livekit = { path = "../../livekit", features = ["native-tls"] } +webrtc-sys = { path = "../../webrtc-sys", features = [ "use_vaapi", "use_nvidia" ] } futures = "0.3" winit = "0.30.11" parking_lot = { version = "0.12.1", features = ["deadlock_detection"] } diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index e3836bd0e..44441d81b 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -19,6 +19,7 @@ tracing = ["tokio/tracing", "console-subscriber"] [dependencies] livekit = { workspace = true } +webrtc-sys = { workspace = true , features = [ "use_vaapi", "use_nvidia" ]} soxr-sys = { workspace = true } imgproc = { workspace = true } livekit-protocol = { workspace = true } diff --git a/webrtc-sys/Cargo.toml b/webrtc-sys/Cargo.toml index c04ee5e14..ae9888bc9 100644 --- a/webrtc-sys/Cargo.toml +++ b/webrtc-sys/Cargo.toml @@ -7,6 +7,11 @@ license = "Apache-2.0" description = "Unsafe bindings to libwebrtc" repository = "https://github.com/livekit/client-sdk-rust" +[features] +default = [] +use_vaapi = [] +use_nvidia = [] + [dependencies] cxx = "1.0" log = "0.4" diff --git a/webrtc-sys/build.rs b/webrtc-sys/build.rs index 4b897fd3f..b022ea7d3 100644 --- a/webrtc-sys/build.rs +++ b/webrtc-sys/build.rs @@ -155,6 +155,7 @@ fn main() { match target_arch.as_str() { "x86_64" => { + #[cfg(feature = "use_vaapi")] builder .file("src/vaapi/vaapi_display_drm.cpp") .file("src/vaapi/vaapi_h264_encoder_wrapper.cpp") @@ -163,8 +164,10 @@ fn main() { .file("src/vaapi/implib/libva-drm.so.init.c") .file("src/vaapi/implib/libva-drm.so.tramp.S") .file("src/vaapi/implib/libva.so.init.c") - .file("src/vaapi/implib/libva.so.tramp.S"); + .file("src/vaapi/implib/libva.so.tramp.S") + .flag("-DUSE_VAAPI_VIDEO_CODEC=1"); + #[cfg(feature = "use_nvidia")] builder .flag("-I/usr/local/cuda/include") .flag("-Isrc/nvidia/NvCodec/include") @@ -181,7 +184,8 @@ fn main() { .file("src/nvidia/implib/libcuda.so.tramp.S") .file("src/nvidia/implib/libnvcuvid.so.init.c") .file("src/nvidia/implib/libnvcuvid.so.tramp.S") - .flag("-Wno-deprecated-declarations"); + .flag("-Wno-deprecated-declarations") + .flag("-DUSE_NVIDIA_VIDEO_CODEC=1"); } _ => {} } diff --git a/webrtc-sys/src/video_decoder_factory.cpp b/webrtc-sys/src/video_decoder_factory.cpp index d12ad1e88..bd2dfbe1d 100644 --- a/webrtc-sys/src/video_decoder_factory.cpp +++ b/webrtc-sys/src/video_decoder_factory.cpp @@ -35,7 +35,7 @@ #include "livekit/android.h" #endif -#if defined(__linux__) && defined(__x86_64__) && !defined(WEBRTC_ANDROID) +#if defined(USE_NVIDIA_VIDEO_CODEC) #include "nvidia/nvidia_decoder_factory.h" #endif @@ -50,7 +50,7 @@ VideoDecoderFactory::VideoDecoderFactory() { factories_.push_back(CreateAndroidVideoDecoderFactory()); #endif -#if defined(__linux__) && defined(__x86_64__) && !defined(WEBRTC_ANDROID) +#if defined(USE_NVIDIA_VIDEO_CODEC) if (webrtc::NvidiaVideoDecoderFactory::IsSupported()) { factories_.push_back(std::make_unique()); } diff --git a/webrtc-sys/src/video_encoder_factory.cpp b/webrtc-sys/src/video_encoder_factory.cpp index 370a7d257..351a5c79f 100644 --- a/webrtc-sys/src/video_encoder_factory.cpp +++ b/webrtc-sys/src/video_encoder_factory.cpp @@ -37,8 +37,11 @@ #include "livekit/android.h" #endif -#if defined(__linux__) && defined(__x86_64__) && !defined(WEBRTC_ANDROID) +#if defined(USE_NVIDIA_VIDEO_CODEC) #include "nvidia/nvidia_encoder_factory.h" +#endif + +#if defined(USE_VAAPI_VIDEO_CODEC) #include "vaapi/vaapi_encoder_factory.h" #endif @@ -63,11 +66,19 @@ VideoEncoderFactory::InternalFactory::InternalFactory() { factories_.push_back(CreateAndroidVideoEncoderFactory()); #endif -#if defined(__linux__) && defined(__x86_64__) && !defined(WEBRTC_ANDROID) +#if defined(USE_NVIDIA_VIDEO_CODEC) if (webrtc::NvidiaVideoEncoderFactory::IsSupported()) { factories_.push_back(std::make_unique()); - } else if (webrtc::VAAPIVideoEncoderFactory::IsSupported()) { - factories_.push_back(std::make_unique()); + } else { +#endif + +#if defined(USE_VAAPI_VIDEO_CODEC) + if (webrtc::VAAPIVideoEncoderFactory::IsSupported()) { + factories_.push_back(std::make_unique()); + } +#endif + +#if defined(USE_NVIDIA_VIDEO_CODEC) } #endif } diff --git a/yuv-sys/Cargo.toml b/yuv-sys/Cargo.toml index 2bd72148e..c3f5c411d 100644 --- a/yuv-sys/Cargo.toml +++ b/yuv-sys/Cargo.toml @@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0" description = "libyuv bindings" [build-dependencies] -bindgen = "0.69" +bindgen = "0.72.1" cc = "1.0" regex = "1" rayon = "1.8" From 289b03677b28feb22fec67759a4a61735fdbff27 Mon Sep 17 00:00:00 2001 From: David Chen Date: Tue, 9 Sep 2025 10:34:33 -0700 Subject: [PATCH 240/274] Add option to enable audio preconnect buffer in SDK & FFI (#700) * add preconnect buffer option to ffi room proto * set AudioTrackFeatures on AddTrackRequest * add preconect to conversion from proto to TrackPublishOptions --- livekit-ffi/protocol/room.proto | 1 + livekit-ffi/src/conversion/room.rs | 3 +++ livekit-ffi/src/livekit.proto.rs | 2 ++ livekit/src/room/options.rs | 2 ++ livekit/src/room/participant/local_participant.rs | 4 ++++ 5 files changed, 12 insertions(+) diff --git a/livekit-ffi/protocol/room.proto b/livekit-ffi/protocol/room.proto index 94203d096..f1f1da372 100644 --- a/livekit-ffi/protocol/room.proto +++ b/livekit-ffi/protocol/room.proto @@ -261,6 +261,7 @@ message TrackPublishOptions { optional bool simulcast = 6; optional TrackSource source = 7; optional string stream = 8; + optional bool preconnect_buffer = 9; } enum IceTransportType { diff --git a/livekit-ffi/src/conversion/room.rs b/livekit-ffi/src/conversion/room.rs index 81772aa7f..b5001649e 100644 --- a/livekit-ffi/src/conversion/room.rs +++ b/livekit-ffi/src/conversion/room.rs @@ -232,6 +232,9 @@ impl From for TrackPublishOptions { red: opts.red.unwrap_or(default_publish_options.red), simulcast: opts.simulcast.unwrap_or(default_publish_options.simulcast), stream: opts.stream.unwrap_or(default_publish_options.stream), + preconnect_buffer: opts + .preconnect_buffer + .unwrap_or(default_publish_options.preconnect_buffer), } } } diff --git a/livekit-ffi/src/livekit.proto.rs b/livekit-ffi/src/livekit.proto.rs index 410c07b92..48bf8dda6 100644 --- a/livekit-ffi/src/livekit.proto.rs +++ b/livekit-ffi/src/livekit.proto.rs @@ -3256,6 +3256,8 @@ pub struct TrackPublishOptions { pub source: ::core::option::Option, #[prost(string, optional, tag="8")] pub stream: ::core::option::Option<::prost::alloc::string::String>, + #[prost(bool, optional, tag="9")] + pub preconnect_buffer: ::core::option::Option, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] diff --git a/livekit/src/room/options.rs b/livekit/src/room/options.rs index e17ac2145..8188e7422 100644 --- a/livekit/src/room/options.rs +++ b/livekit/src/room/options.rs @@ -85,6 +85,7 @@ pub struct TrackPublishOptions { // pub name: String, pub source: TrackSource, pub stream: String, + pub preconnect_buffer: bool, } impl Default for TrackPublishOptions { @@ -98,6 +99,7 @@ impl Default for TrackPublishOptions { simulcast: true, source: TrackSource::Unknown, stream: "".to_string(), + preconnect_buffer: false, } } } diff --git a/livekit/src/room/participant/local_participant.rs b/livekit/src/room/participant/local_participant.rs index f4a12d627..edeac969b 100644 --- a/livekit/src/room/participant/local_participant.rs +++ b/livekit/src/room/participant/local_participant.rs @@ -245,6 +245,10 @@ impl LocalParticipant { ..Default::default() }; + if options.preconnect_buffer { + req.audio_features.push(proto::AudioTrackFeature::TfPreconnectBuffer as i32); + } + let mut encodings = Vec::default(); match &track { LocalTrack::Video(video_track) => { From 89579d5033948294b989ea7c17c97b69acee4fc5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 10 Sep 2025 08:40:59 +1000 Subject: [PATCH 241/274] chore: release (#702) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- Cargo.lock | 42 ++++++++++++++-------------------------- Cargo.toml | 12 ++++++------ imgproc/CHANGELOG.md | 14 ++++++++++++++ imgproc/Cargo.toml | 2 +- libwebrtc/CHANGELOG.md | 6 ++++++ libwebrtc/Cargo.toml | 2 +- livekit-ffi/CHANGELOG.md | 8 ++++++++ livekit-ffi/Cargo.toml | 2 +- livekit/CHANGELOG.md | 7 +++++++ livekit/Cargo.toml | 4 ++-- webrtc-sys/CHANGELOG.md | 6 ++++++ webrtc-sys/Cargo.toml | 2 +- yuv-sys/CHANGELOG.md | 14 ++++++++++++++ yuv-sys/Cargo.toml | 2 +- 14 files changed, 82 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 234632fdb..f46a20ba7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -326,17 +326,15 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bindgen" -version = "0.69.2" +version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c69fae65a523209d34240b60abe0c42d33d1045d445c0839d8a4894a736e2d" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ "bitflags 2.4.1", "cexpr", "clang-sys", - "lazy_static", - "lazycell", + "itertools 0.11.0", "log", - "peeking_take_while", "prettyplease", "proc-macro2", "quote", @@ -344,7 +342,6 @@ dependencies = [ "rustc-hash", "shlex", "syn 2.0.50", - "which", ] [[package]] @@ -1343,7 +1340,7 @@ dependencies = [ [[package]] name = "imgproc" -version = "0.3.12" +version = "0.3.13" dependencies = [ "yuv-sys", ] @@ -1536,12 +1533,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - [[package]] name = "libc" version = "0.2.172" @@ -1560,7 +1551,7 @@ dependencies = [ [[package]] name = "libwebrtc" -version = "0.3.13" +version = "0.3.14" dependencies = [ "cxx", "env_logger", @@ -1616,7 +1607,7 @@ checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "livekit" -version = "0.7.17" +version = "0.7.18" dependencies = [ "anyhow", "bmrng", @@ -1671,7 +1662,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.12.32" +version = "0.12.33" dependencies = [ "bytes", "console-subscriber", @@ -1692,6 +1683,7 @@ dependencies = [ "soxr-sys", "thiserror", "tokio", + "webrtc-sys", "webrtc-sys-build", ] @@ -2009,12 +2001,6 @@ dependencies = [ "sha2", ] -[[package]] -name = "peeking_take_while" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" - [[package]] name = "percent-encoding" version = "2.3.1" @@ -2134,9 +2120,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] @@ -2436,9 +2422,9 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustc-hash" -version = "1.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustix" @@ -3368,7 +3354,7 @@ checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" [[package]] name = "webrtc-sys" -version = "0.3.10" +version = "0.3.11" dependencies = [ "cc", "cxx", @@ -3663,7 +3649,7 @@ dependencies = [ [[package]] name = "yuv-sys" -version = "0.3.7" +version = "0.3.8" dependencies = [ "bindgen", "cc", diff --git a/Cargo.toml b/Cargo.toml index 307782683..fcf72fcc6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,14 +15,14 @@ members = [ ] [workspace.dependencies] -imgproc = { version = "0.3.12", path = "imgproc" } -yuv-sys = { version = "0.3.7", path = "yuv-sys" } -libwebrtc = { version = "0.3.13", path = "libwebrtc" } +imgproc = { version = "0.3.13", path = "imgproc" } +yuv-sys = { version = "0.3.8", path = "yuv-sys" } +libwebrtc = { version = "0.3.14", path = "libwebrtc" } livekit-api = { version = "0.4.6", path = "livekit-api" } -livekit-ffi = { version = "0.12.32", path = "livekit-ffi" } +livekit-ffi = { version = "0.12.33", path = "livekit-ffi" } livekit-protocol = { version = "0.4.0", path = "livekit-protocol" } livekit-runtime = { version = "0.4.0", path = "livekit-runtime" } -livekit = { version = "0.7.17", path = "livekit" } +livekit = { version = "0.7.18", path = "livekit" } soxr-sys = { version = "0.1.0", path = "soxr-sys" } webrtc-sys-build = { version = "0.3.7", path = "webrtc-sys/build" } -webrtc-sys = { version = "0.3.10", path = "webrtc-sys" } +webrtc-sys = { version = "0.3.11", path = "webrtc-sys" } diff --git a/imgproc/CHANGELOG.md b/imgproc/CHANGELOG.md index a362d251d..7dac518b8 100644 --- a/imgproc/CHANGELOG.md +++ b/imgproc/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.3.13](https://github.com/livekit/rust-sdks/compare/rust-sdks/imgproc@0.3.12...rust-sdks/imgproc@0.3.13) - 2025-09-09 + +### Other + +- updated the following local packages: yuv-sys +# Changelog + ## [0.3.12] - 2024-12-14 ### Added diff --git a/imgproc/Cargo.toml b/imgproc/Cargo.toml index 844147d9e..c08caaf64 100644 --- a/imgproc/Cargo.toml +++ b/imgproc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "imgproc" -version = "0.3.12" +version = "0.3.13" edition = "2021" authors = ["Theo Monnom "] license = "MIT OR Apache-2.0" diff --git a/libwebrtc/CHANGELOG.md b/libwebrtc/CHANGELOG.md index 5e9b32cdb..c57fb1315 100644 --- a/libwebrtc/CHANGELOG.md +++ b/libwebrtc/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.3.14](https://github.com/livekit/rust-sdks/compare/rust-sdks/libwebrtc@0.3.13...rust-sdks/libwebrtc@0.3.14) - 2025-09-09 + +### Other + +- updated the following local packages: webrtc-sys + ## [0.3.13](https://github.com/livekit/rust-sdks/compare/rust-sdks/libwebrtc@0.3.12...rust-sdks/libwebrtc@0.3.13) - 2025-09-03 ### Other diff --git a/libwebrtc/Cargo.toml b/libwebrtc/Cargo.toml index 0df6c845d..06c5c5299 100644 --- a/libwebrtc/Cargo.toml +++ b/libwebrtc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libwebrtc" -version = "0.3.13" +version = "0.3.14" edition = "2021" homepage = "https://livekit.io" license = "Apache-2.0" diff --git a/livekit-ffi/CHANGELOG.md b/livekit-ffi/CHANGELOG.md index 273fa335c..dddaa3a59 100644 --- a/livekit-ffi/CHANGELOG.md +++ b/livekit-ffi/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.12.33](https://github.com/livekit/rust-sdks/compare/rust-sdks/livekit-ffi@0.12.32...rust-sdks/livekit-ffi@0.12.33) - 2025-09-09 + +### Other + +- Add option to enable audio preconnect buffer in SDK & FFI ([#700](https://github.com/livekit/rust-sdks/pull/700)) +- Optional flags for video hw codec. ([#701](https://github.com/livekit/rust-sdks/pull/701)) +- Data channel reliability ([#688](https://github.com/livekit/rust-sdks/pull/688)) + ## [0.12.32](https://github.com/livekit/rust-sdks/compare/rust-sdks/livekit-ffi@0.12.31...rust-sdks/livekit-ffi@0.12.32) - 2025-09-03 ### Other diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 44441d81b..5b6857f3a 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.12.32" +version = "0.12.33" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" diff --git a/livekit/CHANGELOG.md b/livekit/CHANGELOG.md index 5bb8d3bac..e92dda36b 100644 --- a/livekit/CHANGELOG.md +++ b/livekit/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.7.18](https://github.com/livekit/rust-sdks/compare/rust-sdks/livekit@0.7.17...rust-sdks/livekit@0.7.18) - 2025-09-09 + +### Other + +- Add option to enable audio preconnect buffer in SDK & FFI ([#700](https://github.com/livekit/rust-sdks/pull/700)) +- Data channel reliability ([#688](https://github.com/livekit/rust-sdks/pull/688)) + ## [0.7.17](https://github.com/livekit/rust-sdks/compare/rust-sdks/livekit@0.7.16...rust-sdks/livekit@0.7.17) - 2025-09-03 ### Other diff --git a/livekit/Cargo.toml b/livekit/Cargo.toml index 8a1cf0b04..bbaae8e33 100644 --- a/livekit/Cargo.toml +++ b/livekit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit" -version = "0.7.17" +version = "0.7.18" edition = "2021" license = "Apache-2.0" description = "Rust Client SDK for LiveKit" @@ -46,4 +46,4 @@ bytes = "1.10.1" bmrng = "0.5.2" [dev-dependencies] -anyhow = "1.0.99" \ No newline at end of file +anyhow = "1.0.99" diff --git a/webrtc-sys/CHANGELOG.md b/webrtc-sys/CHANGELOG.md index 7850a6765..94feecc8b 100644 --- a/webrtc-sys/CHANGELOG.md +++ b/webrtc-sys/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.3.11](https://github.com/livekit/rust-sdks/compare/rust-sdks/webrtc-sys@0.3.10...rust-sdks/webrtc-sys@0.3.11) - 2025-09-09 + +### Other + +- Optional flags for video hw codec. ([#701](https://github.com/livekit/rust-sdks/pull/701)) + ## [0.3.10](https://github.com/livekit/rust-sdks/compare/rust-sdks/webrtc-sys@0.3.9...rust-sdks/webrtc-sys@0.3.10) - 2025-09-03 ### Added diff --git a/webrtc-sys/Cargo.toml b/webrtc-sys/Cargo.toml index ae9888bc9..949759a3a 100644 --- a/webrtc-sys/Cargo.toml +++ b/webrtc-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "webrtc-sys" -version = "0.3.10" +version = "0.3.11" edition = "2021" homepage = "https://livekit.io" license = "Apache-2.0" diff --git a/yuv-sys/CHANGELOG.md b/yuv-sys/CHANGELOG.md index f13664759..63fbb702a 100644 --- a/yuv-sys/CHANGELOG.md +++ b/yuv-sys/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.3.8](https://github.com/livekit/rust-sdks/compare/rust-sdks/yuv-sys@0.3.7...rust-sdks/yuv-sys@0.3.8) - 2025-09-09 + +### Other + +- Optional flags for video hw codec. ([#701](https://github.com/livekit/rust-sdks/pull/701)) +# Changelog + ## [0.3.7] - 2024-12-14 ### Added diff --git a/yuv-sys/Cargo.toml b/yuv-sys/Cargo.toml index c3f5c411d..c5ce8e0e3 100644 --- a/yuv-sys/Cargo.toml +++ b/yuv-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yuv-sys" -version = "0.3.7" +version = "0.3.8" edition = "2021" authors = ["Theo Monnom "] license = "MIT OR Apache-2.0" From 9fb6b4d708c5e5d3c82926af10b63b79ef3e0b6f Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Wed, 10 Sep 2025 15:30:13 +1000 Subject: [PATCH 242/274] Upgrade protocol to v1.41.0 (#703) * Upgrade to protocol v1.41.0 * Use default for new fields --- livekit-protocol/protocol | 2 +- livekit-protocol/src/livekit.rs | 251 +++- livekit-protocol/src/livekit.serde.rs | 1579 ++++++++++++++++++++++++- livekit/src/room/options.rs | 2 + 4 files changed, 1797 insertions(+), 37 deletions(-) diff --git a/livekit-protocol/protocol b/livekit-protocol/protocol index e038e7944..2bc93ddc2 160000 --- a/livekit-protocol/protocol +++ b/livekit-protocol/protocol @@ -1 +1 @@ -Subproject commit e038e7944595dd9a00871ee5ed52ba6062f76c1e +Subproject commit 2bc93ddc27ccfa66ee8d270a1bcd115586fb601d diff --git a/livekit-protocol/src/livekit.rs b/livekit-protocol/src/livekit.rs index e39604819..676becb62 100644 --- a/livekit-protocol/src/livekit.rs +++ b/livekit-protocol/src/livekit.rs @@ -199,6 +199,12 @@ pub struct Pagination { #[prost(int32, tag="2")] pub limit: i32, } +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TokenPagination { + #[prost(string, tag="1")] + pub token: ::prost::alloc::string::String, +} /// ListUpdate is used for updated APIs where 'repeated string' field is modified. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -206,6 +212,15 @@ pub struct ListUpdate { /// set the field to a new list #[prost(string, repeated, tag="1")] pub set: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + /// append items to a list, avoiding duplicates + #[prost(string, repeated, tag="2")] + pub add: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + /// delete items from a list + #[prost(string, repeated, tag="3")] + pub del: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + /// sets the list to an empty list + #[prost(bool, tag="4")] + pub clear: bool, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -484,6 +499,14 @@ pub struct SimulcastCodecInfo { pub cid: ::prost::alloc::string::String, #[prost(message, repeated, tag="4")] pub layers: ::prost::alloc::vec::Vec, + #[prost(enumeration="video_layer::Mode", tag="5")] + pub video_layer_mode: i32, + /// cid (client side id for track) could be different between + /// signalling (AddTrackRequest) and SDP offer. This field + /// will be populated only if it is different to avoid + /// duplication and keep the representation concise. + #[prost(string, tag="6")] + pub sdp_cid: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -498,20 +521,30 @@ pub struct TrackInfo { pub muted: bool, /// original width of video (unset for audio) /// clients may receive a lower resolution version with simulcast + #[deprecated] #[prost(uint32, tag="5")] pub width: u32, /// original height of video (unset for audio) + #[deprecated] #[prost(uint32, tag="6")] pub height: u32, /// true if track is simulcasted + /// + /// see `video_layer_mode` in `codecs` + #[deprecated] #[prost(bool, tag="7")] pub simulcast: bool, /// true if DTX (Discontinuous Transmission) is disabled for audio + /// + /// deprecated in favor of `audio_features` + #[deprecated] #[prost(bool, tag="8")] pub disable_dtx: bool, /// source of media #[prost(enumeration="TrackSource", tag="9")] pub source: i32, + /// see `codecs` for layers of individual codec + #[deprecated] #[prost(message, repeated, tag="10")] pub layers: ::prost::alloc::vec::Vec, /// mime type of codec @@ -521,6 +554,8 @@ pub struct TrackInfo { pub mid: ::prost::alloc::string::String, #[prost(message, repeated, tag="13")] pub codecs: ::prost::alloc::vec::Vec, + /// deprecated in favor of `audio_features` + #[deprecated] #[prost(bool, tag="14")] pub stereo: bool, /// true if RED (Redundant Encoding) is disabled for audio @@ -553,6 +588,42 @@ pub struct VideoLayer { pub bitrate: u32, #[prost(uint32, tag="5")] pub ssrc: u32, + #[prost(int32, tag="6")] + pub spatial_layer: i32, + #[prost(string, tag="7")] + pub rid: ::prost::alloc::string::String, +} +/// Nested message and enum types in `VideoLayer`. +pub mod video_layer { + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] + #[repr(i32)] + pub enum Mode { + Unused = 0, + OneSpatialLayerPerStream = 1, + MultipleSpatialLayersPerStream = 2, + } + impl Mode { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Mode::Unused => "MODE_UNUSED", + Mode::OneSpatialLayerPerStream => "ONE_SPATIAL_LAYER_PER_STREAM", + Mode::MultipleSpatialLayersPerStream => "MULTIPLE_SPATIAL_LAYERS_PER_STREAM", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "MODE_UNUSED" => Some(Self::Unused), + "ONE_SPATIAL_LAYER_PER_STREAM" => Some(Self::OneSpatialLayerPerStream), + "MULTIPLE_SPATIAL_LAYERS_PER_STREAM" => Some(Self::MultipleSpatialLayersPerStream), + _ => None, + } + } + } } /// new DataPacket API #[allow(clippy::derive_partial_eq_without_eq)] @@ -573,7 +644,7 @@ pub struct DataPacket { /// sid of the user that sent the message #[prost(string, tag="17")] pub participant_sid: ::prost::alloc::string::String, - #[prost(oneof="data_packet::Value", tags="2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15")] + #[prost(oneof="data_packet::Value", tags="2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 18")] pub value: ::core::option::Option, } /// Nested message and enum types in `DataPacket`. @@ -631,6 +702,50 @@ pub mod data_packet { StreamChunk(super::data_stream::Chunk), #[prost(message, tag="15")] StreamTrailer(super::data_stream::Trailer), + #[prost(message, tag="18")] + EncryptedPacket(super::EncryptedPacket), + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EncryptedPacket { + #[prost(enumeration="encryption::Type", tag="1")] + pub encryption_type: i32, + #[prost(bytes="vec", tag="2")] + pub iv: ::prost::alloc::vec::Vec, + #[prost(uint32, tag="3")] + pub key_index: u32, + /// This is an encrypted EncryptedPacketPayload message representation + #[prost(bytes="vec", tag="4")] + pub encrypted_value: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EncryptedPacketPayload { + #[prost(oneof="encrypted_packet_payload::Value", tags="1, 3, 4, 5, 6, 7, 8, 9")] + pub value: ::core::option::Option, +} +/// Nested message and enum types in `EncryptedPacketPayload`. +pub mod encrypted_packet_payload { + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Value { + #[prost(message, tag="1")] + User(super::UserPacket), + #[prost(message, tag="3")] + ChatMessage(super::ChatMessage), + #[prost(message, tag="4")] + RpcRequest(super::RpcRequest), + #[prost(message, tag="5")] + RpcAck(super::RpcAck), + #[prost(message, tag="6")] + RpcResponse(super::RpcResponse), + #[prost(message, tag="7")] + StreamHeader(super::data_stream::Header), + #[prost(message, tag="8")] + StreamChunk(super::data_stream::Chunk), + #[prost(message, tag="9")] + StreamTrailer(super::data_stream::Trailer), } } #[allow(clippy::derive_partial_eq_without_eq)] @@ -675,7 +790,7 @@ pub struct UserPacket { /// topic under which the message was published #[prost(string, optional, tag="4")] pub topic: ::core::option::Option<::prost::alloc::string::String>, - /// Unique ID to indentify the message + /// Unique ID to identify the message #[prost(string, optional, tag="8")] pub id: ::core::option::Option<::prost::alloc::string::String>, /// start and end time allow relating the message to specific media time @@ -898,6 +1013,7 @@ pub mod client_info { UnityWeb = 11, Node = 12, Unreal = 13, + Esp32 = 14, } impl Sdk { /// String value of the enum field names used in the ProtoBuf definition. @@ -920,6 +1036,7 @@ pub mod client_info { Sdk::UnityWeb => "UNITY_WEB", Sdk::Node => "NODE", Sdk::Unreal => "UNREAL", + Sdk::Esp32 => "ESP32", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -939,6 +1056,7 @@ pub mod client_info { "UNITY_WEB" => Some(Self::UnityWeb), "NODE" => Some(Self::Node), "UNREAL" => Some(Self::Unreal), + "ESP32" => Some(Self::Esp32), _ => None, } } @@ -1231,7 +1349,8 @@ pub mod data_stream { /// only populated for finite streams, if it's a stream of unknown size this stays empty #[prost(uint64, optional, tag="5")] pub total_length: ::core::option::Option, - /// defaults to NONE + /// this is set on the DataPacket + #[deprecated] #[prost(enumeration="super::encryption::Type", tag="7")] pub encryption_type: i32, /// user defined attributes map that can carry additional info @@ -1267,7 +1386,8 @@ pub mod data_stream { /// a version indicating that this chunk_index has been retroactively modified and the original one needs to be replaced #[prost(int32, tag="4")] pub version: i32, - /// optional, initialization vector for AES-GCM encryption + /// this is set on the DataPacket + #[deprecated] #[prost(bytes="vec", optional, tag="5")] pub iv: ::core::option::Option<::prost::alloc::vec::Vec>, } @@ -2188,6 +2308,12 @@ pub struct S3Upload { pub secret: ::prost::alloc::string::String, #[prost(string, tag="11")] pub session_token: ::prost::alloc::string::String, + /// ARN of the role to assume for file upload. Egress will make an AssumeRole API call using the provided access_key and secret to assume that role. On LiveKit cloud, this is only available on accounts that have the feature enabled + #[prost(string, tag="12")] + pub assume_role_arn: ::prost::alloc::string::String, + /// ExternalID to use when assuming role for upload + #[prost(string, tag="13")] + pub assume_role_external_id: ::prost::alloc::string::String, #[prost(string, tag="3")] pub region: ::prost::alloc::string::String, #[prost(string, tag="4")] @@ -2874,10 +3000,10 @@ pub mod signal_request { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Message { - /// initial join exchange, for publisher + /// participant offer for publisher #[prost(message, tag="1")] Offer(super::SessionDescription), - /// participant answering publisher offer + /// participant answering subscriber offer #[prost(message, tag="2")] Answer(super::SessionDescription), #[prost(message, tag="3")] @@ -2930,7 +3056,7 @@ pub mod signal_request { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SignalResponse { - #[prost(oneof="signal_response::Message", tags="1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24")] + #[prost(oneof="signal_response::Message", tags="1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25")] pub message: ::core::option::Option, } /// Nested message and enum types in `SignalResponse`. @@ -3010,6 +3136,9 @@ pub mod signal_response { /// notify to the participant when they have been moved to a new room #[prost(message, tag="24")] RoomMoved(super::RoomMovedResponse), + /// notify number of required media sections to satisfy subscribed tracks + #[prost(message, tag="25")] + MediaSectionsRequirement(super::MediaSectionsRequirement), } } #[allow(clippy::derive_partial_eq_without_eq)] @@ -3019,6 +3148,10 @@ pub struct SimulcastCodec { pub codec: ::prost::alloc::string::String, #[prost(string, tag="2")] pub cid: ::prost::alloc::string::String, + #[prost(message, repeated, tag="4")] + pub layers: ::prost::alloc::vec::Vec, + #[prost(enumeration="video_layer::Mode", tag="5")] + pub video_layer_mode: i32, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -3030,7 +3163,6 @@ pub struct AddTrackRequest { pub name: ::prost::alloc::string::String, #[prost(enumeration="TrackType", tag="3")] pub r#type: i32, - /// to be deprecated in favor of layers #[prost(uint32, tag="4")] pub width: u32, #[prost(uint32, tag="5")] @@ -3434,7 +3566,9 @@ pub struct RoomMovedResponse { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SyncState { - /// last subscribe answer before reconnecting + /// last subscribe/publish answer before reconnecting + /// subscribe answer if using dual peer connection + /// publish answer if using single peer connection #[prost(message, optional, tag="1")] pub answer: ::core::option::Option, #[prost(message, optional, tag="2")] @@ -3443,7 +3577,9 @@ pub struct SyncState { pub publish_tracks: ::prost::alloc::vec::Vec, #[prost(message, repeated, tag="4")] pub data_channels: ::prost::alloc::vec::Vec, - /// last received server side offer before reconnecting + /// last received server side offer/sent client side offer before reconnecting + /// received server side offer if using dual peer connection + /// sent client side offer if using single peer connection #[prost(message, optional, tag="5")] pub offer: ::core::option::Option, #[prost(string, repeated, tag="6")] @@ -3603,6 +3739,92 @@ pub struct TrackSubscribed { #[prost(string, tag="1")] pub track_sid: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ConnectionSettings { + #[prost(bool, tag="1")] + pub auto_subscribe: bool, + #[prost(bool, tag="2")] + pub adaptive_stream: bool, + #[prost(bool, optional, tag="3")] + pub subscriber_allow_pause: ::core::option::Option, + #[prost(bool, tag="4")] + pub disable_ice_lite: bool, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct JoinRequest { + #[prost(message, optional, tag="1")] + pub client_info: ::core::option::Option, + #[prost(message, optional, tag="2")] + pub connection_settings: ::core::option::Option, + /// if not empty, will overwrite `metadata` in token + #[prost(string, tag="3")] + pub metadata: ::prost::alloc::string::String, + /// will set keys provided via this + /// will overwrite if the same key is in the token + /// will not delete keys from token if there is a key collision and this sets that key to empty value + #[prost(map="string, string", tag="4")] + pub participant_attributes: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + #[prost(message, repeated, tag="5")] + pub add_track_requests: ::prost::alloc::vec::Vec, + #[prost(message, optional, tag="6")] + pub publisher_offer: ::core::option::Option, + #[prost(bool, tag="7")] + pub reconnect: bool, + #[prost(enumeration="ReconnectReason", tag="8")] + pub reconnect_reason: i32, + #[prost(string, tag="9")] + pub participant_sid: ::prost::alloc::string::String, + #[prost(message, optional, tag="10")] + pub sync_state: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct WrappedJoinRequest { + #[prost(enumeration="wrapped_join_request::Compression", tag="1")] + pub compression: i32, + /// marshalled JoinRequest + potentially compressed + #[prost(bytes="vec", tag="2")] + pub join_request: ::prost::alloc::vec::Vec, +} +/// Nested message and enum types in `WrappedJoinRequest`. +pub mod wrapped_join_request { + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] + #[repr(i32)] + pub enum Compression { + None = 0, + Gzip = 1, + } + impl Compression { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Compression::None => "NONE", + Compression::Gzip => "GZIP", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "NONE" => Some(Self::None), + "GZIP" => Some(Self::Gzip), + _ => None, + } + } + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MediaSectionsRequirement { + #[prost(uint32, tag="1")] + pub num_audios: u32, + #[prost(uint32, tag="2")] + pub num_videos: u32, +} #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] pub enum SignalTarget { @@ -3722,6 +3944,10 @@ pub struct JobState { pub updated_at: i64, #[prost(string, tag="6")] pub participant_identity: ::prost::alloc::string::String, + #[prost(string, tag="7")] + pub worker_id: ::prost::alloc::string::String, + #[prost(string, tag="8")] + pub agent_id: ::prost::alloc::string::String, } /// from Worker to Server #[allow(clippy::derive_partial_eq_without_eq)] @@ -4290,6 +4516,9 @@ pub struct RoomConfiguration { /// limit number of participants that can be in a room, excluding Egress and Ingress participants #[prost(uint32, tag="4")] pub max_participants: u32, + /// metadata of room + #[prost(string, tag="11")] + pub metadata: ::prost::alloc::string::String, /// egress #[prost(message, optional, tag="5")] pub egress: ::core::option::Option, @@ -4751,7 +4980,7 @@ impl IngressVideoEncodingPreset { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct WebhookEvent { - /// one of room_started, room_finished, participant_joined, participant_left, + /// one of room_started, room_finished, participant_joined, participant_left, participant_connection_aborted, /// track_published, track_unpublished, egress_started, egress_updated, egress_ended, /// ingress_started, ingress_ended #[prost(string, tag="1")] diff --git a/livekit-protocol/src/livekit.serde.rs b/livekit-protocol/src/livekit.serde.rs index 5c411a433..3c79f6c7a 100644 --- a/livekit-protocol/src/livekit.serde.rs +++ b/livekit-protocol/src/livekit.serde.rs @@ -2848,6 +2848,7 @@ impl serde::Serialize for client_info::Sdk { Self::UnityWeb => "UNITY_WEB", Self::Node => "NODE", Self::Unreal => "UNREAL", + Self::Esp32 => "ESP32", }; serializer.serialize_str(variant) } @@ -2873,6 +2874,7 @@ impl<'de> serde::Deserialize<'de> for client_info::Sdk { "UNITY_WEB", "NODE", "UNREAL", + "ESP32", ]; struct GeneratedVisitor; @@ -2927,6 +2929,7 @@ impl<'de> serde::Deserialize<'de> for client_info::Sdk { "UNITY_WEB" => Ok(client_info::Sdk::UnityWeb), "NODE" => Ok(client_info::Sdk::Node), "UNREAL" => Ok(client_info::Sdk::Unreal), + "ESP32" => Ok(client_info::Sdk::Esp32), _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), } } @@ -3353,6 +3356,156 @@ impl<'de> serde::Deserialize<'de> for ConnectionQualityUpdate { deserializer.deserialize_struct("livekit.ConnectionQualityUpdate", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for ConnectionSettings { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.auto_subscribe { + len += 1; + } + if self.adaptive_stream { + len += 1; + } + if self.subscriber_allow_pause.is_some() { + len += 1; + } + if self.disable_ice_lite { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.ConnectionSettings", len)?; + if self.auto_subscribe { + struct_ser.serialize_field("autoSubscribe", &self.auto_subscribe)?; + } + if self.adaptive_stream { + struct_ser.serialize_field("adaptiveStream", &self.adaptive_stream)?; + } + if let Some(v) = self.subscriber_allow_pause.as_ref() { + struct_ser.serialize_field("subscriberAllowPause", v)?; + } + if self.disable_ice_lite { + struct_ser.serialize_field("disableIceLite", &self.disable_ice_lite)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for ConnectionSettings { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "auto_subscribe", + "autoSubscribe", + "adaptive_stream", + "adaptiveStream", + "subscriber_allow_pause", + "subscriberAllowPause", + "disable_ice_lite", + "disableIceLite", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + AutoSubscribe, + AdaptiveStream, + SubscriberAllowPause, + DisableIceLite, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "autoSubscribe" | "auto_subscribe" => Ok(GeneratedField::AutoSubscribe), + "adaptiveStream" | "adaptive_stream" => Ok(GeneratedField::AdaptiveStream), + "subscriberAllowPause" | "subscriber_allow_pause" => Ok(GeneratedField::SubscriberAllowPause), + "disableIceLite" | "disable_ice_lite" => Ok(GeneratedField::DisableIceLite), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = ConnectionSettings; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.ConnectionSettings") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut auto_subscribe__ = None; + let mut adaptive_stream__ = None; + let mut subscriber_allow_pause__ = None; + let mut disable_ice_lite__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::AutoSubscribe => { + if auto_subscribe__.is_some() { + return Err(serde::de::Error::duplicate_field("autoSubscribe")); + } + auto_subscribe__ = Some(map_.next_value()?); + } + GeneratedField::AdaptiveStream => { + if adaptive_stream__.is_some() { + return Err(serde::de::Error::duplicate_field("adaptiveStream")); + } + adaptive_stream__ = Some(map_.next_value()?); + } + GeneratedField::SubscriberAllowPause => { + if subscriber_allow_pause__.is_some() { + return Err(serde::de::Error::duplicate_field("subscriberAllowPause")); + } + subscriber_allow_pause__ = map_.next_value()?; + } + GeneratedField::DisableIceLite => { + if disable_ice_lite__.is_some() { + return Err(serde::de::Error::duplicate_field("disableIceLite")); + } + disable_ice_lite__ = Some(map_.next_value()?); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(ConnectionSettings { + auto_subscribe: auto_subscribe__.unwrap_or_default(), + adaptive_stream: adaptive_stream__.unwrap_or_default(), + subscriber_allow_pause: subscriber_allow_pause__, + disable_ice_lite: disable_ice_lite__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.ConnectionSettings", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for CreateAgentDispatchRequest { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -5586,6 +5739,9 @@ impl serde::Serialize for DataPacket { data_packet::Value::StreamTrailer(v) => { struct_ser.serialize_field("streamTrailer", v)?; } + data_packet::Value::EncryptedPacket(v) => { + struct_ser.serialize_field("encryptedPacket", v)?; + } } } struct_ser.end() @@ -5626,6 +5782,8 @@ impl<'de> serde::Deserialize<'de> for DataPacket { "streamChunk", "stream_trailer", "streamTrailer", + "encrypted_packet", + "encryptedPacket", ]; #[allow(clippy::enum_variant_names)] @@ -5647,6 +5805,7 @@ impl<'de> serde::Deserialize<'de> for DataPacket { StreamHeader, StreamChunk, StreamTrailer, + EncryptedPacket, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -5686,6 +5845,7 @@ impl<'de> serde::Deserialize<'de> for DataPacket { "streamHeader" | "stream_header" => Ok(GeneratedField::StreamHeader), "streamChunk" | "stream_chunk" => Ok(GeneratedField::StreamChunk), "streamTrailer" | "stream_trailer" => Ok(GeneratedField::StreamTrailer), + "encryptedPacket" | "encrypted_packet" => Ok(GeneratedField::EncryptedPacket), _ => Ok(GeneratedField::__SkipField__), } } @@ -5827,6 +5987,13 @@ impl<'de> serde::Deserialize<'de> for DataPacket { return Err(serde::de::Error::duplicate_field("streamTrailer")); } value__ = map_.next_value::<::std::option::Option<_>>()?.map(data_packet::Value::StreamTrailer) +; + } + GeneratedField::EncryptedPacket => { + if value__.is_some() { + return Err(serde::de::Error::duplicate_field("encryptedPacket")); + } + value__ = map_.next_value::<::std::option::Option<_>>()?.map(data_packet::Value::EncryptedPacket) ; } GeneratedField::__SkipField__ => { @@ -9211,6 +9378,365 @@ impl<'de> serde::Deserialize<'de> for EncodingOptionsPreset { deserializer.deserialize_any(GeneratedVisitor) } } +impl serde::Serialize for EncryptedPacket { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.encryption_type != 0 { + len += 1; + } + if !self.iv.is_empty() { + len += 1; + } + if self.key_index != 0 { + len += 1; + } + if !self.encrypted_value.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.EncryptedPacket", len)?; + if self.encryption_type != 0 { + let v = encryption::Type::try_from(self.encryption_type) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.encryption_type)))?; + struct_ser.serialize_field("encryptionType", &v)?; + } + if !self.iv.is_empty() { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("iv", pbjson::private::base64::encode(&self.iv).as_str())?; + } + if self.key_index != 0 { + struct_ser.serialize_field("keyIndex", &self.key_index)?; + } + if !self.encrypted_value.is_empty() { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("encryptedValue", pbjson::private::base64::encode(&self.encrypted_value).as_str())?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for EncryptedPacket { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "encryption_type", + "encryptionType", + "iv", + "key_index", + "keyIndex", + "encrypted_value", + "encryptedValue", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + EncryptionType, + Iv, + KeyIndex, + EncryptedValue, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "encryptionType" | "encryption_type" => Ok(GeneratedField::EncryptionType), + "iv" => Ok(GeneratedField::Iv), + "keyIndex" | "key_index" => Ok(GeneratedField::KeyIndex), + "encryptedValue" | "encrypted_value" => Ok(GeneratedField::EncryptedValue), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = EncryptedPacket; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.EncryptedPacket") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut encryption_type__ = None; + let mut iv__ = None; + let mut key_index__ = None; + let mut encrypted_value__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::EncryptionType => { + if encryption_type__.is_some() { + return Err(serde::de::Error::duplicate_field("encryptionType")); + } + encryption_type__ = Some(map_.next_value::()? as i32); + } + GeneratedField::Iv => { + if iv__.is_some() { + return Err(serde::de::Error::duplicate_field("iv")); + } + iv__ = + Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) + ; + } + GeneratedField::KeyIndex => { + if key_index__.is_some() { + return Err(serde::de::Error::duplicate_field("keyIndex")); + } + key_index__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::EncryptedValue => { + if encrypted_value__.is_some() { + return Err(serde::de::Error::duplicate_field("encryptedValue")); + } + encrypted_value__ = + Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) + ; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(EncryptedPacket { + encryption_type: encryption_type__.unwrap_or_default(), + iv: iv__.unwrap_or_default(), + key_index: key_index__.unwrap_or_default(), + encrypted_value: encrypted_value__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.EncryptedPacket", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for EncryptedPacketPayload { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.value.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.EncryptedPacketPayload", len)?; + if let Some(v) = self.value.as_ref() { + match v { + encrypted_packet_payload::Value::User(v) => { + struct_ser.serialize_field("user", v)?; + } + encrypted_packet_payload::Value::ChatMessage(v) => { + struct_ser.serialize_field("chatMessage", v)?; + } + encrypted_packet_payload::Value::RpcRequest(v) => { + struct_ser.serialize_field("rpcRequest", v)?; + } + encrypted_packet_payload::Value::RpcAck(v) => { + struct_ser.serialize_field("rpcAck", v)?; + } + encrypted_packet_payload::Value::RpcResponse(v) => { + struct_ser.serialize_field("rpcResponse", v)?; + } + encrypted_packet_payload::Value::StreamHeader(v) => { + struct_ser.serialize_field("streamHeader", v)?; + } + encrypted_packet_payload::Value::StreamChunk(v) => { + struct_ser.serialize_field("streamChunk", v)?; + } + encrypted_packet_payload::Value::StreamTrailer(v) => { + struct_ser.serialize_field("streamTrailer", v)?; + } + } + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for EncryptedPacketPayload { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "user", + "chat_message", + "chatMessage", + "rpc_request", + "rpcRequest", + "rpc_ack", + "rpcAck", + "rpc_response", + "rpcResponse", + "stream_header", + "streamHeader", + "stream_chunk", + "streamChunk", + "stream_trailer", + "streamTrailer", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + User, + ChatMessage, + RpcRequest, + RpcAck, + RpcResponse, + StreamHeader, + StreamChunk, + StreamTrailer, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "user" => Ok(GeneratedField::User), + "chatMessage" | "chat_message" => Ok(GeneratedField::ChatMessage), + "rpcRequest" | "rpc_request" => Ok(GeneratedField::RpcRequest), + "rpcAck" | "rpc_ack" => Ok(GeneratedField::RpcAck), + "rpcResponse" | "rpc_response" => Ok(GeneratedField::RpcResponse), + "streamHeader" | "stream_header" => Ok(GeneratedField::StreamHeader), + "streamChunk" | "stream_chunk" => Ok(GeneratedField::StreamChunk), + "streamTrailer" | "stream_trailer" => Ok(GeneratedField::StreamTrailer), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = EncryptedPacketPayload; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.EncryptedPacketPayload") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut value__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::User => { + if value__.is_some() { + return Err(serde::de::Error::duplicate_field("user")); + } + value__ = map_.next_value::<::std::option::Option<_>>()?.map(encrypted_packet_payload::Value::User) +; + } + GeneratedField::ChatMessage => { + if value__.is_some() { + return Err(serde::de::Error::duplicate_field("chatMessage")); + } + value__ = map_.next_value::<::std::option::Option<_>>()?.map(encrypted_packet_payload::Value::ChatMessage) +; + } + GeneratedField::RpcRequest => { + if value__.is_some() { + return Err(serde::de::Error::duplicate_field("rpcRequest")); + } + value__ = map_.next_value::<::std::option::Option<_>>()?.map(encrypted_packet_payload::Value::RpcRequest) +; + } + GeneratedField::RpcAck => { + if value__.is_some() { + return Err(serde::de::Error::duplicate_field("rpcAck")); + } + value__ = map_.next_value::<::std::option::Option<_>>()?.map(encrypted_packet_payload::Value::RpcAck) +; + } + GeneratedField::RpcResponse => { + if value__.is_some() { + return Err(serde::de::Error::duplicate_field("rpcResponse")); + } + value__ = map_.next_value::<::std::option::Option<_>>()?.map(encrypted_packet_payload::Value::RpcResponse) +; + } + GeneratedField::StreamHeader => { + if value__.is_some() { + return Err(serde::de::Error::duplicate_field("streamHeader")); + } + value__ = map_.next_value::<::std::option::Option<_>>()?.map(encrypted_packet_payload::Value::StreamHeader) +; + } + GeneratedField::StreamChunk => { + if value__.is_some() { + return Err(serde::de::Error::duplicate_field("streamChunk")); + } + value__ = map_.next_value::<::std::option::Option<_>>()?.map(encrypted_packet_payload::Value::StreamChunk) +; + } + GeneratedField::StreamTrailer => { + if value__.is_some() { + return Err(serde::de::Error::duplicate_field("streamTrailer")); + } + value__ = map_.next_value::<::std::option::Option<_>>()?.map(encrypted_packet_payload::Value::StreamTrailer) +; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(EncryptedPacketPayload { + value: value__, + }) + } + } + deserializer.deserialize_struct("livekit.EncryptedPacketPayload", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for Encryption { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -13485,6 +14011,12 @@ impl serde::Serialize for JobState { if !self.participant_identity.is_empty() { len += 1; } + if !self.worker_id.is_empty() { + len += 1; + } + if !self.agent_id.is_empty() { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.JobState", len)?; if self.status != 0 { let v = JobStatus::try_from(self.status) @@ -13512,6 +14044,12 @@ impl serde::Serialize for JobState { if !self.participant_identity.is_empty() { struct_ser.serialize_field("participantIdentity", &self.participant_identity)?; } + if !self.worker_id.is_empty() { + struct_ser.serialize_field("workerId", &self.worker_id)?; + } + if !self.agent_id.is_empty() { + struct_ser.serialize_field("agentId", &self.agent_id)?; + } struct_ser.end() } } @@ -13532,6 +14070,10 @@ impl<'de> serde::Deserialize<'de> for JobState { "updatedAt", "participant_identity", "participantIdentity", + "worker_id", + "workerId", + "agent_id", + "agentId", ]; #[allow(clippy::enum_variant_names)] @@ -13542,6 +14084,8 @@ impl<'de> serde::Deserialize<'de> for JobState { EndedAt, UpdatedAt, ParticipantIdentity, + WorkerId, + AgentId, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -13570,6 +14114,8 @@ impl<'de> serde::Deserialize<'de> for JobState { "endedAt" | "ended_at" => Ok(GeneratedField::EndedAt), "updatedAt" | "updated_at" => Ok(GeneratedField::UpdatedAt), "participantIdentity" | "participant_identity" => Ok(GeneratedField::ParticipantIdentity), + "workerId" | "worker_id" => Ok(GeneratedField::WorkerId), + "agentId" | "agent_id" => Ok(GeneratedField::AgentId), _ => Ok(GeneratedField::__SkipField__), } } @@ -13595,6 +14141,8 @@ impl<'de> serde::Deserialize<'de> for JobState { let mut ended_at__ = None; let mut updated_at__ = None; let mut participant_identity__ = None; + let mut worker_id__ = None; + let mut agent_id__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::Status => { @@ -13639,6 +14187,18 @@ impl<'de> serde::Deserialize<'de> for JobState { } participant_identity__ = Some(map_.next_value()?); } + GeneratedField::WorkerId => { + if worker_id__.is_some() { + return Err(serde::de::Error::duplicate_field("workerId")); + } + worker_id__ = Some(map_.next_value()?); + } + GeneratedField::AgentId => { + if agent_id__.is_some() { + return Err(serde::de::Error::duplicate_field("agentId")); + } + agent_id__ = Some(map_.next_value()?); + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -13651,6 +14211,8 @@ impl<'de> serde::Deserialize<'de> for JobState { ended_at: ended_at__.unwrap_or_default(), updated_at: updated_at__.unwrap_or_default(), participant_identity: participant_identity__.unwrap_or_default(), + worker_id: worker_id__.unwrap_or_default(), + agent_id: agent_id__.unwrap_or_default(), }) } } @@ -13904,6 +14466,266 @@ impl<'de> serde::Deserialize<'de> for JobType { deserializer.deserialize_any(GeneratedVisitor) } } +impl serde::Serialize for JoinRequest { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.client_info.is_some() { + len += 1; + } + if self.connection_settings.is_some() { + len += 1; + } + if !self.metadata.is_empty() { + len += 1; + } + if !self.participant_attributes.is_empty() { + len += 1; + } + if !self.add_track_requests.is_empty() { + len += 1; + } + if self.publisher_offer.is_some() { + len += 1; + } + if self.reconnect { + len += 1; + } + if self.reconnect_reason != 0 { + len += 1; + } + if !self.participant_sid.is_empty() { + len += 1; + } + if self.sync_state.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.JoinRequest", len)?; + if let Some(v) = self.client_info.as_ref() { + struct_ser.serialize_field("clientInfo", v)?; + } + if let Some(v) = self.connection_settings.as_ref() { + struct_ser.serialize_field("connectionSettings", v)?; + } + if !self.metadata.is_empty() { + struct_ser.serialize_field("metadata", &self.metadata)?; + } + if !self.participant_attributes.is_empty() { + struct_ser.serialize_field("participantAttributes", &self.participant_attributes)?; + } + if !self.add_track_requests.is_empty() { + struct_ser.serialize_field("addTrackRequests", &self.add_track_requests)?; + } + if let Some(v) = self.publisher_offer.as_ref() { + struct_ser.serialize_field("publisherOffer", v)?; + } + if self.reconnect { + struct_ser.serialize_field("reconnect", &self.reconnect)?; + } + if self.reconnect_reason != 0 { + let v = ReconnectReason::try_from(self.reconnect_reason) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.reconnect_reason)))?; + struct_ser.serialize_field("reconnectReason", &v)?; + } + if !self.participant_sid.is_empty() { + struct_ser.serialize_field("participantSid", &self.participant_sid)?; + } + if let Some(v) = self.sync_state.as_ref() { + struct_ser.serialize_field("syncState", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for JoinRequest { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "client_info", + "clientInfo", + "connection_settings", + "connectionSettings", + "metadata", + "participant_attributes", + "participantAttributes", + "add_track_requests", + "addTrackRequests", + "publisher_offer", + "publisherOffer", + "reconnect", + "reconnect_reason", + "reconnectReason", + "participant_sid", + "participantSid", + "sync_state", + "syncState", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + ClientInfo, + ConnectionSettings, + Metadata, + ParticipantAttributes, + AddTrackRequests, + PublisherOffer, + Reconnect, + ReconnectReason, + ParticipantSid, + SyncState, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "clientInfo" | "client_info" => Ok(GeneratedField::ClientInfo), + "connectionSettings" | "connection_settings" => Ok(GeneratedField::ConnectionSettings), + "metadata" => Ok(GeneratedField::Metadata), + "participantAttributes" | "participant_attributes" => Ok(GeneratedField::ParticipantAttributes), + "addTrackRequests" | "add_track_requests" => Ok(GeneratedField::AddTrackRequests), + "publisherOffer" | "publisher_offer" => Ok(GeneratedField::PublisherOffer), + "reconnect" => Ok(GeneratedField::Reconnect), + "reconnectReason" | "reconnect_reason" => Ok(GeneratedField::ReconnectReason), + "participantSid" | "participant_sid" => Ok(GeneratedField::ParticipantSid), + "syncState" | "sync_state" => Ok(GeneratedField::SyncState), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = JoinRequest; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.JoinRequest") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut client_info__ = None; + let mut connection_settings__ = None; + let mut metadata__ = None; + let mut participant_attributes__ = None; + let mut add_track_requests__ = None; + let mut publisher_offer__ = None; + let mut reconnect__ = None; + let mut reconnect_reason__ = None; + let mut participant_sid__ = None; + let mut sync_state__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::ClientInfo => { + if client_info__.is_some() { + return Err(serde::de::Error::duplicate_field("clientInfo")); + } + client_info__ = map_.next_value()?; + } + GeneratedField::ConnectionSettings => { + if connection_settings__.is_some() { + return Err(serde::de::Error::duplicate_field("connectionSettings")); + } + connection_settings__ = map_.next_value()?; + } + GeneratedField::Metadata => { + if metadata__.is_some() { + return Err(serde::de::Error::duplicate_field("metadata")); + } + metadata__ = Some(map_.next_value()?); + } + GeneratedField::ParticipantAttributes => { + if participant_attributes__.is_some() { + return Err(serde::de::Error::duplicate_field("participantAttributes")); + } + participant_attributes__ = Some( + map_.next_value::>()? + ); + } + GeneratedField::AddTrackRequests => { + if add_track_requests__.is_some() { + return Err(serde::de::Error::duplicate_field("addTrackRequests")); + } + add_track_requests__ = Some(map_.next_value()?); + } + GeneratedField::PublisherOffer => { + if publisher_offer__.is_some() { + return Err(serde::de::Error::duplicate_field("publisherOffer")); + } + publisher_offer__ = map_.next_value()?; + } + GeneratedField::Reconnect => { + if reconnect__.is_some() { + return Err(serde::de::Error::duplicate_field("reconnect")); + } + reconnect__ = Some(map_.next_value()?); + } + GeneratedField::ReconnectReason => { + if reconnect_reason__.is_some() { + return Err(serde::de::Error::duplicate_field("reconnectReason")); + } + reconnect_reason__ = Some(map_.next_value::()? as i32); + } + GeneratedField::ParticipantSid => { + if participant_sid__.is_some() { + return Err(serde::de::Error::duplicate_field("participantSid")); + } + participant_sid__ = Some(map_.next_value()?); + } + GeneratedField::SyncState => { + if sync_state__.is_some() { + return Err(serde::de::Error::duplicate_field("syncState")); + } + sync_state__ = map_.next_value()?; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(JoinRequest { + client_info: client_info__, + connection_settings: connection_settings__, + metadata: metadata__.unwrap_or_default(), + participant_attributes: participant_attributes__.unwrap_or_default(), + add_track_requests: add_track_requests__.unwrap_or_default(), + publisher_offer: publisher_offer__, + reconnect: reconnect__.unwrap_or_default(), + reconnect_reason: reconnect_reason__.unwrap_or_default(), + participant_sid: participant_sid__.unwrap_or_default(), + sync_state: sync_state__, + }) + } + } + deserializer.deserialize_struct("livekit.JoinRequest", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for JoinResponse { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -16384,10 +17206,28 @@ impl serde::Serialize for ListUpdate { if !self.set.is_empty() { len += 1; } + if !self.add.is_empty() { + len += 1; + } + if !self.del.is_empty() { + len += 1; + } + if self.clear { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.ListUpdate", len)?; if !self.set.is_empty() { struct_ser.serialize_field("set", &self.set)?; } + if !self.add.is_empty() { + struct_ser.serialize_field("add", &self.add)?; + } + if !self.del.is_empty() { + struct_ser.serialize_field("del", &self.del)?; + } + if self.clear { + struct_ser.serialize_field("clear", &self.clear)?; + } struct_ser.end() } } @@ -16399,11 +17239,17 @@ impl<'de> serde::Deserialize<'de> for ListUpdate { { const FIELDS: &[&str] = &[ "set", + "add", + "del", + "clear", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { Set, + Add, + Del, + Clear, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -16427,6 +17273,9 @@ impl<'de> serde::Deserialize<'de> for ListUpdate { { match value { "set" => Ok(GeneratedField::Set), + "add" => Ok(GeneratedField::Add), + "del" => Ok(GeneratedField::Del), + "clear" => Ok(GeneratedField::Clear), _ => Ok(GeneratedField::__SkipField__), } } @@ -16447,6 +17296,9 @@ impl<'de> serde::Deserialize<'de> for ListUpdate { V: serde::de::MapAccess<'de>, { let mut set__ = None; + let mut add__ = None; + let mut del__ = None; + let mut clear__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::Set => { @@ -16455,6 +17307,24 @@ impl<'de> serde::Deserialize<'de> for ListUpdate { } set__ = Some(map_.next_value()?); } + GeneratedField::Add => { + if add__.is_some() { + return Err(serde::de::Error::duplicate_field("add")); + } + add__ = Some(map_.next_value()?); + } + GeneratedField::Del => { + if del__.is_some() { + return Err(serde::de::Error::duplicate_field("del")); + } + del__ = Some(map_.next_value()?); + } + GeneratedField::Clear => { + if clear__.is_some() { + return Err(serde::de::Error::duplicate_field("clear")); + } + clear__ = Some(map_.next_value()?); + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -16462,12 +17332,133 @@ impl<'de> serde::Deserialize<'de> for ListUpdate { } Ok(ListUpdate { set: set__.unwrap_or_default(), + add: add__.unwrap_or_default(), + del: del__.unwrap_or_default(), + clear: clear__.unwrap_or_default(), }) } } deserializer.deserialize_struct("livekit.ListUpdate", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for MediaSectionsRequirement { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.num_audios != 0 { + len += 1; + } + if self.num_videos != 0 { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.MediaSectionsRequirement", len)?; + if self.num_audios != 0 { + struct_ser.serialize_field("numAudios", &self.num_audios)?; + } + if self.num_videos != 0 { + struct_ser.serialize_field("numVideos", &self.num_videos)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for MediaSectionsRequirement { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "num_audios", + "numAudios", + "num_videos", + "numVideos", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + NumAudios, + NumVideos, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "numAudios" | "num_audios" => Ok(GeneratedField::NumAudios), + "numVideos" | "num_videos" => Ok(GeneratedField::NumVideos), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = MediaSectionsRequirement; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.MediaSectionsRequirement") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut num_audios__ = None; + let mut num_videos__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::NumAudios => { + if num_audios__.is_some() { + return Err(serde::de::Error::duplicate_field("numAudios")); + } + num_audios__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::NumVideos => { + if num_videos__.is_some() { + return Err(serde::de::Error::duplicate_field("numVideos")); + } + num_videos__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(MediaSectionsRequirement { + num_audios: num_audios__.unwrap_or_default(), + num_videos: num_videos__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.MediaSectionsRequirement", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for MetricLabel { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -23352,6 +24343,9 @@ impl serde::Serialize for RoomConfiguration { if self.max_participants != 0 { len += 1; } + if !self.metadata.is_empty() { + len += 1; + } if self.egress.is_some() { len += 1; } @@ -23380,6 +24374,9 @@ impl serde::Serialize for RoomConfiguration { if self.max_participants != 0 { struct_ser.serialize_field("maxParticipants", &self.max_participants)?; } + if !self.metadata.is_empty() { + struct_ser.serialize_field("metadata", &self.metadata)?; + } if let Some(v) = self.egress.as_ref() { struct_ser.serialize_field("egress", v)?; } @@ -23412,6 +24409,7 @@ impl<'de> serde::Deserialize<'de> for RoomConfiguration { "departureTimeout", "max_participants", "maxParticipants", + "metadata", "egress", "min_playout_delay", "minPlayoutDelay", @@ -23428,6 +24426,7 @@ impl<'de> serde::Deserialize<'de> for RoomConfiguration { EmptyTimeout, DepartureTimeout, MaxParticipants, + Metadata, Egress, MinPlayoutDelay, MaxPlayoutDelay, @@ -23459,6 +24458,7 @@ impl<'de> serde::Deserialize<'de> for RoomConfiguration { "emptyTimeout" | "empty_timeout" => Ok(GeneratedField::EmptyTimeout), "departureTimeout" | "departure_timeout" => Ok(GeneratedField::DepartureTimeout), "maxParticipants" | "max_participants" => Ok(GeneratedField::MaxParticipants), + "metadata" => Ok(GeneratedField::Metadata), "egress" => Ok(GeneratedField::Egress), "minPlayoutDelay" | "min_playout_delay" => Ok(GeneratedField::MinPlayoutDelay), "maxPlayoutDelay" | "max_playout_delay" => Ok(GeneratedField::MaxPlayoutDelay), @@ -23487,6 +24487,7 @@ impl<'de> serde::Deserialize<'de> for RoomConfiguration { let mut empty_timeout__ = None; let mut departure_timeout__ = None; let mut max_participants__ = None; + let mut metadata__ = None; let mut egress__ = None; let mut min_playout_delay__ = None; let mut max_playout_delay__ = None; @@ -23524,6 +24525,12 @@ impl<'de> serde::Deserialize<'de> for RoomConfiguration { Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) ; } + GeneratedField::Metadata => { + if metadata__.is_some() { + return Err(serde::de::Error::duplicate_field("metadata")); + } + metadata__ = Some(map_.next_value()?); + } GeneratedField::Egress => { if egress__.is_some() { return Err(serde::de::Error::duplicate_field("egress")); @@ -23568,6 +24575,7 @@ impl<'de> serde::Deserialize<'de> for RoomConfiguration { empty_timeout: empty_timeout__.unwrap_or_default(), departure_timeout: departure_timeout__.unwrap_or_default(), max_participants: max_participants__.unwrap_or_default(), + metadata: metadata__.unwrap_or_default(), egress: egress__, min_playout_delay: min_playout_delay__.unwrap_or_default(), max_playout_delay: max_playout_delay__.unwrap_or_default(), @@ -24604,6 +25612,12 @@ impl serde::Serialize for S3Upload { if !self.session_token.is_empty() { len += 1; } + if !self.assume_role_arn.is_empty() { + len += 1; + } + if !self.assume_role_external_id.is_empty() { + len += 1; + } if !self.region.is_empty() { len += 1; } @@ -24638,6 +25652,12 @@ impl serde::Serialize for S3Upload { if !self.session_token.is_empty() { struct_ser.serialize_field("sessionToken", &self.session_token)?; } + if !self.assume_role_arn.is_empty() { + struct_ser.serialize_field("assumeRoleArn", &self.assume_role_arn)?; + } + if !self.assume_role_external_id.is_empty() { + struct_ser.serialize_field("assumeRoleExternalId", &self.assume_role_external_id)?; + } if !self.region.is_empty() { struct_ser.serialize_field("region", &self.region)?; } @@ -24677,6 +25697,10 @@ impl<'de> serde::Deserialize<'de> for S3Upload { "secret", "session_token", "sessionToken", + "assume_role_arn", + "assumeRoleArn", + "assume_role_external_id", + "assumeRoleExternalId", "region", "endpoint", "bucket", @@ -24694,6 +25718,8 @@ impl<'de> serde::Deserialize<'de> for S3Upload { AccessKey, Secret, SessionToken, + AssumeRoleArn, + AssumeRoleExternalId, Region, Endpoint, Bucket, @@ -24727,6 +25753,8 @@ impl<'de> serde::Deserialize<'de> for S3Upload { "accessKey" | "access_key" => Ok(GeneratedField::AccessKey), "secret" => Ok(GeneratedField::Secret), "sessionToken" | "session_token" => Ok(GeneratedField::SessionToken), + "assumeRoleArn" | "assume_role_arn" => Ok(GeneratedField::AssumeRoleArn), + "assumeRoleExternalId" | "assume_role_external_id" => Ok(GeneratedField::AssumeRoleExternalId), "region" => Ok(GeneratedField::Region), "endpoint" => Ok(GeneratedField::Endpoint), "bucket" => Ok(GeneratedField::Bucket), @@ -24757,6 +25785,8 @@ impl<'de> serde::Deserialize<'de> for S3Upload { let mut access_key__ = None; let mut secret__ = None; let mut session_token__ = None; + let mut assume_role_arn__ = None; + let mut assume_role_external_id__ = None; let mut region__ = None; let mut endpoint__ = None; let mut bucket__ = None; @@ -24785,6 +25815,18 @@ impl<'de> serde::Deserialize<'de> for S3Upload { } session_token__ = Some(map_.next_value()?); } + GeneratedField::AssumeRoleArn => { + if assume_role_arn__.is_some() { + return Err(serde::de::Error::duplicate_field("assumeRoleArn")); + } + assume_role_arn__ = Some(map_.next_value()?); + } + GeneratedField::AssumeRoleExternalId => { + if assume_role_external_id__.is_some() { + return Err(serde::de::Error::duplicate_field("assumeRoleExternalId")); + } + assume_role_external_id__ = Some(map_.next_value()?); + } GeneratedField::Region => { if region__.is_some() { return Err(serde::de::Error::duplicate_field("region")); @@ -24844,6 +25886,8 @@ impl<'de> serde::Deserialize<'de> for S3Upload { access_key: access_key__.unwrap_or_default(), secret: secret__.unwrap_or_default(), session_token: session_token__.unwrap_or_default(), + assume_role_arn: assume_role_arn__.unwrap_or_default(), + assume_role_external_id: assume_role_external_id__.unwrap_or_default(), region: region__.unwrap_or_default(), endpoint: endpoint__.unwrap_or_default(), bucket: bucket__.unwrap_or_default(), @@ -31459,6 +32503,9 @@ impl serde::Serialize for SignalResponse { signal_response::Message::RoomMoved(v) => { struct_ser.serialize_field("roomMoved", v)?; } + signal_response::Message::MediaSectionsRequirement(v) => { + struct_ser.serialize_field("mediaSectionsRequirement", v)?; + } } } struct_ser.end() @@ -31508,6 +32555,8 @@ impl<'de> serde::Deserialize<'de> for SignalResponse { "trackSubscribed", "room_moved", "roomMoved", + "media_sections_requirement", + "mediaSectionsRequirement", ]; #[allow(clippy::enum_variant_names)] @@ -31535,6 +32584,7 @@ impl<'de> serde::Deserialize<'de> for SignalResponse { RequestResponse, TrackSubscribed, RoomMoved, + MediaSectionsRequirement, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -31580,6 +32630,7 @@ impl<'de> serde::Deserialize<'de> for SignalResponse { "requestResponse" | "request_response" => Ok(GeneratedField::RequestResponse), "trackSubscribed" | "track_subscribed" => Ok(GeneratedField::TrackSubscribed), "roomMoved" | "room_moved" => Ok(GeneratedField::RoomMoved), + "mediaSectionsRequirement" | "media_sections_requirement" => Ok(GeneratedField::MediaSectionsRequirement), _ => Ok(GeneratedField::__SkipField__), } } @@ -31759,6 +32810,13 @@ impl<'de> serde::Deserialize<'de> for SignalResponse { return Err(serde::de::Error::duplicate_field("roomMoved")); } message__ = map_.next_value::<::std::option::Option<_>>()?.map(signal_response::Message::RoomMoved) +; + } + GeneratedField::MediaSectionsRequirement => { + if message__.is_some() { + return Err(serde::de::Error::duplicate_field("mediaSectionsRequirement")); + } + message__ = map_.next_value::<::std::option::Option<_>>()?.map(signal_response::Message::MediaSectionsRequirement) ; } GeneratedField::__SkipField__ => { @@ -32197,6 +33255,12 @@ impl serde::Serialize for SimulcastCodec { if !self.cid.is_empty() { len += 1; } + if !self.layers.is_empty() { + len += 1; + } + if self.video_layer_mode != 0 { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.SimulcastCodec", len)?; if !self.codec.is_empty() { struct_ser.serialize_field("codec", &self.codec)?; @@ -32204,6 +33268,14 @@ impl serde::Serialize for SimulcastCodec { if !self.cid.is_empty() { struct_ser.serialize_field("cid", &self.cid)?; } + if !self.layers.is_empty() { + struct_ser.serialize_field("layers", &self.layers)?; + } + if self.video_layer_mode != 0 { + let v = video_layer::Mode::try_from(self.video_layer_mode) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.video_layer_mode)))?; + struct_ser.serialize_field("videoLayerMode", &v)?; + } struct_ser.end() } } @@ -32216,12 +33288,17 @@ impl<'de> serde::Deserialize<'de> for SimulcastCodec { const FIELDS: &[&str] = &[ "codec", "cid", + "layers", + "video_layer_mode", + "videoLayerMode", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { Codec, Cid, + Layers, + VideoLayerMode, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -32246,6 +33323,8 @@ impl<'de> serde::Deserialize<'de> for SimulcastCodec { match value { "codec" => Ok(GeneratedField::Codec), "cid" => Ok(GeneratedField::Cid), + "layers" => Ok(GeneratedField::Layers), + "videoLayerMode" | "video_layer_mode" => Ok(GeneratedField::VideoLayerMode), _ => Ok(GeneratedField::__SkipField__), } } @@ -32267,6 +33346,8 @@ impl<'de> serde::Deserialize<'de> for SimulcastCodec { { let mut codec__ = None; let mut cid__ = None; + let mut layers__ = None; + let mut video_layer_mode__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::Codec => { @@ -32281,6 +33362,18 @@ impl<'de> serde::Deserialize<'de> for SimulcastCodec { } cid__ = Some(map_.next_value()?); } + GeneratedField::Layers => { + if layers__.is_some() { + return Err(serde::de::Error::duplicate_field("layers")); + } + layers__ = Some(map_.next_value()?); + } + GeneratedField::VideoLayerMode => { + if video_layer_mode__.is_some() { + return Err(serde::de::Error::duplicate_field("videoLayerMode")); + } + video_layer_mode__ = Some(map_.next_value::()? as i32); + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -32289,6 +33382,8 @@ impl<'de> serde::Deserialize<'de> for SimulcastCodec { Ok(SimulcastCodec { codec: codec__.unwrap_or_default(), cid: cid__.unwrap_or_default(), + layers: layers__.unwrap_or_default(), + video_layer_mode: video_layer_mode__.unwrap_or_default(), }) } } @@ -32315,6 +33410,12 @@ impl serde::Serialize for SimulcastCodecInfo { if !self.layers.is_empty() { len += 1; } + if self.video_layer_mode != 0 { + len += 1; + } + if !self.sdp_cid.is_empty() { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.SimulcastCodecInfo", len)?; if !self.mime_type.is_empty() { struct_ser.serialize_field("mimeType", &self.mime_type)?; @@ -32328,6 +33429,14 @@ impl serde::Serialize for SimulcastCodecInfo { if !self.layers.is_empty() { struct_ser.serialize_field("layers", &self.layers)?; } + if self.video_layer_mode != 0 { + let v = video_layer::Mode::try_from(self.video_layer_mode) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.video_layer_mode)))?; + struct_ser.serialize_field("videoLayerMode", &v)?; + } + if !self.sdp_cid.is_empty() { + struct_ser.serialize_field("sdpCid", &self.sdp_cid)?; + } struct_ser.end() } } @@ -32343,6 +33452,10 @@ impl<'de> serde::Deserialize<'de> for SimulcastCodecInfo { "mid", "cid", "layers", + "video_layer_mode", + "videoLayerMode", + "sdp_cid", + "sdpCid", ]; #[allow(clippy::enum_variant_names)] @@ -32351,6 +33464,8 @@ impl<'de> serde::Deserialize<'de> for SimulcastCodecInfo { Mid, Cid, Layers, + VideoLayerMode, + SdpCid, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -32377,6 +33492,8 @@ impl<'de> serde::Deserialize<'de> for SimulcastCodecInfo { "mid" => Ok(GeneratedField::Mid), "cid" => Ok(GeneratedField::Cid), "layers" => Ok(GeneratedField::Layers), + "videoLayerMode" | "video_layer_mode" => Ok(GeneratedField::VideoLayerMode), + "sdpCid" | "sdp_cid" => Ok(GeneratedField::SdpCid), _ => Ok(GeneratedField::__SkipField__), } } @@ -32400,6 +33517,8 @@ impl<'de> serde::Deserialize<'de> for SimulcastCodecInfo { let mut mid__ = None; let mut cid__ = None; let mut layers__ = None; + let mut video_layer_mode__ = None; + let mut sdp_cid__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::MimeType => { @@ -32426,6 +33545,18 @@ impl<'de> serde::Deserialize<'de> for SimulcastCodecInfo { } layers__ = Some(map_.next_value()?); } + GeneratedField::VideoLayerMode => { + if video_layer_mode__.is_some() { + return Err(serde::de::Error::duplicate_field("videoLayerMode")); + } + video_layer_mode__ = Some(map_.next_value::()? as i32); + } + GeneratedField::SdpCid => { + if sdp_cid__.is_some() { + return Err(serde::de::Error::duplicate_field("sdpCid")); + } + sdp_cid__ = Some(map_.next_value()?); + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -32436,6 +33567,8 @@ impl<'de> serde::Deserialize<'de> for SimulcastCodecInfo { mid: mid__.unwrap_or_default(), cid: cid__.unwrap_or_default(), layers: layers__.unwrap_or_default(), + video_layer_mode: video_layer_mode__.unwrap_or_default(), + sdp_cid: sdp_cid__.unwrap_or_default(), }) } } @@ -34961,8 +36094,115 @@ impl<'de> serde::Deserialize<'de> for TimedVersion { E: serde::de::Error, { match value { - "unixMicro" | "unix_micro" => Ok(GeneratedField::UnixMicro), - "ticks" => Ok(GeneratedField::Ticks), + "unixMicro" | "unix_micro" => Ok(GeneratedField::UnixMicro), + "ticks" => Ok(GeneratedField::Ticks), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = TimedVersion; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.TimedVersion") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut unix_micro__ = None; + let mut ticks__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::UnixMicro => { + if unix_micro__.is_some() { + return Err(serde::de::Error::duplicate_field("unixMicro")); + } + unix_micro__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::Ticks => { + if ticks__.is_some() { + return Err(serde::de::Error::duplicate_field("ticks")); + } + ticks__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(TimedVersion { + unix_micro: unix_micro__.unwrap_or_default(), + ticks: ticks__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.TimedVersion", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for TokenPagination { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.token.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.TokenPagination", len)?; + if !self.token.is_empty() { + struct_ser.serialize_field("token", &self.token)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for TokenPagination { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "token", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Token, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "token" => Ok(GeneratedField::Token), _ => Ok(GeneratedField::__SkipField__), } } @@ -34972,48 +36212,36 @@ impl<'de> serde::Deserialize<'de> for TimedVersion { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = TimedVersion; + type Value = TokenPagination; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.TimedVersion") + formatter.write_str("struct livekit.TokenPagination") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut unix_micro__ = None; - let mut ticks__ = None; + let mut token__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::UnixMicro => { - if unix_micro__.is_some() { - return Err(serde::de::Error::duplicate_field("unixMicro")); - } - unix_micro__ = - Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; - } - GeneratedField::Ticks => { - if ticks__.is_some() { - return Err(serde::de::Error::duplicate_field("ticks")); + GeneratedField::Token => { + if token__.is_some() { + return Err(serde::de::Error::duplicate_field("token")); } - ticks__ = - Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; + token__ = Some(map_.next_value()?); } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } } } - Ok(TimedVersion { - unix_micro: unix_micro__.unwrap_or_default(), - ticks: ticks__.unwrap_or_default(), + Ok(TokenPagination { + token: token__.unwrap_or_default(), }) } } - deserializer.deserialize_struct("livekit.TimedVersion", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.TokenPagination", FIELDS, GeneratedVisitor) } } impl serde::Serialize for TrackCompositeEgressRequest { @@ -40417,6 +41645,12 @@ impl serde::Serialize for VideoLayer { if self.ssrc != 0 { len += 1; } + if self.spatial_layer != 0 { + len += 1; + } + if !self.rid.is_empty() { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.VideoLayer", len)?; if self.quality != 0 { let v = VideoQuality::try_from(self.quality) @@ -40435,6 +41669,12 @@ impl serde::Serialize for VideoLayer { if self.ssrc != 0 { struct_ser.serialize_field("ssrc", &self.ssrc)?; } + if self.spatial_layer != 0 { + struct_ser.serialize_field("spatialLayer", &self.spatial_layer)?; + } + if !self.rid.is_empty() { + struct_ser.serialize_field("rid", &self.rid)?; + } struct_ser.end() } } @@ -40450,6 +41690,9 @@ impl<'de> serde::Deserialize<'de> for VideoLayer { "height", "bitrate", "ssrc", + "spatial_layer", + "spatialLayer", + "rid", ]; #[allow(clippy::enum_variant_names)] @@ -40459,6 +41702,8 @@ impl<'de> serde::Deserialize<'de> for VideoLayer { Height, Bitrate, Ssrc, + SpatialLayer, + Rid, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -40486,6 +41731,8 @@ impl<'de> serde::Deserialize<'de> for VideoLayer { "height" => Ok(GeneratedField::Height), "bitrate" => Ok(GeneratedField::Bitrate), "ssrc" => Ok(GeneratedField::Ssrc), + "spatialLayer" | "spatial_layer" => Ok(GeneratedField::SpatialLayer), + "rid" => Ok(GeneratedField::Rid), _ => Ok(GeneratedField::__SkipField__), } } @@ -40510,6 +41757,8 @@ impl<'de> serde::Deserialize<'de> for VideoLayer { let mut height__ = None; let mut bitrate__ = None; let mut ssrc__ = None; + let mut spatial_layer__ = None; + let mut rid__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::Quality => { @@ -40550,6 +41799,20 @@ impl<'de> serde::Deserialize<'de> for VideoLayer { Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) ; } + GeneratedField::SpatialLayer => { + if spatial_layer__.is_some() { + return Err(serde::de::Error::duplicate_field("spatialLayer")); + } + spatial_layer__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::Rid => { + if rid__.is_some() { + return Err(serde::de::Error::duplicate_field("rid")); + } + rid__ = Some(map_.next_value()?); + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -40561,12 +41824,88 @@ impl<'de> serde::Deserialize<'de> for VideoLayer { height: height__.unwrap_or_default(), bitrate: bitrate__.unwrap_or_default(), ssrc: ssrc__.unwrap_or_default(), + spatial_layer: spatial_layer__.unwrap_or_default(), + rid: rid__.unwrap_or_default(), }) } } deserializer.deserialize_struct("livekit.VideoLayer", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for video_layer::Mode { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + let variant = match self { + Self::Unused => "MODE_UNUSED", + Self::OneSpatialLayerPerStream => "ONE_SPATIAL_LAYER_PER_STREAM", + Self::MultipleSpatialLayersPerStream => "MULTIPLE_SPATIAL_LAYERS_PER_STREAM", + }; + serializer.serialize_str(variant) + } +} +impl<'de> serde::Deserialize<'de> for video_layer::Mode { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "MODE_UNUSED", + "ONE_SPATIAL_LAYER_PER_STREAM", + "MULTIPLE_SPATIAL_LAYERS_PER_STREAM", + ]; + + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = video_layer::Mode; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + fn visit_i64(self, v: i64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self) + }) + } + + fn visit_u64(self, v: u64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(v), &self) + }) + } + + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "MODE_UNUSED" => Ok(video_layer::Mode::Unused), + "ONE_SPATIAL_LAYER_PER_STREAM" => Ok(video_layer::Mode::OneSpatialLayerPerStream), + "MULTIPLE_SPATIAL_LAYERS_PER_STREAM" => Ok(video_layer::Mode::MultipleSpatialLayersPerStream), + _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), + } + } + } + deserializer.deserialize_any(GeneratedVisitor) + } +} impl serde::Serialize for VideoQuality { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -41793,3 +43132,193 @@ impl<'de> serde::Deserialize<'de> for WorkerStatus { deserializer.deserialize_any(GeneratedVisitor) } } +impl serde::Serialize for WrappedJoinRequest { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.compression != 0 { + len += 1; + } + if !self.join_request.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.WrappedJoinRequest", len)?; + if self.compression != 0 { + let v = wrapped_join_request::Compression::try_from(self.compression) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.compression)))?; + struct_ser.serialize_field("compression", &v)?; + } + if !self.join_request.is_empty() { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("joinRequest", pbjson::private::base64::encode(&self.join_request).as_str())?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for WrappedJoinRequest { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "compression", + "join_request", + "joinRequest", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Compression, + JoinRequest, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "compression" => Ok(GeneratedField::Compression), + "joinRequest" | "join_request" => Ok(GeneratedField::JoinRequest), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = WrappedJoinRequest; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.WrappedJoinRequest") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut compression__ = None; + let mut join_request__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Compression => { + if compression__.is_some() { + return Err(serde::de::Error::duplicate_field("compression")); + } + compression__ = Some(map_.next_value::()? as i32); + } + GeneratedField::JoinRequest => { + if join_request__.is_some() { + return Err(serde::de::Error::duplicate_field("joinRequest")); + } + join_request__ = + Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) + ; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(WrappedJoinRequest { + compression: compression__.unwrap_or_default(), + join_request: join_request__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.WrappedJoinRequest", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for wrapped_join_request::Compression { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + let variant = match self { + Self::None => "NONE", + Self::Gzip => "GZIP", + }; + serializer.serialize_str(variant) + } +} +impl<'de> serde::Deserialize<'de> for wrapped_join_request::Compression { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "NONE", + "GZIP", + ]; + + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = wrapped_join_request::Compression; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + fn visit_i64(self, v: i64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self) + }) + } + + fn visit_u64(self, v: u64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(v), &self) + }) + } + + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "NONE" => Ok(wrapped_join_request::Compression::None), + "GZIP" => Ok(wrapped_join_request::Compression::Gzip), + _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), + } + } + } + deserializer.deserialize_any(GeneratedVisitor) + } +} diff --git a/livekit/src/room/options.rs b/livekit/src/room/options.rs index 8188e7422..2bf69a2a1 100644 --- a/livekit/src/room/options.rs +++ b/livekit/src/room/options.rs @@ -284,6 +284,7 @@ pub fn video_layers_from_encodings( height, bitrate: 0, ssrc: 0, + ..Default::default() }]; } @@ -298,6 +299,7 @@ pub fn video_layers_from_encodings( height: (height as f64 / scale) as u32, bitrate: encoding.max_bitrate.unwrap_or(0) as u32, ssrc: 0, + ..Default::default() }); } From bda4a663e4616ff500041d24a4279211145080d5 Mon Sep 17 00:00:00 2001 From: lukasIO Date: Mon, 15 Sep 2025 14:26:03 +0200 Subject: [PATCH 243/274] Disable opus red for e2ee enabled clients (#706) --- livekit/src/room/participant/local_participant.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/livekit/src/room/participant/local_participant.rs b/livekit/src/room/participant/local_participant.rs index edeac969b..4ed2998b6 100644 --- a/livekit/src/room/participant/local_participant.rs +++ b/livekit/src/room/participant/local_participant.rs @@ -232,6 +232,8 @@ impl LocalParticipant { track: LocalTrack, options: TrackPublishOptions, ) -> RoomResult { + let disable_red = self.local.encryption_type != EncryptionType::None || !options.red; + let mut req = proto::AddTrackRequest { cid: track.rtc_track().id(), name: track.name(), @@ -239,7 +241,7 @@ impl LocalParticipant { muted: track.is_muted(), source: proto::TrackSource::from(options.source) as i32, disable_dtx: !options.dtx, - disable_red: !options.red, + disable_red, encryption: proto::encryption::Type::from(self.local.encryption_type) as i32, stream: options.stream.clone(), ..Default::default() From 10f358017b2d01fb4f53c84ebadff088049a7a3f Mon Sep 17 00:00:00 2001 From: CloudWebRTC Date: Thu, 18 Sep 2025 12:24:58 +0800 Subject: [PATCH 244/274] chore: Upgrade libwebrtc to m137. (#696) * chore: Upgrade libwebrtc to m137. * fix compile issue for linux. * fix. * Add g++ for aarch64 to Ubuntu dependencies * fix build for linux arm64. * Remove goma usage and switch to autoninja * fix android build. * fix build on linux arm64 for gcc. * Update Windows OS version in workflow configuration * Add GCC 14 installation steps for Ubuntu * Update webrtc-builds.yml * Change Windows OS to latest and add SDK installation * Update webrtc-builds.yml * Update webrtc-builds.yml * Update webrtc-builds.yml * Update webrtc-builds.yml * Update build_linux.sh * Update webrtc-builds.yml * Update webrtc-builds.yml * Update build_windows.cmd * cargo fmt. * Update build_windows.cmd * clean. * fix wgpu_room crash on macOS 26. * add lldb launch for wgpu_room. * Update GitHub Actions workflow for Windows and Python * Update webrtc-builds.yml * Update webrtc-builds.yml * bump version for webrtc libraries. * fix build for win arm64. * fix android ffi compile issue. --- .github/workflows/webrtc-builds.yml | 41 +- .vscode/launch.json | 12 + .vscode/tasks.json | 33 ++ examples/Cargo.lock | 435 ++++++++++++------ examples/wgpu_room/Cargo.toml | 4 + webrtc-sys/build.rs | 3 +- webrtc-sys/build/src/lib.rs | 2 +- webrtc-sys/include/livekit/apm.h | 2 +- webrtc-sys/include/livekit/audio_device.h | 2 +- webrtc-sys/include/livekit/audio_track.h | 6 +- webrtc-sys/include/livekit/data_channel.h | 4 +- webrtc-sys/include/livekit/frame_cryptor.h | 22 +- webrtc-sys/include/livekit/jsep.h | 2 +- webrtc-sys/include/livekit/media_stream.h | 4 +- .../include/livekit/media_stream_track.h | 6 +- webrtc-sys/include/livekit/peer_connection.h | 20 +- .../include/livekit/peer_connection_factory.h | 4 +- webrtc-sys/include/livekit/rtp_receiver.h | 10 +- webrtc-sys/include/livekit/rtp_sender.h | 10 +- webrtc-sys/include/livekit/rtp_transceiver.h | 8 +- .../include/livekit/video_encoder_factory.h | 4 +- .../include/livekit/video_frame_buffer.h | 28 +- webrtc-sys/include/livekit/video_track.h | 14 +- webrtc-sys/include/livekit/webrtc.h | 30 +- webrtc-sys/libwebrtc/.gclient | 2 +- webrtc-sys/libwebrtc/build_android.sh | 9 +- webrtc-sys/libwebrtc/build_ios.sh | 6 +- webrtc-sys/libwebrtc/build_linux.sh | 17 +- webrtc-sys/libwebrtc/build_macos.sh | 10 +- webrtc-sys/libwebrtc/build_windows.cmd | 10 +- .../patches/abseil_use_optional.patch | 13 - webrtc-sys/libwebrtc/patches/add_deps.patch | 6 +- .../patches/android_use_libunwind.patch | 6 +- webrtc-sys/libwebrtc/patches/force_gcc.patch | 25 + ...l_verify_callback_with_native_handle.patch | 24 +- webrtc-sys/src/apm.cpp | 6 +- webrtc-sys/src/audio_device.cpp | 2 +- webrtc-sys/src/audio_resampler.cpp | 7 +- webrtc-sys/src/audio_track.cpp | 12 +- webrtc-sys/src/data_channel.cpp | 6 +- webrtc-sys/src/media_stream.cpp | 10 +- webrtc-sys/src/media_stream_track.cpp | 2 +- webrtc-sys/src/nvidia/h264_decoder_impl.cpp | 8 +- webrtc-sys/src/nvidia/h264_decoder_impl.h | 4 +- webrtc-sys/src/nvidia/h264_encoder_impl.cpp | 13 +- webrtc-sys/src/nvidia/h264_encoder_impl.h | 1 + webrtc-sys/src/objc_video_frame_buffer.mm | 2 +- webrtc-sys/src/peer_connection.cpp | 36 +- webrtc-sys/src/peer_connection_factory.cpp | 12 +- webrtc-sys/src/rtp_parameters.cpp | 4 +- webrtc-sys/src/rtp_receiver.cpp | 6 +- webrtc-sys/src/rtp_sender.cpp | 6 +- webrtc-sys/src/rtp_transceiver.cpp | 4 +- webrtc-sys/src/vaapi/h264_encoder_impl.cpp | 9 +- webrtc-sys/src/vaapi/h264_encoder_impl.h | 1 + webrtc-sys/src/video_encoder_factory.cpp | 4 +- webrtc-sys/src/video_frame.cpp | 2 +- webrtc-sys/src/video_frame_buffer.cpp | 40 +- webrtc-sys/src/video_track.cpp | 18 +- webrtc-sys/src/webrtc.cpp | 36 +- yuv-sys/yuv_functions.txt | 316 ++++++++++++- 61 files changed, 970 insertions(+), 431 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json delete mode 100644 webrtc-sys/libwebrtc/patches/abseil_use_optional.patch create mode 100644 webrtc-sys/libwebrtc/patches/force_gcc.patch diff --git a/.github/workflows/webrtc-builds.yml b/.github/workflows/webrtc-builds.yml index def67d31d..b6c97b1f6 100644 --- a/.github/workflows/webrtc-builds.yml +++ b/.github/workflows/webrtc-builds.yml @@ -26,12 +26,12 @@ jobs: matrix: target: - name: win - os: windows-latest + os: windows-2022 cmd: .\build_windows.cmd arch: x64 - name: win - os: windows-latest + os: windows-2022 cmd: .\build_windows.cmd arch: arm64 @@ -104,17 +104,38 @@ jobs: echo "OutName: ${{ steps.setup.outputs.OUT }}" echo "OutZip: ${{ steps.setup.outputs.ZIP }}" - - uses: actions/checkout@v4 - with: - submodules: true - - - uses: actions/setup-python@v4 + - uses: actions/checkout@v5 + - uses: actions/setup-python@v5 - name: install setuptools (none-macOS) if: ${{ matrix.target.os != 'macos-latest' }} run: | pip3 install setuptools # pkg_resources is sometimes not found? + - name: Add GCC PPA and install GCC 14 + if: ${{ matrix.target.os == 'ubuntu-latest' }} + run: | + sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y + sudo apt update + sudo apt install gcc-14 g++-14 g++-14-aarch64-linux-gnu -y + + - name: Verify GCC 14 installation + if: ${{ matrix.target.os == 'ubuntu-latest' }} + run: | + gcc-14 --version + g++-14 --version + aarch64-linux-gnu-g++-14 --version + + - name: Set GCC 14 as default (optional) + if: ${{ matrix.target.os == 'ubuntu-latest' }} + run: | + sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 140 --slave /usr/bin/g++ g++ /usr/bin/g++-14 + sudo update-alternatives --config gcc + gcc --version + sudo update-alternatives --install /usr/bin/aarch64-linux-gnu-gcc aarch64-linux-gnu-gcc /usr/bin/aarch64-linux-gnu-gcc-14 140 --slave /usr/bin/aarch64-linux-gnu-g++ aarch64-linux-gnu-g++ /usr/bin/aarch64-linux-gnu-g++-14 + sudo update-alternatives --config aarch64-linux-gnu-gcc + aarch64-linux-gnu-gcc --version + - name: Install linux dependencies if: ${{ matrix.target.os == 'ubuntu-latest' }} run: | @@ -126,7 +147,7 @@ jobs: run: brew install ninja - name: Install windows dependencies - if: ${{ matrix.target.os == 'windows-latest' }} + if: ${{ matrix.target.os == 'windows-2022' }} run: | Invoke-WebRequest -Uri "https://github.com/ninja-build/ninja/releases/latest/download/ninja-win.zip" -OutFile ninja.zip Expand-Archive -Path ninja.zip -DestinationPath ${{ github.workspace }}\ninja @@ -145,13 +166,13 @@ jobs: working-directory: webrtc-sys/libwebrtc - name: Zip artifact (Unix) - if: ${{ matrix.target.os != 'windows-latest' }} + if: ${{ matrix.target.os != 'windows-2022' }} run: | cd webrtc-sys/libwebrtc zip ${{ github.workspace }}/${{ steps.setup.outputs.ZIP }} ${{ steps.setup.outputs.OUT }} -r - name: Zip artifact (Windows) - if: ${{ matrix.target.os == 'windows-latest' }} + if: ${{ matrix.target.os == 'windows-2022' }} run: Compress-Archive -Path .\webrtc-sys\libwebrtc\${{ steps.setup.outputs.OUT }} -DestinationPath ${{ steps.setup.outputs.ZIP }} - name: Upload artifacts diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..2e0006786 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,12 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "wgpu_room(debug)", + "program": "${workspaceFolder}/examples/target/debug/wgpu_room", + "preLaunchTask": "build" + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 000000000..900d036c6 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,33 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "type": "shell", + "command": "cargo", + "args": [ + "build" + ], + "options": { + "env": { + // cd webrtc-sys/libwebrtc && ./build_macos.sh --arch arm64 --profile debug + "LK_CUSTOM_WEBRTC": "${workspaceFolder}/webrtc-sys/libwebrtc/mac-arm64-debug" + }, + "cwd": "${workspaceFolder}/examples" + }, + "dependsOn": ["libwebrtc-build"] + }, + { + "label": "libwebrtc-build", + "type": "shell", + "command": "./build_macos.sh", + "args": [ + "--arch", "arm64", + "--profile", "debug" + ], + "options": { + "cwd": "${workspaceFolder}/webrtc-sys/libwebrtc" + }, + } + ] +} \ No newline at end of file diff --git a/examples/Cargo.lock b/examples/Cargo.lock index eab9084ad..4e0647a5c 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -126,12 +126,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - [[package]] name = "android_log-sys" version = "0.3.2" @@ -231,7 +225,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf" dependencies = [ "clipboard-win", - "image 0.25.6", + "image 0.25.8", "log", "objc2 0.6.2", "objc2-app-kit 0.3.1", @@ -325,20 +319,20 @@ dependencies = [ [[package]] name = "async-io" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19634d6336019ef220f09fd31168ce5c184b295cbf80345437cc36094ef223ca" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" dependencies = [ - "async-lock", + "autocfg", "cfg-if", "concurrent-queue", "futures-io", "futures-lite", "parking", "polling", - "rustix 1.0.8", + "rustix 1.1.2", "slab", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -716,9 +710,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.35" +version = "1.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "590f9024a68a8c40351881787f1934dc11afd69090f5edb6831464694d836ea3" +checksum = "65193589c6404eb80b450d618eaf9a2cafaaafd57ecce47370519ef674a7bd44" dependencies = [ "find-msvc-tools", "jobserver", @@ -755,16 +749,15 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.41" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" dependencies = [ - "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "wasm-bindgen", - "windows-link", + "windows-link 0.2.0", ] [[package]] @@ -1101,11 +1094,12 @@ checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" [[package]] name = "cxx" -version = "1.0.174" +version = "1.0.184" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ba77f286ce5c44c7ba02de894b057bc0a605a210e3d81fa83b92d94586c0e1" +checksum = "be4a0beb369d20d0de6aa7084ee523e4c9a31d7d8c61ba357b119bb574d7f368" dependencies = [ "cc", + "cxx-build", "cxxbridge-cmd", "cxxbridge-flags", "cxxbridge-macro", @@ -1115,13 +1109,13 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.174" +version = "1.0.184" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c56fdf6fba27288d1fda3384062692e66dc40ca41bafd15f616dd4e8b0ac909" +checksum = "27d955b93e56a8e45cbc34df0ae920d8b5ad01541a4571222c78527c00e1a40a" dependencies = [ "cc", "codespan-reporting 0.12.0", - "indexmap 2.11.0", + "indexmap 2.11.3", "proc-macro2", "quote", "scratch", @@ -1130,13 +1124,13 @@ dependencies = [ [[package]] name = "cxxbridge-cmd" -version = "1.0.174" +version = "1.0.184" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ade5eb6d6e6ef9c5631eff7e4f74e0e7109140e775f124d76904c0e5e6a202" +checksum = "052f6c468d9dabdc2b8b228bcb2d7843b2bea0f3fb9c4e2c6ba5852574ec0150" dependencies = [ "clap", "codespan-reporting 0.12.0", - "indexmap 2.11.0", + "indexmap 2.11.3", "proc-macro2", "quote", "syn 2.0.106", @@ -1144,17 +1138,17 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.174" +version = "1.0.184" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99f99fe2f3f76a2ba40c5431f854efe3725c19a89f4d59966bca3ec561be940e" +checksum = "0fd145fa180986cb8002c63217d03b2c782fdcd5fa323adcd1f62d2d6ece6144" [[package]] name = "cxxbridge-macro" -version = "1.0.174" +version = "1.0.184" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b6e5fa0545804d2d8d398a1e995203a1f2403a9f0651d50546462e61a28340e" +checksum = "02ac4a3bc4484a2daa0a8421c9588bd26522be9682a2fe02c7087bc4e8bc3c60" dependencies = [ - "indexmap 2.11.0", + "indexmap 2.11.3", "proc-macro2", "quote", "rustversion", @@ -1274,7 +1268,7 @@ dependencies = [ "egui-wgpu", "egui-winit", "home", - "image 0.25.6", + "image 0.25.8", "js-sys", "log", "objc2 0.5.2", @@ -1438,12 +1432,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -1500,6 +1494,26 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "fax" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab" +dependencies = [ + "fax_derive", +] + +[[package]] +name = "fax_derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "fdeflate" version = "0.3.7" @@ -1511,9 +1525,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e178e4fba8a2726903f6ba98a6d221e76f9c12c650d5dc0e6afdc50677b49650" +checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d" [[package]] name = "fixedbitset" @@ -1728,7 +1742,7 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc257fdb4038301ce4b9cd1b3b51704509692bb3ff716a410cbd07925d9dae55" dependencies = [ - "rustix 1.0.8", + "rustix 1.1.2", "windows-targets 0.52.6", ] @@ -1754,7 +1768,7 @@ dependencies = [ "cfg-if", "libc", "r-efi", - "wasi 0.14.3+wasi-0.2.4", + "wasi 0.14.7+wasi-0.2.4", ] [[package]] @@ -1886,7 +1900,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.11.0", + "indexmap 2.11.3", "slab", "tokio", "tokio-util", @@ -2021,9 +2035,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "humantime" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" [[package]] name = "hyper" @@ -2090,9 +2104,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.63" +version = "0.1.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -2100,7 +2114,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.61.2", + "windows-core 0.62.0", ] [[package]] @@ -2232,22 +2246,23 @@ dependencies = [ "gif", "jpeg-decoder", "num-traits", - "png", + "png 0.17.16", "qoi", - "tiff", + "tiff 0.9.1", ] [[package]] name = "image" -version = "0.25.6" +version = "0.25.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a" +checksum = "529feb3e6769d234375c4cf1ee2ce713682b8e76538cb13f9fc23e1400a591e7" dependencies = [ "bytemuck", "byteorder-lite", + "moxcms", "num-traits", - "png", - "tiff", + "png 0.18.0", + "tiff 0.10.3", ] [[package]] @@ -2262,9 +2277,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.11.0" +version = "2.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" +checksum = "92119844f513ffa41556430369ab02c295a3578af21cf945caa3e9e0c2481ac3" dependencies = [ "equivalent", "hashbrown 0.15.5", @@ -2398,9 +2413,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "6247da8b8658ad4e73a186e747fcc5fc2a29f979d6fe6269127fdb5fd08298d0" dependencies = [ "once_cell", "wasm-bindgen", @@ -2453,9 +2468,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "lebe" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" +checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" [[package]] name = "libc" @@ -2481,9 +2496,9 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ "bitflags 2.9.4", "libc", @@ -2492,7 +2507,7 @@ dependencies = [ [[package]] name = "libwebrtc" -version = "0.3.12" +version = "0.3.14" dependencies = [ "cxx", "jni", @@ -2514,9 +2529,9 @@ dependencies = [ [[package]] name = "link-cplusplus" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c349c75e1ab4a03bd6b33fe6cbd3c479c5dd443e44ad732664d72cb0e755475" +checksum = "7f78c730aaa7d0b9336a299029ea49f9ee53b0ed06e9202e8cb7db9bae7b8c82" dependencies = [ "cc", ] @@ -2529,9 +2544,9 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "litemap" @@ -2547,7 +2562,7 @@ checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" [[package]] name = "livekit" -version = "0.7.16" +version = "0.7.18" dependencies = [ "bmrng", "bytes", @@ -2571,7 +2586,7 @@ dependencies = [ [[package]] name = "livekit-api" -version = "0.4.5" +version = "0.4.6" dependencies = [ "async-tungstenite", "base64 0.21.7", @@ -2650,9 +2665,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.27" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" dependencies = [ "value-bag", ] @@ -2766,6 +2781,16 @@ dependencies = [ "webrtc-sys-build", ] +[[package]] +name = "moxcms" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd32fa8935aeadb8a8a6b6b351e40225570a37c43de67690383d87ef170cd08" +dependencies = [ + "num-traits", + "pxfm", +] + [[package]] name = "multimap" version = "0.10.1" @@ -2784,7 +2809,7 @@ dependencies = [ "cfg_aliases", "codespan-reporting 0.11.1", "hexf-parse", - "indexmap 2.11.0", + "indexmap 2.11.3", "log", "rustc-hash 1.1.0", "spirv", @@ -2808,7 +2833,7 @@ dependencies = [ "half", "hashbrown 0.15.5", "hexf-parse", - "indexmap 2.11.0", + "indexmap 2.11.3", "log", "num-traits", "once_cell", @@ -3459,7 +3484,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.11.0", + "indexmap 2.11.3", ] [[package]] @@ -3524,18 +3549,31 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "png" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0" +dependencies = [ + "bitflags 2.9.4", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "polling" -version = "3.10.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5bd19146350fe804f7cb2669c851c03d69da628803dab0d98018142aaa5d829" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" dependencies = [ "cfg-if", "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix 1.0.8", - "windows-sys 0.60.2", + "rustix 1.1.2", + "windows-sys 0.61.0", ] [[package]] @@ -3592,9 +3630,9 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ "toml_edit", ] @@ -3699,6 +3737,15 @@ dependencies = [ "prost 0.12.6", ] +[[package]] +name = "pxfm" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55f4fedc84ed39cb7a489322318976425e42a147e2be79d8f878e2884f94e84" +dependencies = [ + "num-traits", +] + [[package]] name = "qoi" version = "0.4.1" @@ -3708,6 +3755,12 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quick-xml" version = "0.37.5" @@ -3993,15 +4046,15 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.8" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ "bitflags 2.9.4", "errno", "libc", - "linux-raw-sys 0.9.4", - "windows-sys 0.60.2", + "linux-raw-sys 0.11.0", + "windows-sys 0.61.0", ] [[package]] @@ -4081,11 +4134,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.0", ] [[package]] @@ -4144,9 +4197,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.14.0" +version = "2.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" dependencies = [ "core-foundation-sys", "libc", @@ -4154,24 +4207,34 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.225" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "fd6c24dee235d0da097043389623fb913daddf92c76e9f5a1db88607a0bcbd1d" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.225" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "659356f9a0cb1e529b24c01e43ad2bdf520ec4ceaf83047b83ddcc2251f96383" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.225" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "0ea936adf78b1f766949a4977b91d2f5595825bd6ec079aa9543ad2685fc4516" dependencies = [ "proc-macro2", "quote", @@ -4180,14 +4243,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.143" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", "ryu", "serde", + "serde_core", ] [[package]] @@ -4463,15 +4527,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.21.0" +version = "3.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e" +checksum = "84fa4d11fadde498443cca10fd3ac23c951f0dc59e080e9f4b93d4df4e4eea53" dependencies = [ "fastrand", "getrandom 0.3.3", "once_cell", - "rustix 1.0.8", - "windows-sys 0.60.2", + "rustix 1.1.2", + "windows-sys 0.61.0", ] [[package]] @@ -4553,6 +4617,20 @@ dependencies = [ "weezl", ] +[[package]] +name = "tiff" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg", +] + [[package]] name = "time" version = "0.3.43" @@ -4713,18 +4791,31 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.11" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +checksum = "a197c0ec7d131bfc6f7e82c8442ba1595aeab35da7adbf05b6b73cd06a16b6be" +dependencies = [ + "serde_core", +] [[package]] name = "toml_edit" -version = "0.22.27" +version = "0.23.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +checksum = "c2ad0b7ae9cfeef5605163839cb9221f453399f15cfb5c10be9885fcf56611f9" dependencies = [ - "indexmap 2.11.0", + "indexmap 2.11.3", "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10" +dependencies = [ "winnow", ] @@ -4906,9 +4997,9 @@ checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "unicode-segmentation" @@ -5021,30 +5112,40 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" -version = "0.14.3+wasi-0.2.4" +version = "0.14.7+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" +dependencies = [ + "wasip2", +] + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a51ae83037bdd272a9e28ce236db8c07016dd0d50c27038b3f407533c030c95" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "4ad224d2776649cfb4f4471124f8176e54c1cca67a88108e30a0cd98b90e7ad3" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", + "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.100" +version = "0.2.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +checksum = "3a1364104bdcd3c03f22b16a3b1c9620891469f5e9f09bc38b2db121e593e732" dependencies = [ "bumpalo", "log", @@ -5056,9 +5157,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.50" +version = "0.4.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +checksum = "9c0a08ecf5d99d5604a6666a70b3cde6ab7cc6142f5e641a8ef48fc744ce8854" dependencies = [ "cfg-if", "js-sys", @@ -5069,9 +5170,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "0d7ab4ca3e367bb1ed84ddbd83cc6e41e115f8337ed047239578210214e36c76" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5079,9 +5180,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "4a518014843a19e2dbbd0ed5dfb6b99b23fb886b14e6192a00803a3e14c552b0" dependencies = [ "proc-macro2", "quote", @@ -5092,9 +5193,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "255eb0aa4cc2eea3662a00c2bbd66e93911b7361d5e0fcd62385acfd7e15dcee" dependencies = [ "unicode-ident", ] @@ -5107,7 +5208,7 @@ checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35" dependencies = [ "cc", "downcast-rs", - "rustix 1.0.8", + "rustix 1.1.2", "scoped-tls", "smallvec", "wayland-sys", @@ -5120,7 +5221,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d" dependencies = [ "bitflags 2.9.4", - "rustix 1.0.8", + "rustix 1.1.2", "wayland-backend", "wayland-scanner", ] @@ -5142,7 +5243,7 @@ version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "447ccc440a881271b19e9989f75726d60faa09b95b0200a9b7eb5cc47c3eeb29" dependencies = [ - "rustix 1.0.8", + "rustix 1.1.2", "wayland-client", "xcursor", ] @@ -5210,9 +5311,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.77" +version = "0.3.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +checksum = "50462a022f46851b81d5441d1a6f5bac0b21a1d72d64bd4906fbdd4bf7230ec7" dependencies = [ "js-sys", "wasm-bindgen", @@ -5261,7 +5362,7 @@ checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "webrtc-sys" -version = "0.3.9" +version = "0.3.11" dependencies = [ "cc", "cxx", @@ -5355,7 +5456,7 @@ dependencies = [ "bitflags 2.9.4", "cfg_aliases", "document-features", - "indexmap 2.11.0", + "indexmap 2.11.3", "log", "naga 24.0.0", "once_cell", @@ -5382,7 +5483,7 @@ dependencies = [ "cfg_aliases", "document-features", "hashbrown 0.15.5", - "indexmap 2.11.0", + "indexmap 2.11.3", "log", "naga 25.0.1", "once_cell", @@ -5555,9 +5656,11 @@ dependencies = [ "image 0.24.9", "livekit", "log", + "objc2 0.6.2", "parking_lot", "serde", "tokio", + "webrtc-sys", "wgpu 25.0.2", "winit", ] @@ -5580,11 +5683,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0978bf7171b3d90bac376700cb56d606feb40f251a475a5d6634613564460b22" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -5638,15 +5741,15 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.61.2" +version = "0.62.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +checksum = "57fe7168f7de578d2d8a05b07fd61870d2e73b4020e9f49aa00da8471723497c" dependencies = [ "windows-implement 0.60.0", "windows-interface 0.59.1", - "windows-link", - "windows-result 0.3.4", - "windows-strings 0.4.2", + "windows-link 0.2.0", + "windows-result 0.4.0", + "windows-strings 0.5.0", ] [[package]] @@ -5699,6 +5802,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +[[package]] +name = "windows-link" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" + [[package]] name = "windows-result" version = "0.1.2" @@ -5719,11 +5828,11 @@ dependencies = [ [[package]] name = "windows-result" -version = "0.3.4" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f" dependencies = [ - "windows-link", + "windows-link 0.2.0", ] [[package]] @@ -5738,11 +5847,11 @@ dependencies = [ [[package]] name = "windows-strings" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda" dependencies = [ - "windows-link", + "windows-link 0.2.0", ] [[package]] @@ -5790,6 +5899,15 @@ dependencies = [ "windows-targets 0.53.3", ] +[[package]] +name = "windows-sys" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" +dependencies = [ + "windows-link 0.2.0", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -5842,7 +5960,7 @@ version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ - "windows-link", + "windows-link 0.1.3", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -6106,9 +6224,9 @@ dependencies = [ [[package]] name = "wit-bindgen" -version = "0.45.0" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052283831dbae3d879dc7f51f3d92703a316ca49f91540417d38591826127814" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "writeable" @@ -6138,7 +6256,7 @@ dependencies = [ "libc", "libloading", "once_cell", - "rustix 1.0.8", + "rustix 1.1.2", "x11rb-protocol", ] @@ -6205,18 +6323,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", @@ -6318,14 +6436,20 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.15+zstd.1.5.7" +version = "2.0.16+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" dependencies = [ "cc", "pkg-config", ] +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + [[package]] name = "zune-inflate" version = "0.2.54" @@ -6334,3 +6458,12 @@ checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" dependencies = [ "simd-adler32", ] + +[[package]] +name = "zune-jpeg" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713" +dependencies = [ + "zune-core", +] diff --git a/examples/wgpu_room/Cargo.toml b/examples/wgpu_room/Cargo.toml index 6030d64cd..de3cb3996 100644 --- a/examples/wgpu_room/Cargo.toml +++ b/examples/wgpu_room/Cargo.toml @@ -23,3 +23,7 @@ serde = { version = "1", features = ["derive"] } log = "0.4" env_logger = "0.10.0" console-subscriber = { version = "0.1.10", features = ["parking_lot"], optional = true } + +[target.'cfg(target_os = "macos")'.dependencies] +# On macos use feature relax-sign-encoding to avoid runtime crash (https://github.com/rust-windowing/winit/pull/4302) +objc2 = { version = "0.6.0", features = ["relax-sign-encoding"] } \ No newline at end of file diff --git a/webrtc-sys/build.rs b/webrtc-sys/build.rs index b022ea7d3..e2a3dcdfd 100644 --- a/webrtc-sys/build.rs +++ b/webrtc-sys/build.rs @@ -190,7 +190,7 @@ fn main() { _ => {} } - builder.flag("-std=c++2a"); + builder.flag("-Wno-changes-meaning").flag("-std=c++20"); } "macos" => { println!("cargo:rustc-link-lib=framework=Foundation"); @@ -208,6 +208,7 @@ fn main() { println!("cargo:rustc-link-lib=framework=QuartzCore"); println!("cargo:rustc-link-lib=framework=IOKit"); println!("cargo:rustc-link-lib=framework=IOSurface"); + println!("cargo:rustc-link-lib=framework=ScreenCaptureKit"); configure_darwin_sysroot(&mut builder); diff --git a/webrtc-sys/build/src/lib.rs b/webrtc-sys/build/src/lib.rs index 87cbeb48e..6b858ef2c 100644 --- a/webrtc-sys/build/src/lib.rs +++ b/webrtc-sys/build/src/lib.rs @@ -27,7 +27,7 @@ use regex::Regex; use reqwest::StatusCode; pub const SCRATH_PATH: &str = "livekit_webrtc"; -pub const WEBRTC_TAG: &str = "webrtc-ed96590"; +pub const WEBRTC_TAG: &str = "webrtc-f4967ef"; pub const IGNORE_DEFINES: [&str; 2] = ["CR_CLANG_REVISION", "CR_XCODE_VERSION"]; pub fn target_os() -> String { diff --git a/webrtc-sys/include/livekit/apm.h b/webrtc-sys/include/livekit/apm.h index 5f0e17eca..57cd6ea5c 100644 --- a/webrtc-sys/include/livekit/apm.h +++ b/webrtc-sys/include/livekit/apm.h @@ -63,7 +63,7 @@ class AudioProcessingModule { int set_stream_delay_ms(int delay_ms); private: - rtc::scoped_refptr apm_; + webrtc::scoped_refptr apm_; }; std::unique_ptr create_apm( diff --git a/webrtc-sys/include/livekit/audio_device.h b/webrtc-sys/include/livekit/audio_device.h index 74d76823a..d0ae2ded1 100644 --- a/webrtc-sys/include/livekit/audio_device.h +++ b/webrtc-sys/include/livekit/audio_device.h @@ -114,7 +114,7 @@ class AudioDevice : public webrtc::AudioDeviceModule { int GetRecordAudioParameters(webrtc::AudioParameters* params) const override; #endif // WEBRTC_IOS - int32_t SetAudioDeviceSink(webrtc::AudioDeviceSink* sink) const override; + int32_t SetObserver(webrtc::AudioDeviceObserver* sink) override; private: mutable webrtc::Mutex mutex_; diff --git a/webrtc-sys/include/livekit/audio_track.h b/webrtc-sys/include/livekit/audio_track.h index c780b7797..349bb7cea 100644 --- a/webrtc-sys/include/livekit/audio_track.h +++ b/webrtc-sys/include/livekit/audio_track.h @@ -48,7 +48,7 @@ class AudioTrack : public MediaStreamTrack { private: friend RtcRuntime; AudioTrack(std::shared_ptr rtc_runtime, - rtc::scoped_refptr track); + webrtc::scoped_refptr track); public: ~AudioTrack(); @@ -167,10 +167,10 @@ class AudioTrackSource { void clear_buffer() const; - rtc::scoped_refptr get() const; + webrtc::scoped_refptr get() const; private: - rtc::scoped_refptr source_; + webrtc::scoped_refptr source_; }; std::shared_ptr new_audio_track_source( diff --git a/webrtc-sys/include/livekit/data_channel.h b/webrtc-sys/include/livekit/data_channel.h index 33fea51b4..e511dc7e8 100644 --- a/webrtc-sys/include/livekit/data_channel.h +++ b/webrtc-sys/include/livekit/data_channel.h @@ -39,7 +39,7 @@ class DataChannel { public: explicit DataChannel( std::shared_ptr rtc_runtime, - rtc::scoped_refptr data_channel); + webrtc::scoped_refptr data_channel); ~DataChannel(); void register_observer(rust::Box observer) const; @@ -54,7 +54,7 @@ class DataChannel { private: mutable webrtc::Mutex mutex_; std::shared_ptr rtc_runtime_; - rtc::scoped_refptr data_channel_; + webrtc::scoped_refptr data_channel_; mutable std::unique_ptr observer_; }; diff --git a/webrtc-sys/include/livekit/frame_cryptor.h b/webrtc-sys/include/livekit/frame_cryptor.h index 43102d3cf..f7ba3584b 100644 --- a/webrtc-sys/include/livekit/frame_cryptor.h +++ b/webrtc-sys/include/livekit/frame_cryptor.h @@ -116,10 +116,10 @@ class KeyProvider { impl_->SetSifTrailer(trailer_vec); } - rtc::scoped_refptr rtc_key_provider() { return impl_; } + webrtc::scoped_refptr rtc_key_provider() { return impl_; } private: - rtc::scoped_refptr impl_; + webrtc::scoped_refptr impl_; }; class FrameCryptor { @@ -127,14 +127,14 @@ class FrameCryptor { FrameCryptor(std::shared_ptr rtc_runtime, const std::string participant_id, webrtc::FrameCryptorTransformer::Algorithm algorithm, - rtc::scoped_refptr key_provider, - rtc::scoped_refptr sender); + webrtc::scoped_refptr key_provider, + webrtc::scoped_refptr sender); FrameCryptor(std::shared_ptr rtc_runtime, const std::string participant_id, webrtc::FrameCryptorTransformer::Algorithm algorithm, - rtc::scoped_refptr key_provider, - rtc::scoped_refptr receiver); + webrtc::scoped_refptr key_provider, + webrtc::scoped_refptr receiver); ~FrameCryptor(); /// Enable/Disable frame crypto for the sender or receiver. @@ -161,11 +161,11 @@ class FrameCryptor { std::shared_ptr rtc_runtime_; const rust::String participant_id_; mutable webrtc::Mutex mutex_; - rtc::scoped_refptr e2ee_transformer_; - rtc::scoped_refptr key_provider_; - rtc::scoped_refptr sender_; - rtc::scoped_refptr receiver_; - mutable rtc::scoped_refptr observer_; + webrtc::scoped_refptr e2ee_transformer_; + webrtc::scoped_refptr key_provider_; + webrtc::scoped_refptr sender_; + webrtc::scoped_refptr receiver_; + mutable webrtc::scoped_refptr observer_; }; class NativeFrameCryptorObserver diff --git a/webrtc-sys/include/livekit/jsep.h b/webrtc-sys/include/livekit/jsep.h index 6f100f43e..62ec36669 100644 --- a/webrtc-sys/include/livekit/jsep.h +++ b/webrtc-sys/include/livekit/jsep.h @@ -139,7 +139,7 @@ class NativeRtcStatsCollector : public webrtc::RTCStatsCollectorCallback { : ctx_(std::move(ctx)), on_stats_(on_stats) {} void OnStatsDelivered( - const rtc::scoped_refptr& report) override { + const webrtc::scoped_refptr& report) override { on_stats_(std::move(ctx_), report->ToJson()); } diff --git a/webrtc-sys/include/livekit/media_stream.h b/webrtc-sys/include/livekit/media_stream.h index fe64dbe6a..fcfd09762 100644 --- a/webrtc-sys/include/livekit/media_stream.h +++ b/webrtc-sys/include/livekit/media_stream.h @@ -33,7 +33,7 @@ namespace livekit { class MediaStream { public: MediaStream(std::shared_ptr rtc_runtime, - rtc::scoped_refptr stream); + webrtc::scoped_refptr stream); rust::String id() const; rust::Vec get_video_tracks() const; @@ -47,7 +47,7 @@ class MediaStream { private: std::shared_ptr rtc_runtime_; - rtc::scoped_refptr media_stream_; + webrtc::scoped_refptr media_stream_; }; static std::shared_ptr _shared_media_stream() { diff --git a/webrtc-sys/include/livekit/media_stream_track.h b/webrtc-sys/include/livekit/media_stream_track.h index 6be69047a..1b86c6165 100644 --- a/webrtc-sys/include/livekit/media_stream_track.h +++ b/webrtc-sys/include/livekit/media_stream_track.h @@ -33,7 +33,7 @@ namespace livekit { class MediaStreamTrack { protected: MediaStreamTrack(std::shared_ptr, - rtc::scoped_refptr track); + webrtc::scoped_refptr track); public: rust::String kind() const; @@ -44,13 +44,13 @@ class MediaStreamTrack { TrackState state() const; - rtc::scoped_refptr rtc_track() const { + webrtc::scoped_refptr rtc_track() const { return track_; } protected: std::shared_ptr rtc_runtime_; - rtc::scoped_refptr track_; + webrtc::scoped_refptr track_; }; static std::shared_ptr _shared_media_stream_track() { diff --git a/webrtc-sys/include/livekit/peer_connection.h b/webrtc-sys/include/livekit/peer_connection.h index 32496ca00..6e7587b50 100644 --- a/webrtc-sys/include/livekit/peer_connection.h +++ b/webrtc-sys/include/livekit/peer_connection.h @@ -48,7 +48,7 @@ class PeerConnection : webrtc::PeerConnectionObserver { public: PeerConnection( std::shared_ptr rtc_runtime, - rtc::scoped_refptr pc_factory, + webrtc::scoped_refptr pc_factory, rust::Box observer); ~PeerConnection(); @@ -141,13 +141,13 @@ class PeerConnection : webrtc::PeerConnectionObserver { webrtc::PeerConnectionInterface::SignalingState new_state) override; void OnAddStream( - rtc::scoped_refptr stream) override; + webrtc::scoped_refptr stream) override; void OnRemoveStream( - rtc::scoped_refptr stream) override; + webrtc::scoped_refptr stream) override; void OnDataChannel( - rtc::scoped_refptr data_channel) override; + webrtc::scoped_refptr data_channel) override; void OnRenegotiationNeeded() override; @@ -182,23 +182,23 @@ class PeerConnection : webrtc::PeerConnectionObserver { const cricket::CandidatePairChangeEvent& event) override; void OnAddTrack( - rtc::scoped_refptr receiver, - const std::vector>& + webrtc::scoped_refptr receiver, + const std::vector>& streams) override; void OnTrack( - rtc::scoped_refptr transceiver) override; + webrtc::scoped_refptr transceiver) override; void OnRemoveTrack( - rtc::scoped_refptr receiver) override; + webrtc::scoped_refptr receiver) override; void OnInterestingUsage(int usage_pattern) override; private: std::shared_ptr rtc_runtime_; - rtc::scoped_refptr pc_factory_; + webrtc::scoped_refptr pc_factory_; rust::Box observer_; - rtc::scoped_refptr peer_connection_; + webrtc::scoped_refptr peer_connection_; }; static std::shared_ptr _shared_peer_connection() { diff --git a/webrtc-sys/include/livekit/peer_connection_factory.h b/webrtc-sys/include/livekit/peer_connection_factory.h index ae49842ba..2a4bf8539 100644 --- a/webrtc-sys/include/livekit/peer_connection_factory.h +++ b/webrtc-sys/include/livekit/peer_connection_factory.h @@ -64,8 +64,8 @@ class PeerConnectionFactory { private: std::shared_ptr rtc_runtime_; - rtc::scoped_refptr audio_device_; - rtc::scoped_refptr peer_factory_; + webrtc::scoped_refptr audio_device_; + webrtc::scoped_refptr peer_factory_; webrtc::TaskQueueFactory* task_queue_factory_; }; diff --git a/webrtc-sys/include/livekit/rtp_receiver.h b/webrtc-sys/include/livekit/rtp_receiver.h index 1bec92b8f..25181680d 100644 --- a/webrtc-sys/include/livekit/rtp_receiver.h +++ b/webrtc-sys/include/livekit/rtp_receiver.h @@ -40,8 +40,8 @@ class RtpReceiver { public: RtpReceiver( std::shared_ptr rtc_runtime, - rtc::scoped_refptr receiver, - rtc::scoped_refptr peer_connection); + webrtc::scoped_refptr receiver, + webrtc::scoped_refptr peer_connection); std::shared_ptr track() const; @@ -62,14 +62,14 @@ class RtpReceiver { void set_jitter_buffer_minimum_delay(bool is_some, double delay_seconds) const; - rtc::scoped_refptr rtc_receiver() const { + webrtc::scoped_refptr rtc_receiver() const { return receiver_; } private: std::shared_ptr rtc_runtime_; - rtc::scoped_refptr receiver_; - rtc::scoped_refptr peer_connection_; + webrtc::scoped_refptr receiver_; + webrtc::scoped_refptr peer_connection_; }; static std::shared_ptr _shared_rtp_receiver() { diff --git a/webrtc-sys/include/livekit/rtp_sender.h b/webrtc-sys/include/livekit/rtp_sender.h index b9ff44e29..80363942b 100644 --- a/webrtc-sys/include/livekit/rtp_sender.h +++ b/webrtc-sys/include/livekit/rtp_sender.h @@ -38,8 +38,8 @@ class RtpSender { public: RtpSender( std::shared_ptr rtc_runtime, - rtc::scoped_refptr sender, - rtc::scoped_refptr peer_connection); + webrtc::scoped_refptr sender, + webrtc::scoped_refptr peer_connection); bool set_track(std::shared_ptr track) const; @@ -65,14 +65,14 @@ class RtpSender { void set_parameters(RtpParameters params) const; - rtc::scoped_refptr rtc_sender() const { + webrtc::scoped_refptr rtc_sender() const { return sender_; } private: std::shared_ptr rtc_runtime_; - rtc::scoped_refptr sender_; - rtc::scoped_refptr peer_connection_; + webrtc::scoped_refptr sender_; + webrtc::scoped_refptr peer_connection_; }; static std::shared_ptr _shared_rtp_sender() { diff --git a/webrtc-sys/include/livekit/rtp_transceiver.h b/webrtc-sys/include/livekit/rtp_transceiver.h index 0eba06fb2..99a994ccc 100644 --- a/webrtc-sys/include/livekit/rtp_transceiver.h +++ b/webrtc-sys/include/livekit/rtp_transceiver.h @@ -43,8 +43,8 @@ class RtpTransceiver { public: RtpTransceiver( std::shared_ptr rtc_runtime, - rtc::scoped_refptr transceiver, - rtc::scoped_refptr peer_connection); + webrtc::scoped_refptr transceiver, + webrtc::scoped_refptr peer_connection); MediaType media_type() const; @@ -82,8 +82,8 @@ class RtpTransceiver { private: std::shared_ptr rtc_runtime_; - rtc::scoped_refptr transceiver_; - rtc::scoped_refptr peer_connection_; + webrtc::scoped_refptr transceiver_; + webrtc::scoped_refptr peer_connection_; }; static std::shared_ptr _shared_rtp_transceiver() { diff --git a/webrtc-sys/include/livekit/video_encoder_factory.h b/webrtc-sys/include/livekit/video_encoder_factory.h index f1e8bc84a..902049e3a 100644 --- a/webrtc-sys/include/livekit/video_encoder_factory.h +++ b/webrtc-sys/include/livekit/video_encoder_factory.h @@ -29,7 +29,7 @@ class VideoEncoderFactory : public webrtc::VideoEncoderFactory { CodecSupport QueryCodecSupport( const webrtc::SdpVideoFormat& format, - absl::optional scalability_mode) const override; + std::optional scalability_mode) const override; std::unique_ptr Create( const webrtc::Environment& env, const webrtc::SdpVideoFormat& format) override; @@ -45,7 +45,7 @@ class VideoEncoderFactory : public webrtc::VideoEncoderFactory { CodecSupport QueryCodecSupport( const webrtc::SdpVideoFormat& format, - absl::optional scalability_mode) const override; + std::optional scalability_mode) const override; std::unique_ptr Create( const webrtc::Environment& env, const webrtc::SdpVideoFormat& format) override; diff --git a/webrtc-sys/include/livekit/video_frame_buffer.h b/webrtc-sys/include/livekit/video_frame_buffer.h index a7e25fdf5..0a612b324 100644 --- a/webrtc-sys/include/livekit/video_frame_buffer.h +++ b/webrtc-sys/include/livekit/video_frame_buffer.h @@ -58,7 +58,7 @@ namespace livekit { class VideoFrameBuffer { public: explicit VideoFrameBuffer( - rtc::scoped_refptr buffer); + webrtc::scoped_refptr buffer); VideoFrameBufferType buffer_type() const; @@ -74,15 +74,15 @@ class VideoFrameBuffer { std::unique_ptr get_i444(); std::unique_ptr get_i010(); std::unique_ptr get_nv12(); - rtc::scoped_refptr get() const; + webrtc::scoped_refptr get() const; protected: - rtc::scoped_refptr buffer_; + webrtc::scoped_refptr buffer_; }; class PlanarYuvBuffer : public VideoFrameBuffer { public: - explicit PlanarYuvBuffer(rtc::scoped_refptr buffer); + explicit PlanarYuvBuffer(webrtc::scoped_refptr buffer); unsigned int chroma_width() const; unsigned int chroma_height() const; @@ -98,7 +98,7 @@ class PlanarYuvBuffer : public VideoFrameBuffer { class PlanarYuv8Buffer : public PlanarYuvBuffer { public: explicit PlanarYuv8Buffer( - rtc::scoped_refptr buffer); + webrtc::scoped_refptr buffer); const uint8_t* data_y() const; const uint8_t* data_u() const; @@ -111,7 +111,7 @@ class PlanarYuv8Buffer : public PlanarYuvBuffer { class PlanarYuv16BBuffer : public PlanarYuvBuffer { public: explicit PlanarYuv16BBuffer( - rtc::scoped_refptr buffer); + webrtc::scoped_refptr buffer); const uint16_t* data_y() const; const uint16_t* data_u() const; @@ -124,7 +124,7 @@ class PlanarYuv16BBuffer : public PlanarYuvBuffer { class BiplanarYuvBuffer : public VideoFrameBuffer { public: explicit BiplanarYuvBuffer( - rtc::scoped_refptr buffer); + webrtc::scoped_refptr buffer); unsigned int chroma_width() const; unsigned int chroma_height() const; @@ -139,7 +139,7 @@ class BiplanarYuvBuffer : public VideoFrameBuffer { class BiplanarYuv8Buffer : public BiplanarYuvBuffer { public: explicit BiplanarYuv8Buffer( - rtc::scoped_refptr buffer); + webrtc::scoped_refptr buffer); const uint8_t* data_y() const; const uint8_t* data_uv() const; @@ -150,12 +150,12 @@ class BiplanarYuv8Buffer : public BiplanarYuvBuffer { class I420Buffer : public PlanarYuv8Buffer { public: - explicit I420Buffer(rtc::scoped_refptr buffer); + explicit I420Buffer(webrtc::scoped_refptr buffer); }; class I420ABuffer : public I420Buffer { public: - explicit I420ABuffer(rtc::scoped_refptr buffer); + explicit I420ABuffer(webrtc::scoped_refptr buffer); unsigned int stride_a() const; const uint8_t* data_a() const; @@ -166,22 +166,22 @@ class I420ABuffer : public I420Buffer { class I422Buffer : public PlanarYuv8Buffer { public: - explicit I422Buffer(rtc::scoped_refptr buffer); + explicit I422Buffer(webrtc::scoped_refptr buffer); }; class I444Buffer : public PlanarYuv8Buffer { public: - explicit I444Buffer(rtc::scoped_refptr buffer); + explicit I444Buffer(webrtc::scoped_refptr buffer); }; class I010Buffer : public PlanarYuv16BBuffer { public: - explicit I010Buffer(rtc::scoped_refptr buffer); + explicit I010Buffer(webrtc::scoped_refptr buffer); }; class NV12Buffer : public BiplanarYuv8Buffer { public: - explicit NV12Buffer(rtc::scoped_refptr buffer); + explicit NV12Buffer(webrtc::scoped_refptr buffer); }; std::unique_ptr copy_i420_buffer( diff --git a/webrtc-sys/include/livekit/video_track.h b/webrtc-sys/include/livekit/video_track.h index f60c3dd91..ac74b79c9 100644 --- a/webrtc-sys/include/livekit/video_track.h +++ b/webrtc-sys/include/livekit/video_track.h @@ -42,7 +42,7 @@ class VideoTrack : public MediaStreamTrack { private: friend RtcRuntime; VideoTrack(std::shared_ptr rtc_runtime, - rtc::scoped_refptr track); + webrtc::scoped_refptr track); public: ~VideoTrack(); @@ -68,7 +68,7 @@ class VideoTrack : public MediaStreamTrack { mutable std::vector> sinks_; }; -class NativeVideoSink : public rtc::VideoSinkInterface { +class NativeVideoSink : public webrtc::VideoSinkInterface { public: explicit NativeVideoSink(rust::Box observer); @@ -85,7 +85,7 @@ std::shared_ptr new_native_video_sink( rust::Box observer); class VideoTrackSource { - class InternalSource : public rtc::AdaptedVideoTrackSource { + class InternalSource : public webrtc::AdaptedVideoTrackSource { public: InternalSource(const VideoResolution& resolution); // (0, 0) means no resolution/optional, the @@ -94,7 +94,7 @@ class VideoTrackSource { ~InternalSource() override; bool is_screencast() const override; - absl::optional needs_denoising() const override; + std::optional needs_denoising() const override; SourceState state() const override; bool remote() const override; VideoResolution video_resolution() const; @@ -102,7 +102,7 @@ class VideoTrackSource { private: mutable webrtc::Mutex mutex_; - rtc::TimestampAligner timestamp_aligner_; + webrtc::TimestampAligner timestamp_aligner_; VideoResolution resolution_; }; @@ -114,10 +114,10 @@ class VideoTrackSource { bool on_captured_frame(const std::unique_ptr& frame) const; // frames pushed from Rust (+interior mutability) - rtc::scoped_refptr get() const; + webrtc::scoped_refptr get() const; private: - rtc::scoped_refptr source_; + webrtc::scoped_refptr source_; }; std::shared_ptr new_video_track_source( diff --git a/webrtc-sys/include/livekit/webrtc.h b/webrtc-sys/include/livekit/webrtc.h index 88ad1027e..afa86e1fd 100644 --- a/webrtc-sys/include/livekit/webrtc.h +++ b/webrtc-sys/include/livekit/webrtc.h @@ -25,6 +25,7 @@ #include "rtc_base/logging.h" #include "rtc_base/physical_socket_server.h" #include "rtc_base/ssl_adapter.h" +#include "rtc_base/thread.h" #include "rust/cxx.h" #ifdef WEBRTC_WIN @@ -55,25 +56,25 @@ class RtcRuntime : public std::enable_shared_from_this { RtcRuntime& operator=(const RtcRuntime&) = delete; ~RtcRuntime(); - rtc::Thread* network_thread() const; - rtc::Thread* worker_thread() const; - rtc::Thread* signaling_thread() const; + webrtc::Thread* network_thread() const; + webrtc::Thread* worker_thread() const; + webrtc::Thread* signaling_thread() const; std::shared_ptr get_or_create_media_stream_track( - rtc::scoped_refptr track); + webrtc::scoped_refptr track); std::shared_ptr get_or_create_audio_track( - rtc::scoped_refptr track); + webrtc::scoped_refptr track); std::shared_ptr get_or_create_video_track( - rtc::scoped_refptr track); + webrtc::scoped_refptr track); private: RtcRuntime(); - std::unique_ptr network_thread_; - std::unique_ptr worker_thread_; - std::unique_ptr signaling_thread_; + std::unique_ptr network_thread_; + std::unique_ptr worker_thread_; + std::unique_ptr signaling_thread_; // Lists used to make sure we don't create multiple wrappers for one // underlying webrtc object. (e.g: webrtc::VideoTrackInterface should only @@ -89,19 +90,20 @@ class RtcRuntime : public std::enable_shared_from_this { // std::vector> rtp_senders_; #ifdef WEBRTC_WIN - // rtc::WinsockInitializer winsock_; - // rtc::PhysicalSocketServer ss_; - // rtc::AutoSocketServerThread main_thread_{&ss_}; + // webrtc::WinsockInitializer winsock_; + // webrtc::PhysicalSocketServer ss_; + // webrtc::AutoSocketServerThread main_thread_{&ss_}; #endif }; -class LogSink : public rtc::LogSink { +class LogSink : public webrtc::LogSink { public: LogSink(rust::Fn fnc); ~LogSink(); void OnLogMessage(const std::string& message, - rtc::LoggingSeverity severity) override; + webrtc::LoggingSeverity severity) override; + void OnLogMessage(const std::string& message) override {} private: diff --git a/webrtc-sys/libwebrtc/.gclient b/webrtc-sys/libwebrtc/.gclient index b4fbb1f00..2cdc191a5 100644 --- a/webrtc-sys/libwebrtc/.gclient +++ b/webrtc-sys/libwebrtc/.gclient @@ -1,7 +1,7 @@ solutions = [ { "name": 'src', - "url": 'https://github.com/webrtc-sdk/webrtc.git@m125_release', + "url": 'https://github.com/webrtc-sdk/webrtc.git@m137_release', "custom_deps": {}, "deps_file": "DEPS", "managed": False, diff --git a/webrtc-sys/libwebrtc/build_android.sh b/webrtc-sys/libwebrtc/build_android.sh index 0b7006fcc..1222cfdfb 100755 --- a/webrtc-sys/libwebrtc/build_android.sh +++ b/webrtc-sys/libwebrtc/build_android.sh @@ -72,10 +72,6 @@ git apply "$COMMAND_DIR/patches/ssl_verify_callback_with_native_handle.patch" -v git apply "$COMMAND_DIR/patches/add_deps.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn git apply "$COMMAND_DIR/patches/android_use_libunwind.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn -cd third_party -git apply "$COMMAND_DIR/patches/abseil_use_optional.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn -cd .. - cd .. mkdir -p "$ARTIFACTS_DIR/lib" @@ -98,7 +94,6 @@ args="is_debug=$debug \ rtc_libvpx_build_vp9=false \ is_component_build=false \ enable_stripping=true \ - use_goma=false \ rtc_use_h264=false \ rtc_use_pipewire=false \ symbol_level=0 \ @@ -113,7 +108,7 @@ fi gn gen "$OUTPUT_DIR" --root="src" --args="${args}" # build shared library -ninja -C "$OUTPUT_DIR" :default \ +autoninja -C "$OUTPUT_DIR" :default \ sdk/android:native_api \ sdk/android:libwebrtc \ sdk/android:libjingle_peerconnection_so @@ -134,4 +129,4 @@ cp "src/sdk/android/AndroidManifest.xml" "$ARTIFACTS_DIR" cd src find . -name "*.h" -print | cpio -pd "$ARTIFACTS_DIR/include" - +find . -name "*.inc" -print | cpio -pd "$ARTIFACTS_DIR/include" diff --git a/webrtc-sys/libwebrtc/build_ios.sh b/webrtc-sys/libwebrtc/build_ios.sh index 6db1ce21f..28173b1fb 100755 --- a/webrtc-sys/libwebrtc/build_ios.sh +++ b/webrtc-sys/libwebrtc/build_ios.sh @@ -82,10 +82,6 @@ cd src git apply "$COMMAND_DIR/patches/ssl_verify_callback_with_native_handle.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn git apply "$COMMAND_DIR/patches/add_deps.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn -cd third_party -git apply "$COMMAND_DIR/patches/abseil_use_optional.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn -cd .. - cd .. mkdir -p "$ARTIFACTS_DIR/lib" @@ -142,4 +138,4 @@ cp "$OUTPUT_DIR/LICENSE.md" "$ARTIFACTS_DIR" cd src find . -name "*.h" -print | cpio -pd "$ARTIFACTS_DIR/include" - +find . -name "*.inc" -print | cpio -pd "$ARTIFACTS_DIR/include" diff --git a/webrtc-sys/libwebrtc/build_linux.sh b/webrtc-sys/libwebrtc/build_linux.sh index b08e29890..8baa7c6be 100755 --- a/webrtc-sys/libwebrtc/build_linux.sh +++ b/webrtc-sys/libwebrtc/build_linux.sh @@ -71,11 +71,12 @@ git apply "$COMMAND_DIR/patches/add_licenses.patch" -v --ignore-space-change --i git apply "$COMMAND_DIR/patches/ssl_verify_callback_with_native_handle.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn git apply "$COMMAND_DIR/patches/add_deps.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn -cd third_party -git apply "$COMMAND_DIR/patches/abseil_use_optional.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn -cd .. +cd build + +git apply "$COMMAND_DIR/patches/force_gcc.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn + +cd ../.. -cd .. mkdir -p "$ARTIFACTS_DIR/lib" @@ -92,6 +93,9 @@ args="is_debug=$debug \ rtc_enable_protobuf=false \ treat_warnings_as_errors=false \ use_custom_libcxx=false \ + use_llvm_libatomic=false \ + use_libcxx_modules=false \ + use_custom_libcxx_for_host=false \ rtc_include_tests=false \ rtc_build_tools=false \ rtc_build_examples=false \ @@ -99,13 +103,14 @@ args="is_debug=$debug \ enable_libaom=true \ is_component_build=false \ enable_stripping=true \ - use_goma=false \ ffmpeg_branding=\"Chrome\" \ rtc_use_h264=true \ + rtc_use_h265=true \ rtc_use_pipewire=false \ symbol_level=0 \ enable_iterator_debugging=false \ use_rtti=true \ + is_clang=false \ rtc_use_x11=false" # generate ninja files @@ -128,4 +133,4 @@ cp "$OUTPUT_DIR/LICENSE.md" "$ARTIFACTS_DIR" cd src find . -name "*.h" -print | cpio -pd "$ARTIFACTS_DIR/include" - +find . -name "*.inc" -print | cpio -pd "$ARTIFACTS_DIR/include" diff --git a/webrtc-sys/libwebrtc/build_macos.sh b/webrtc-sys/libwebrtc/build_macos.sh index c4ba72bd6..6a9f672b4 100755 --- a/webrtc-sys/libwebrtc/build_macos.sh +++ b/webrtc-sys/libwebrtc/build_macos.sh @@ -71,10 +71,6 @@ git apply "$COMMAND_DIR/patches/add_licenses.patch" -v --ignore-space-change --i git apply "$COMMAND_DIR/patches/ssl_verify_callback_with_native_handle.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn git apply "$COMMAND_DIR/patches/add_deps.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn -cd third_party -git apply "$COMMAND_DIR/patches/abseil_use_optional.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn -cd .. - cd .. mkdir -p "$ARTIFACTS_DIR/lib" @@ -104,6 +100,7 @@ gn gen "$OUTPUT_DIR" --root="src" \ rtc_enable_objc_symbol_export=false \ rtc_include_dav1d_in_internal_decoder_factory = true \ rtc_use_h264=true \ + rtc_use_h265=true \ use_custom_libcxx=false \ clang_use_chrome_plugins=false \ use_rtti=true \ @@ -117,7 +114,8 @@ ninja -C "$OUTPUT_DIR" :default \ sdk:default_codec_factory_objc \ pc:peer_connection \ sdk:videocapture_objc \ - sdk:mac_framework_objc + sdk:mac_framework_objc \ + desktop_capture_objc # make libwebrtc.a # don't include nasm @@ -132,4 +130,4 @@ cp "$OUTPUT_DIR/LICENSE.md" "$ARTIFACTS_DIR" cd src find . -name "*.h" -print | cpio -pd "$ARTIFACTS_DIR/include" - +find . -name "*.inc" -print | cpio -pd "$ARTIFACTS_DIR/include" diff --git a/webrtc-sys/libwebrtc/build_windows.cmd b/webrtc-sys/libwebrtc/build_windows.cmd index add24f4e9..1de0d83a1 100644 --- a/webrtc-sys/libwebrtc/build_windows.cmd +++ b/webrtc-sys/libwebrtc/build_windows.cmd @@ -40,10 +40,10 @@ set COMMAND_DIR=%~dp0 set PATH=%cd%\depot_tools;%PATH% set DEPOT_TOOLS_WIN_TOOLCHAIN=0 set GYP_GENERATORS=ninja,msvs-ninja -set GYP_MSVS_VERSION=2019 +set GYP_MSVS_VERSION=2022 set OUTPUT_DIR=src\out-!arch!-!profile! set ARTIFACTS_DIR=%cd%\win-!arch!-!profile! -set vs2019_install=C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional +set vs2019_install=C:\Program Files\Microsoft Visual Studio\2022\Enterprise if not exist src ( call gclient.bat sync -D --with_branch_heads --with_tags @@ -55,10 +55,6 @@ call git apply "%COMMAND_DIR%/patches/add_deps.patch" -v --ignore-space-change - call git apply "%COMMAND_DIR%/patches/windows_silence_warnings.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn call git apply "%COMMAND_DIR%/patches/ssl_verify_callback_with_native_handle.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn -cd third_party -call git apply "%COMMAND_DIR%/patches/abseil_use_optional.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn -cd .. - cd .. mkdir "%ARTIFACTS_DIR%\lib" @@ -88,4 +84,4 @@ copy "%OUTPUT_DIR%\LICENSE.md" "%ARTIFACTS_DIR%" rem copy header xcopy src\*.h "%ARTIFACTS_DIR%\include" /C /S /I /F /H - +xcopy src\*.inc "%ARTIFACTS_DIR%\include" /C /S /I /F /H diff --git a/webrtc-sys/libwebrtc/patches/abseil_use_optional.patch b/webrtc-sys/libwebrtc/patches/abseil_use_optional.patch deleted file mode 100644 index 476ad2619..000000000 --- a/webrtc-sys/libwebrtc/patches/abseil_use_optional.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/abseil-cpp/absl/base/options.h b/abseil-cpp/absl/base/options.h -index bd43b6ef0..ab5917e75 100644 ---- a/abseil-cpp/absl/base/options.h -+++ b/abseil-cpp/absl/base/options.h -@@ -121,7 +121,7 @@ - // absl::optional is a typedef of std::optional, use the feature macro - // ABSL_USES_STD_OPTIONAL. - --#define ABSL_OPTION_USE_STD_OPTIONAL 2 -+#define ABSL_OPTION_USE_STD_OPTIONAL 0 - - - // ABSL_OPTION_USE_STD_STRING_VIEW diff --git a/webrtc-sys/libwebrtc/patches/add_deps.patch b/webrtc-sys/libwebrtc/patches/add_deps.patch index 1b383de4e..92ae3fa8f 100644 --- a/webrtc-sys/libwebrtc/patches/add_deps.patch +++ b/webrtc-sys/libwebrtc/patches/add_deps.patch @@ -1,5 +1,5 @@ diff --git a/BUILD.gn b/BUILD.gn -index d5289b85d7..76823f6cff 100644 +index ca8d8faa61..13e07a2f28 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -24,6 +24,9 @@ @@ -12,7 +12,7 @@ index d5289b85d7..76823f6cff 100644 if (rtc_enable_protobuf) { import("//third_party/protobuf/proto_library.gni") } -@@ -292,6 +295,10 @@ config("common_config") { +@@ -331,6 +334,10 @@ config("common_config") { defines += [ "WEBRTC_INCLUDE_INTERNAL_AUDIO_DEVICE" ] } @@ -23,7 +23,7 @@ index d5289b85d7..76823f6cff 100644 if (rtc_libvpx_build_vp9) { defines += [ "RTC_ENABLE_VP9" ] } -@@ -517,6 +524,10 @@ if (!build_with_chromium) { +@@ -565,6 +572,10 @@ if (!build_with_chromium) { "pc:rtc_pc", "sdk", "video", diff --git a/webrtc-sys/libwebrtc/patches/android_use_libunwind.patch b/webrtc-sys/libwebrtc/patches/android_use_libunwind.patch index 82512a2fc..1339b31d8 100644 --- a/webrtc-sys/libwebrtc/patches/android_use_libunwind.patch +++ b/webrtc-sys/libwebrtc/patches/android_use_libunwind.patch @@ -2,7 +2,7 @@ +++ src/buildtools/third_party/libunwind/BUILD.gn 2023-07-10 10:19:23 @@ -21,7 +21,7 @@ config("libunwind_config") { - # TODO(crbug.com/1458042): Move this build file to third_party/libc++/BUILD.gn once submodule migration is done + # TODO(crbug.com/40273848): Move this build file to third_party/libc++/BUILD.gn once submodule migration is done source_set("libunwind") { - visibility = [ "//buildtools/third_party/libc++abi" ] + visibility = [ "//buildtools/third_party/libc++abi", "//build/config:common_deps" ] @@ -11,7 +11,7 @@ } --- src/build/config/BUILD.gn 2023-07-10 10:23:49 +++ src/build/config/BUILD.gn 2023-07-10 10:23:54 -@@ -246,6 +246,8 @@ group("common_deps") { +@@ -296,6 +296,8 @@ group("common_deps") { if (use_custom_libcxx) { public_deps += [ "//buildtools/third_party/libc++" ] @@ -19,4 +19,4 @@ + public_deps += [ "//buildtools/third_party/libunwind" ] } - if (use_afl) { + if (use_llvm_libatomic) { diff --git a/webrtc-sys/libwebrtc/patches/force_gcc.patch b/webrtc-sys/libwebrtc/patches/force_gcc.patch new file mode 100644 index 000000000..c409737d3 --- /dev/null +++ b/webrtc-sys/libwebrtc/patches/force_gcc.patch @@ -0,0 +1,25 @@ +diff --git a/config/c++/c++.gni b/config/c++/c++.gni +index c29d898fb..5ef014e51 100644 +--- a/config/c++/c++.gni ++++ b/config/c++/c++.gni +@@ -62,7 +62,7 @@ declare_args() { + # case. + # We disable that on LibFuzzer builds because it breaks the libfuzzer + # runtime. See crbug.com/411020147. +- use_llvm_libatomic = !is_apple && !is_nacl && !use_libfuzzer ++ use_llvm_libatomic = !is_apple && !is_nacl && !use_libfuzzer && !is_linux + } + + if (use_implicit_libcxx_modules) { +diff --git a/config/linux/BUILD.gn b/config/linux/BUILD.gn +index 131bb71d1..36c86c48e 100644 +--- a/config/linux/BUILD.gn ++++ b/config/linux/BUILD.gn +@@ -15,6 +15,7 @@ group("linux") { + # is applied to all targets. It is here to separate out the logic that is + # Linux-only. This is not applied to Android, but is applied to ChromeOS. + config("compiler") { ++ cflags_cc = [ "-Wno-changes-meaning" ] + if (current_cpu == "arm64") { + import("//build/config/arm.gni") + cflags = [] diff --git a/webrtc-sys/libwebrtc/patches/ssl_verify_callback_with_native_handle.patch b/webrtc-sys/libwebrtc/patches/ssl_verify_callback_with_native_handle.patch index ccfb138f7..df2a8ed69 100644 --- a/webrtc-sys/libwebrtc/patches/ssl_verify_callback_with_native_handle.patch +++ b/webrtc-sys/libwebrtc/patches/ssl_verify_callback_with_native_handle.patch @@ -1,8 +1,8 @@ diff --git a/rtc_base/boringssl_certificate.cc b/rtc_base/boringssl_certificate.cc -index 99b2ab3e24..c37d6d963f 100644 +index 016b4dabba..1a0c1b8d42 100644 --- a/rtc_base/boringssl_certificate.cc +++ b/rtc_base/boringssl_certificate.cc -@@ -253,6 +253,12 @@ BoringSSLCertificate::BoringSSLCertificate( +@@ -259,6 +259,12 @@ BoringSSLCertificate::BoringSSLCertificate( RTC_DCHECK(cert_buffer_ != nullptr); } @@ -16,10 +16,10 @@ index 99b2ab3e24..c37d6d963f 100644 OpenSSLKeyPair* key_pair, const SSLIdentityParams& params) { diff --git a/rtc_base/boringssl_certificate.h b/rtc_base/boringssl_certificate.h -index 8b4577a17c..e1fe26cba5 100644 +index 926dd332ec..5c62c63390 100644 --- a/rtc_base/boringssl_certificate.h +++ b/rtc_base/boringssl_certificate.h -@@ -33,6 +33,7 @@ class OpenSSLKeyPair; +@@ -34,6 +34,7 @@ namespace webrtc { class BoringSSLCertificate final : public SSLCertificate { public: explicit BoringSSLCertificate(bssl::UniquePtr cert_buffer); @@ -27,7 +27,7 @@ index 8b4577a17c..e1fe26cba5 100644 static std::unique_ptr Generate( OpenSSLKeyPair* key_pair, -@@ -74,6 +75,11 @@ class BoringSSLCertificate final : public SSLCertificate { +@@ -66,6 +67,11 @@ class BoringSSLCertificate final : public SSLCertificate { private: // A handle to the DER encoded certificate data. bssl::UniquePtr cert_buffer_; @@ -38,12 +38,12 @@ index 8b4577a17c..e1fe26cba5 100644 + SSL* ssl() const { return ssl_; } }; - } // namespace rtc + } // namespace webrtc diff --git a/rtc_base/openssl_adapter.cc b/rtc_base/openssl_adapter.cc -index bc10e619eb..836ef9ea18 100644 +index cb5dfc6a85..a2de51ee74 100644 --- a/rtc_base/openssl_adapter.cc +++ b/rtc_base/openssl_adapter.cc -@@ -822,7 +822,7 @@ enum ssl_verify_result_t OpenSSLAdapter::SSLVerifyInternal(SSL* ssl, +@@ -840,7 +840,7 @@ enum ssl_verify_result_t OpenSSLAdapter::SSLVerifyInternal(SSL* ssl, return ssl_verify_invalid; } @@ -52,20 +52,20 @@ index bc10e619eb..836ef9ea18 100644 if (!ssl_cert_verifier_->Verify(cert)) { RTC_LOG(LS_WARNING) << "Failed to verify certificate using custom callback"; return ssl_verify_invalid; -@@ -894,7 +894,7 @@ int OpenSSLAdapter::SSLVerifyInternal(int previous_status, +@@ -912,7 +912,7 @@ int OpenSSLAdapter::SSLVerifyInternal(int previous_status, RTC_LOG(LS_ERROR) << "Failed to allocate CRYPTO_BUFFER."; return previous_status; } - const BoringSSLCertificate cert(std::move(crypto_buffer)); + const BoringSSLCertificate cert(std::move(crypto_buffer), ssl); #else - const OpenSSLCertificate cert(X509_STORE_CTX_get_current_cert(store)); + const webrtc::OpenSSLCertificate cert(X509_STORE_CTX_get_current_cert(store)); #endif diff --git a/rtc_base/openssl_stream_adapter.cc b/rtc_base/openssl_stream_adapter.cc -index dd82e4f061..6d7e39c534 100644 +index b46faff911..537171b1f3 100644 --- a/rtc_base/openssl_stream_adapter.cc +++ b/rtc_base/openssl_stream_adapter.cc -@@ -1154,7 +1154,7 @@ enum ssl_verify_result_t OpenSSLStreamAdapter::SSLVerifyCallback( +@@ -1181,7 +1181,7 @@ enum ssl_verify_result_t OpenSSLStreamAdapter::SSLVerifyCallback( // Creates certificate chain. std::vector> cert_chain; for (CRYPTO_BUFFER* cert : chain) { diff --git a/webrtc-sys/src/apm.cpp b/webrtc-sys/src/apm.cpp index 0c6bcd1ec..4fe34d2be 100644 --- a/webrtc-sys/src/apm.cpp +++ b/webrtc-sys/src/apm.cpp @@ -1,5 +1,8 @@ #include "livekit/apm.h" +#include "api/audio/builtin_audio_processing_builder.h" +#include "api/environment/environment_factory.h" + #include #include @@ -7,7 +10,8 @@ namespace livekit { AudioProcessingModule::AudioProcessingModule( const AudioProcessingConfig& config) { - apm_ = webrtc::AudioProcessingBuilder().Create(); + apm_ = webrtc::BuiltinAudioProcessingBuilder() + .Build(webrtc::CreateEnvironment()); apm_->ApplyConfig(config.ToWebrtcConfig()); apm_->Initialize(); diff --git a/webrtc-sys/src/audio_device.cpp b/webrtc-sys/src/audio_device.cpp index d146a0c8a..364ca01f7 100644 --- a/webrtc-sys/src/audio_device.cpp +++ b/webrtc-sys/src/audio_device.cpp @@ -328,7 +328,7 @@ int AudioDevice::GetRecordAudioParameters( } #endif // WEBRTC_IOS -int32_t AudioDevice::SetAudioDeviceSink(webrtc::AudioDeviceSink* sink) const { +int32_t AudioDevice::SetObserver(webrtc::AudioDeviceObserver* observer) { return 0; } diff --git a/webrtc-sys/src/audio_resampler.cpp b/webrtc-sys/src/audio_resampler.cpp index cbb6060d4..8618018c8 100644 --- a/webrtc-sys/src/audio_resampler.cpp +++ b/webrtc-sys/src/audio_resampler.cpp @@ -19,6 +19,7 @@ #include #include "audio/remix_resample.h" +#include "api/audio/audio_view.h" namespace livekit { @@ -30,8 +31,10 @@ size_t AudioResampler::remix_and_resample(const int16_t* src, int dest_sample_rate) { frame_.num_channels_ = dest_num_channels; frame_.sample_rate_hz_ = dest_sample_rate; - webrtc::voe::RemixAndResample(src, samples_per_channel, num_channels, - sample_rate, &resampler_, &frame_); + webrtc::InterleavedView source(static_cast(src), + samples_per_channel, + num_channels); + webrtc::voe::RemixAndResample(source, sample_rate, &resampler_, &frame_); return frame_.num_channels() * frame_.samples_per_channel() * sizeof(int16_t); } diff --git a/webrtc-sys/src/audio_track.cpp b/webrtc-sys/src/audio_track.cpp index 8ca76e801..16e62f4c2 100644 --- a/webrtc-sys/src/audio_track.cpp +++ b/webrtc-sys/src/audio_track.cpp @@ -56,7 +56,7 @@ inline AudioSourceOptions to_rust_audio_options( } AudioTrack::AudioTrack(std::shared_ptr rtc_runtime, - rtc::scoped_refptr track) + webrtc::scoped_refptr track) : MediaStreamTrack(rtc_runtime, std::move(track)) {} AudioTrack::~AudioTrack() { @@ -99,9 +99,11 @@ void NativeAudioSink::OnData(const void* audio_data, const int16_t* data = static_cast(audio_data); if (sample_rate_ != sample_rate || num_channels_ != number_of_channels) { + webrtc::InterleavedView source(static_cast(data), + number_of_frames, + number_of_channels); // resample/remix before capturing - webrtc::voe::RemixAndResample(data, number_of_frames, number_of_channels, - sample_rate, &resampler_, &frame_); + webrtc::voe::RemixAndResample(source, sample_rate, &resampler_, &frame_); rust::Slice rust_slice( frame_.data(), frame_.num_channels() * frame_.samples_per_channel()); @@ -270,7 +272,7 @@ AudioTrackSource::AudioTrackSource(AudioSourceOptions options, int num_channels, int queue_size_ms, webrtc::TaskQueueFactory* task_queue_factory) - : source_(rtc::make_ref_counted( + : source_(webrtc::make_ref_counted( to_native_audio_options(options), sample_rate, num_channels, @@ -311,7 +313,7 @@ std::shared_ptr new_audio_track_source( GetGlobalTaskQueueFactory()); } -rtc::scoped_refptr AudioTrackSource::get() +webrtc::scoped_refptr AudioTrackSource::get() const { return source_; } diff --git a/webrtc-sys/src/data_channel.cpp b/webrtc-sys/src/data_channel.cpp index bec5cef4e..70d15122e 100644 --- a/webrtc-sys/src/data_channel.cpp +++ b/webrtc-sys/src/data_channel.cpp @@ -37,14 +37,14 @@ webrtc::DataChannelInit to_native_data_channel_init(DataChannelInit init) { rtc_init.maxRetransmits = init.max_retransmits; if (init.has_priority) - rtc_init.priority = static_cast(init.priority); + rtc_init.priority = webrtc::PriorityValue(static_cast(init.priority)); return rtc_init; } DataChannel::DataChannel( std::shared_ptr rtc_runtime, - rtc::scoped_refptr data_channel) + webrtc::scoped_refptr data_channel) : rtc_runtime_(rtc_runtime), data_channel_(std::move(data_channel)) { RTC_LOG(LS_VERBOSE) << "DataChannel::DataChannel()"; } @@ -73,7 +73,7 @@ void DataChannel::unregister_observer() const { bool DataChannel::send(const DataBuffer& buffer) const { return data_channel_->Send(webrtc::DataBuffer{ - rtc::CopyOnWriteBuffer(buffer.ptr, buffer.len), buffer.binary}); + webrtc::CopyOnWriteBuffer(buffer.ptr, buffer.len), buffer.binary}); } int DataChannel::id() const { diff --git a/webrtc-sys/src/media_stream.cpp b/webrtc-sys/src/media_stream.cpp index ed6f78e10..3a4ad1275 100644 --- a/webrtc-sys/src/media_stream.cpp +++ b/webrtc-sys/src/media_stream.cpp @@ -33,7 +33,7 @@ namespace livekit { MediaStream::MediaStream( std::shared_ptr rtc_runtime, - rtc::scoped_refptr stream) + webrtc::scoped_refptr stream) : rtc_runtime_(rtc_runtime), media_stream_(std::move(stream)) {} rust::String MediaStream::id() const { @@ -73,12 +73,12 @@ std::shared_ptr MediaStream::find_video_track( bool MediaStream::add_track(std::shared_ptr track) const { if (track->kind() == webrtc::MediaStreamTrackInterface::kVideoKind) { return media_stream_->AddTrack( - rtc::scoped_refptr( + webrtc::scoped_refptr( static_cast( track->rtc_track().get()))); } else { return media_stream_->AddTrack( - rtc::scoped_refptr( + webrtc::scoped_refptr( static_cast( track->rtc_track().get()))); } @@ -87,12 +87,12 @@ bool MediaStream::add_track(std::shared_ptr track) const { bool MediaStream::remove_track(std::shared_ptr track) const { if (track->kind() == webrtc::MediaStreamTrackInterface::kVideoKind) { return media_stream_->RemoveTrack( - rtc::scoped_refptr( + webrtc::scoped_refptr( static_cast( track->rtc_track().get()))); } else { return media_stream_->RemoveTrack( - rtc::scoped_refptr( + webrtc::scoped_refptr( static_cast( track->rtc_track().get()))); } diff --git a/webrtc-sys/src/media_stream_track.cpp b/webrtc-sys/src/media_stream_track.cpp index dafefca60..1f9fc1e3c 100644 --- a/webrtc-sys/src/media_stream_track.cpp +++ b/webrtc-sys/src/media_stream_track.cpp @@ -32,7 +32,7 @@ namespace livekit { MediaStreamTrack::MediaStreamTrack( std::shared_ptr rtc_runtime, - rtc::scoped_refptr track) + webrtc::scoped_refptr track) : rtc_runtime_(rtc_runtime), track_(std::move(track)) {} rust::String MediaStreamTrack::kind() const { diff --git a/webrtc-sys/src/nvidia/h264_decoder_impl.cpp b/webrtc-sys/src/nvidia/h264_decoder_impl.cpp index 3ba449b38..af716c503 100644 --- a/webrtc-sys/src/nvidia/h264_decoder_impl.cpp +++ b/webrtc-sys/src/nvidia/h264_decoder_impl.cpp @@ -110,8 +110,8 @@ int32_t NvidiaH264DecoderImpl::Decode(const EncodedImage& input_image, } h264_bitstream_parser_.ParseBitstream(input_image); - absl::optional qp = h264_bitstream_parser_.GetLastSliceQp(); - absl::optional sps = h264_bitstream_parser_.sps(); + std::optional qp = h264_bitstream_parser_.GetLastSliceQp(); + std::optional sps = h264_bitstream_parser_.sps(); if (is_configured_decoder_) { if (!sps || @@ -149,7 +149,7 @@ int32_t NvidiaH264DecoderImpl::Decode(const EncodedImage& input_image, int64_t timeStamp; uint8_t* pFrame = decoder_->GetFrame(&timeStamp); - rtc::scoped_refptr i420_buffer = + webrtc::scoped_refptr i420_buffer = buffer_pool_.CreateI420Buffer(decoder_->GetWidth(), decoder_->GetHeight()); @@ -176,7 +176,7 @@ int32_t NvidiaH264DecoderImpl::Decode(const EncodedImage& input_image, .build(); // todo: measurement decoding time - absl::optional decodetime; + std::optional decodetime; decoded_complete_callback_->Decoded(decoded_frame, decodetime, qp); } diff --git a/webrtc-sys/src/nvidia/h264_decoder_impl.h b/webrtc-sys/src/nvidia/h264_decoder_impl.h index 882f8f0f7..4674a3b5c 100644 --- a/webrtc-sys/src/nvidia/h264_decoder_impl.h +++ b/webrtc-sys/src/nvidia/h264_decoder_impl.h @@ -20,8 +20,8 @@ namespace webrtc { class H264BitstreamParserEx : public ::webrtc::H264BitstreamParser { public: - absl::optional sps() { return sps_; } - absl::optional pps() { return pps_; } + std::optional sps() { return sps_; } + std::optional pps() { return pps_; } }; class NvidiaH264DecoderImpl : public VideoDecoder { diff --git a/webrtc-sys/src/nvidia/h264_encoder_impl.cpp b/webrtc-sys/src/nvidia/h264_encoder_impl.cpp index f32e72a42..cd8ed881c 100644 --- a/webrtc-sys/src/nvidia/h264_encoder_impl.cpp +++ b/webrtc-sys/src/nvidia/h264_encoder_impl.cpp @@ -80,7 +80,8 @@ NvidiaH264EncoderImpl::NvidiaH264EncoderImpl( CUmemorytype memory_type, NV_ENC_BUFFER_FORMAT nv_format, const SdpVideoFormat& format) - : encoder_(nullptr), + : env_(env), + encoder_(nullptr), cu_context_(context), cu_memory_type_(memory_type), cu_scaled_array_(nullptr), @@ -89,7 +90,7 @@ NvidiaH264EncoderImpl::NvidiaH264EncoderImpl( H264EncoderSettings::Parse(format).packetization_mode), format_(format) { std::string hexString = format_.parameters.at("profile-level-id"); - absl::optional profile_level_id = + std::optional profile_level_id = webrtc::ParseH264ProfileLevelId(hexString.c_str()); if (profile_level_id.has_value()) { profile_ = profile_level_id->profile; @@ -237,7 +238,7 @@ int32_t NvidiaH264EncoderImpl::InitEncode( return WEBRTC_VIDEO_CODEC_ERROR; } - SimulcastRateAllocator init_allocator(codec_); + SimulcastRateAllocator init_allocator(env_, codec_); VideoBitrateAllocation allocation = init_allocator.Allocate(VideoBitrateAllocationParameters( DataRate::KilobitsPerSec(codec_.startBitrate), codec_.maxFramerate)); @@ -279,7 +280,7 @@ int32_t NvidiaH264EncoderImpl::Encode( return WEBRTC_VIDEO_CODEC_UNINITIALIZED; } - rtc::scoped_refptr frame_buffer = + webrtc::scoped_refptr frame_buffer = input_frame.video_frame_buffer()->ToI420(); if (!frame_buffer) { RTC_LOG(LS_ERROR) << "Failed to convert " @@ -361,7 +362,7 @@ int32_t NvidiaH264EncoderImpl::ProcessEncodedFrame( const ::webrtc::VideoFrame& inputFrame) { encoded_image_._encodedWidth = encoder_->GetEncodeWidth(); encoded_image_._encodedHeight = encoder_->GetEncodeHeight(); - encoded_image_.SetRtpTimestamp(inputFrame.timestamp()); + encoded_image_.SetRtpTimestamp(inputFrame.rtp_timestamp()); encoded_image_.SetSimulcastIndex(0); encoded_image_.ntp_time_ms_ = inputFrame.ntp_time_ms(); encoded_image_.capture_time_ms_ = inputFrame.render_time_ms(); @@ -371,7 +372,7 @@ int32_t NvidiaH264EncoderImpl::ProcessEncodedFrame( encoded_image_._frameType = VideoFrameType::kVideoFrameDelta; encoded_image_.SetColorSpace(inputFrame.color_space()); std::vector naluIndices = - H264::FindNaluIndices(packet.data(), packet.size()); + H264::FindNaluIndices(MakeArrayView(packet.data(), packet.size())); for (uint32_t i = 0; i < naluIndices.size(); i++) { const H264::NaluType naluType = H264::ParseNaluType(packet[naluIndices[i].payload_start_offset]); diff --git a/webrtc-sys/src/nvidia/h264_encoder_impl.h b/webrtc-sys/src/nvidia/h264_encoder_impl.h index d1009fbb1..b50a7e308 100644 --- a/webrtc-sys/src/nvidia/h264_encoder_impl.h +++ b/webrtc-sys/src/nvidia/h264_encoder_impl.h @@ -67,6 +67,7 @@ class NvidiaH264EncoderImpl : public VideoEncoder { int32_t ProcessEncodedFrame(std::vector& packet, const ::webrtc::VideoFrame& inputFrame); private: + const webrtc::Environment& env_; EncodedImageCallback* encoded_image_callback_ = nullptr; std::unique_ptr encoder_; diff --git a/webrtc-sys/src/objc_video_frame_buffer.mm b/webrtc-sys/src/objc_video_frame_buffer.mm index e8490beb1..6621b0f8e 100644 --- a/webrtc-sys/src/objc_video_frame_buffer.mm +++ b/webrtc-sys/src/objc_video_frame_buffer.mm @@ -26,7 +26,7 @@ CVPixelBufferRef pixelBuffer ) { RTCCVPixelBuffer *buffer = [[RTCCVPixelBuffer alloc] initWithPixelBuffer:pixelBuffer]; - rtc::scoped_refptr frame_buffer = webrtc::ObjCToNativeVideoFrameBuffer(buffer); + webrtc::scoped_refptr frame_buffer = webrtc::ObjCToNativeVideoFrameBuffer(buffer); [buffer release]; CVPixelBufferRelease(pixelBuffer); return std::make_unique(frame_buffer); diff --git a/webrtc-sys/src/peer_connection.cpp b/webrtc-sys/src/peer_connection.cpp index a3de766c6..17944ebf9 100644 --- a/webrtc-sys/src/peer_connection.cpp +++ b/webrtc-sys/src/peer_connection.cpp @@ -73,7 +73,7 @@ to_native_offer_answer_options(const RtcOfferAnswerOptions& options) { PeerConnection::PeerConnection( std::shared_ptr rtc_runtime, - rtc::scoped_refptr pc_factory, + webrtc::scoped_refptr pc_factory, rust::Box observer) : rtc_runtime_(std::move(rtc_runtime)), pc_factory_(std::move(pc_factory)), @@ -115,8 +115,8 @@ void PeerConnection::create_offer( rust::Fn, std::unique_ptr)> on_success, rust::Fn, RtcError)> on_error) const { - rtc::scoped_refptr observer = - rtc::make_ref_counted(std::move(ctx), on_success, + webrtc::scoped_refptr observer = + webrtc::make_ref_counted(std::move(ctx), on_success, on_error); peer_connection_->CreateOffer(observer.get(), @@ -129,8 +129,8 @@ void PeerConnection::create_answer( rust::Fn, std::unique_ptr)> on_success, rust::Fn, RtcError)> on_error) const { - rtc::scoped_refptr observer = - rtc::make_ref_counted(std::move(ctx), on_success, + webrtc::scoped_refptr observer = + webrtc::make_ref_counted(std::move(ctx), on_success, on_error); peer_connection_->CreateAnswer(observer.get(), @@ -141,8 +141,8 @@ void PeerConnection::set_local_description( std::unique_ptr desc, rust::Box ctx, rust::Fn, RtcError)> on_complete) const { - rtc::scoped_refptr observer = - rtc::make_ref_counted(std::move(ctx), + webrtc::scoped_refptr observer = + webrtc::make_ref_counted(std::move(ctx), on_complete); peer_connection_->SetLocalDescription(desc->clone()->release(), observer); @@ -152,8 +152,8 @@ void PeerConnection::set_remote_description( std::unique_ptr desc, rust::Box ctx, rust::Fn, RtcError)> on_complete) const { - rtc::scoped_refptr observer = - rtc::make_ref_counted(std::move(ctx), + webrtc::scoped_refptr observer = + webrtc::make_ref_counted(std::move(ctx), on_complete); peer_connection_->SetRemoteDescription(desc->clone()->release(), observer); @@ -209,7 +209,7 @@ void PeerConnection::remove_track(std::shared_ptr sender) const { void PeerConnection::get_stats( rust::Box ctx, rust::Fn, rust::String)> on_stats) const { - auto observer = rtc::make_ref_counted>( + auto observer = webrtc::make_ref_counted>( std::move(ctx), on_stats); peer_connection_->GetStats(observer.get()); } @@ -230,7 +230,7 @@ std::shared_ptr PeerConnection::add_transceiver_for_media( MediaType media_type, RtpTransceiverInit init) const { auto result = peer_connection_->AddTransceiver( - static_cast(media_type), + static_cast(media_type), to_native_rtp_transceiver_init(init)); if (!result.ok()) @@ -350,19 +350,19 @@ void PeerConnection::OnSignalingChange( } void PeerConnection::OnAddStream( - rtc::scoped_refptr stream) { + webrtc::scoped_refptr stream) { observer_->on_add_stream(std::make_unique(rtc_runtime_, stream)); } void PeerConnection::OnRemoveStream( - rtc::scoped_refptr stream) { + webrtc::scoped_refptr stream) { // Find current MediaStream // observer_->on_remove_stream(std::make_unique(rtc_runtime_, // stream)); } void PeerConnection::OnDataChannel( - rtc::scoped_refptr data_channel) { + webrtc::scoped_refptr data_channel) { observer_->on_data_channel( std::make_shared(rtc_runtime_, data_channel)); } @@ -444,8 +444,8 @@ void PeerConnection::OnIceSelectedCandidatePairChanged( } void PeerConnection::OnAddTrack( - rtc::scoped_refptr receiver, - const std::vector>& + webrtc::scoped_refptr receiver, + const std::vector>& streams) { rust::Vec vec; @@ -460,13 +460,13 @@ void PeerConnection::OnAddTrack( } void PeerConnection::OnTrack( - rtc::scoped_refptr transceiver) { + webrtc::scoped_refptr transceiver) { observer_->on_track(std::make_unique( rtc_runtime_, transceiver, peer_connection_)); } void PeerConnection::OnRemoveTrack( - rtc::scoped_refptr receiver) { + webrtc::scoped_refptr receiver) { observer_->on_remove_track( std::make_unique(rtc_runtime_, receiver, peer_connection_)); } diff --git a/webrtc-sys/src/peer_connection_factory.cpp b/webrtc-sys/src/peer_connection_factory.cpp index 31b32729e..2980a36e5 100644 --- a/webrtc-sys/src/peer_connection_factory.cpp +++ b/webrtc-sys/src/peer_connection_factory.cpp @@ -21,7 +21,10 @@ #include "api/audio_codecs/builtin_audio_decoder_factory.h" #include "api/audio_codecs/builtin_audio_encoder_factory.h" +#include "api/audio/builtin_audio_processing_builder.h" +#include "api/environment/environment_factory.h" #include "api/peer_connection_interface.h" +#include "api/transport/field_trial_based_config.h" #include "api/rtc_error.h" #include "api/enable_media.h" #include "api/rtc_event_log/rtc_event_log_factory.h" @@ -59,7 +62,7 @@ PeerConnectionFactory::PeerConnectionFactory( dependencies.trials = std::make_unique(); audio_device_ = rtc_runtime_->worker_thread()->BlockingCall([&] { - return rtc::make_ref_counted( + return webrtc::make_ref_counted( dependencies.task_queue_factory.get()); }); @@ -71,7 +74,8 @@ PeerConnectionFactory::PeerConnectionFactory( std::move(std::make_unique()); dependencies.audio_encoder_factory = webrtc::CreateBuiltinAudioEncoderFactory(); dependencies.audio_decoder_factory = webrtc::CreateBuiltinAudioDecoderFactory(); - dependencies.audio_processing = webrtc::AudioProcessingBuilder().Create(); + dependencies.audio_processing = webrtc::BuiltinAudioProcessingBuilder() + .Build(webrtc::CreateEnvironment()); webrtc::EnableMedia(dependencies); peer_factory_ = @@ -127,13 +131,13 @@ std::shared_ptr PeerConnectionFactory::create_audio_track( RtpCapabilities PeerConnectionFactory::rtp_sender_capabilities( MediaType type) const { return to_rust_rtp_capabilities(peer_factory_->GetRtpSenderCapabilities( - static_cast(type))); + static_cast(type))); } RtpCapabilities PeerConnectionFactory::rtp_receiver_capabilities( MediaType type) const { return to_rust_rtp_capabilities(peer_factory_->GetRtpReceiverCapabilities( - static_cast(type))); + static_cast(type))); } std::shared_ptr create_peer_connection_factory() { diff --git a/webrtc-sys/src/rtp_parameters.cpp b/webrtc-sys/src/rtp_parameters.cpp index 75bbcd4c9..66ffe0d99 100644 --- a/webrtc-sys/src/rtp_parameters.cpp +++ b/webrtc-sys/src/rtp_parameters.cpp @@ -34,7 +34,7 @@ webrtc::RtpCodecCapability to_native_rtp_codec_capability( // native.mime_type(); IGNORED native.name = capability.name.c_str(); - native.kind = static_cast(capability.kind); + native.kind = static_cast(capability.kind); if (capability.has_clock_rate) native.clock_rate = capability.clock_rate; @@ -133,7 +133,7 @@ webrtc::RtpCodecParameters to_native_rtp_codec_parameters( RtpCodecParameters params) { webrtc::RtpCodecParameters native{}; native.name = params.name.c_str(); - native.kind = static_cast(params.kind); + native.kind = static_cast(params.kind); native.payload_type = params.payload_type; for (auto pair : params.parameters) diff --git a/webrtc-sys/src/rtp_receiver.cpp b/webrtc-sys/src/rtp_receiver.cpp index 857f7a71a..04524c0c9 100644 --- a/webrtc-sys/src/rtp_receiver.cpp +++ b/webrtc-sys/src/rtp_receiver.cpp @@ -27,8 +27,8 @@ namespace livekit { RtpReceiver::RtpReceiver( std::shared_ptr rtc_runtime, - rtc::scoped_refptr receiver, - rtc::scoped_refptr peer_connection) + webrtc::scoped_refptr receiver, + webrtc::scoped_refptr peer_connection) : rtc_runtime_(rtc_runtime), receiver_(std::move(receiver)), peer_connection_(std::move(peer_connection)) {} @@ -48,7 +48,7 @@ void RtpReceiver::get_stats( rust::Box ctx, rust::Fn, rust::String)> on_stats) const { auto observer = - rtc::make_ref_counted>(std::move(ctx), on_stats); + webrtc::make_ref_counted>(std::move(ctx), on_stats); peer_connection_->GetStats(receiver_, observer); } diff --git a/webrtc-sys/src/rtp_sender.cpp b/webrtc-sys/src/rtp_sender.cpp index 8d5008f81..f56477466 100644 --- a/webrtc-sys/src/rtp_sender.cpp +++ b/webrtc-sys/src/rtp_sender.cpp @@ -26,8 +26,8 @@ namespace livekit { RtpSender::RtpSender( std::shared_ptr rtc_runtime, - rtc::scoped_refptr sender, - rtc::scoped_refptr peer_connection) + webrtc::scoped_refptr sender, + webrtc::scoped_refptr peer_connection) : rtc_runtime_(rtc_runtime), sender_(std::move(sender)), peer_connection_(std::move(peer_connection)) {} @@ -48,7 +48,7 @@ void RtpSender::get_stats( rust::Box ctx, rust::Fn, rust::String)> on_stats) const { auto observer = - rtc::make_ref_counted>(std::move(ctx), on_stats); + webrtc::make_ref_counted>(std::move(ctx), on_stats); peer_connection_->GetStats(sender_, observer); } diff --git a/webrtc-sys/src/rtp_transceiver.cpp b/webrtc-sys/src/rtp_transceiver.cpp index 82eb73b2f..62c20dcb2 100644 --- a/webrtc-sys/src/rtp_transceiver.cpp +++ b/webrtc-sys/src/rtp_transceiver.cpp @@ -38,8 +38,8 @@ webrtc::RtpTransceiverInit to_native_rtp_transceiver_init( RtpTransceiver::RtpTransceiver( std::shared_ptr rtc_runtime, - rtc::scoped_refptr transceiver, - rtc::scoped_refptr peer_connection) + webrtc::scoped_refptr transceiver, + webrtc::scoped_refptr peer_connection) : rtc_runtime_(rtc_runtime), transceiver_(std::move(transceiver)), peer_connection_(std::move(peer_connection)) {} diff --git a/webrtc-sys/src/vaapi/h264_encoder_impl.cpp b/webrtc-sys/src/vaapi/h264_encoder_impl.cpp index ec7965997..b2232bdcd 100644 --- a/webrtc-sys/src/vaapi/h264_encoder_impl.cpp +++ b/webrtc-sys/src/vaapi/h264_encoder_impl.cpp @@ -34,12 +34,13 @@ enum H264EncoderImplEvent { VAAPIH264EncoderWrapper::VAAPIH264EncoderWrapper(const webrtc::Environment& env, const SdpVideoFormat& format) - : encoder_(new livekit::VaapiH264EncoderWrapper()), + : env_(env), + encoder_(new livekit::VaapiH264EncoderWrapper()), packetization_mode_( H264EncoderSettings::Parse(format).packetization_mode), format_(format) { std::string hexString = format_.parameters.at("profile-level-id"); - absl::optional profile_level_id = + std::optional profile_level_id = webrtc::ParseH264ProfileLevelId(hexString.c_str()); if (profile_level_id.has_value()) { profile_ = profile_level_id->profile; @@ -152,7 +153,7 @@ int32_t VAAPIH264EncoderWrapper::InitEncode( va_profile, VA_RC_CBR); } - SimulcastRateAllocator init_allocator(codec_); + SimulcastRateAllocator init_allocator(env_, codec_); VideoBitrateAllocation allocation = init_allocator.Allocate(VideoBitrateAllocationParameters( DataRate::KilobitsPerSec(codec_.startBitrate), codec_.maxFramerate)); @@ -189,7 +190,7 @@ int32_t VAAPIH264EncoderWrapper::Encode( return WEBRTC_VIDEO_CODEC_UNINITIALIZED; } - rtc::scoped_refptr frame_buffer = + webrtc::scoped_refptr frame_buffer = input_frame.video_frame_buffer()->ToI420(); if (!frame_buffer) { RTC_LOG(LS_ERROR) << "Failed to convert " diff --git a/webrtc-sys/src/vaapi/h264_encoder_impl.h b/webrtc-sys/src/vaapi/h264_encoder_impl.h index ad67e7fb5..be800a757 100644 --- a/webrtc-sys/src/vaapi/h264_encoder_impl.h +++ b/webrtc-sys/src/vaapi/h264_encoder_impl.h @@ -61,6 +61,7 @@ class VAAPIH264EncoderWrapper : public VideoEncoder { VAProfile GetVAProfile() const; private: + const webrtc::Environment& env_; EncodedImageCallback* encoded_image_callback_ = nullptr; std::unique_ptr encoder_; LayerConfig configuration_; diff --git a/webrtc-sys/src/video_encoder_factory.cpp b/webrtc-sys/src/video_encoder_factory.cpp index 351a5c79f..953c9be6d 100644 --- a/webrtc-sys/src/video_encoder_factory.cpp +++ b/webrtc-sys/src/video_encoder_factory.cpp @@ -98,7 +98,7 @@ VideoEncoderFactory::InternalFactory::GetSupportedFormats() const { VideoEncoderFactory::CodecSupport VideoEncoderFactory::InternalFactory::QueryCodecSupport( const webrtc::SdpVideoFormat& format, - absl::optional scalability_mode) const { + std::optional scalability_mode) const { auto original_format = webrtc::FuzzyMatchSdpVideoFormat(Factory().GetSupportedFormats(), format); return original_format @@ -139,7 +139,7 @@ std::vector VideoEncoderFactory::GetSupportedFormats() VideoEncoderFactory::CodecSupport VideoEncoderFactory::QueryCodecSupport( const webrtc::SdpVideoFormat& format, - absl::optional scalability_mode) const { + std::optional scalability_mode) const { return internal_factory_->QueryCodecSupport(format, scalability_mode); } diff --git a/webrtc-sys/src/video_frame.cpp b/webrtc-sys/src/video_frame.cpp index ad082bdb8..e2dfc0a1c 100644 --- a/webrtc-sys/src/video_frame.cpp +++ b/webrtc-sys/src/video_frame.cpp @@ -43,7 +43,7 @@ int64_t VideoFrame::ntp_time_ms() const { return frame_.ntp_time_ms(); } uint32_t VideoFrame::timestamp() const { - return frame_.timestamp(); + return frame_.rtp_timestamp(); } VideoRotation VideoFrame::rotation() const { diff --git a/webrtc-sys/src/video_frame_buffer.cpp b/webrtc-sys/src/video_frame_buffer.cpp index b33ccf18a..32d5c0320 100644 --- a/webrtc-sys/src/video_frame_buffer.cpp +++ b/webrtc-sys/src/video_frame_buffer.cpp @@ -21,7 +21,7 @@ namespace livekit { VideoFrameBuffer::VideoFrameBuffer( - rtc::scoped_refptr buffer) + webrtc::scoped_refptr buffer) : buffer_(std::move(buffer)) {} VideoFrameBufferType VideoFrameBuffer::buffer_type() const { @@ -43,46 +43,46 @@ std::unique_ptr VideoFrameBuffer::to_i420() const { // const_cast is valid here because we take the ownership on the rust side std::unique_ptr VideoFrameBuffer::get_i420() { return std::make_unique( - rtc::scoped_refptr( + webrtc::scoped_refptr( const_cast(buffer_->GetI420()))); } std::unique_ptr VideoFrameBuffer::get_i420a() { return std::make_unique( - rtc::scoped_refptr( + webrtc::scoped_refptr( const_cast(buffer_->GetI420A()))); } std::unique_ptr VideoFrameBuffer::get_i422() { return std::make_unique( - rtc::scoped_refptr( + webrtc::scoped_refptr( const_cast(buffer_->GetI422()))); } std::unique_ptr VideoFrameBuffer::get_i444() { return std::make_unique( - rtc::scoped_refptr( + webrtc::scoped_refptr( const_cast(buffer_->GetI444()))); } std::unique_ptr VideoFrameBuffer::get_i010() { return std::make_unique( - rtc::scoped_refptr( + webrtc::scoped_refptr( const_cast(buffer_->GetI010()))); } std::unique_ptr VideoFrameBuffer::get_nv12() { return std::make_unique( - rtc::scoped_refptr( + webrtc::scoped_refptr( const_cast(buffer_->GetNV12()))); } -rtc::scoped_refptr VideoFrameBuffer::get() const { +webrtc::scoped_refptr VideoFrameBuffer::get() const { return buffer_; } PlanarYuvBuffer::PlanarYuvBuffer( - rtc::scoped_refptr buffer) + webrtc::scoped_refptr buffer) : VideoFrameBuffer(buffer) {} unsigned int PlanarYuvBuffer::chroma_width() const { @@ -110,7 +110,7 @@ webrtc::PlanarYuvBuffer* PlanarYuvBuffer::buffer() const { } PlanarYuv8Buffer::PlanarYuv8Buffer( - rtc::scoped_refptr buffer) + webrtc::scoped_refptr buffer) : PlanarYuvBuffer(buffer) {} const uint8_t* PlanarYuv8Buffer::data_y() const { @@ -130,7 +130,7 @@ webrtc::PlanarYuv8Buffer* PlanarYuv8Buffer::buffer() const { } PlanarYuv16BBuffer::PlanarYuv16BBuffer( - rtc::scoped_refptr buffer) + webrtc::scoped_refptr buffer) : PlanarYuvBuffer(buffer) {} const uint16_t* PlanarYuv16BBuffer::data_y() const { @@ -150,7 +150,7 @@ webrtc::PlanarYuv16BBuffer* PlanarYuv16BBuffer::buffer() const { } BiplanarYuvBuffer::BiplanarYuvBuffer( - rtc::scoped_refptr buffer) + webrtc::scoped_refptr buffer) : VideoFrameBuffer(buffer) {} unsigned int BiplanarYuvBuffer::chroma_width() const { @@ -174,7 +174,7 @@ webrtc::BiplanarYuvBuffer* BiplanarYuvBuffer::buffer() const { } BiplanarYuv8Buffer::BiplanarYuv8Buffer( - rtc::scoped_refptr buffer) + webrtc::scoped_refptr buffer) : BiplanarYuvBuffer(buffer) {} const uint8_t* BiplanarYuv8Buffer::data_y() const { @@ -189,11 +189,11 @@ webrtc::BiplanarYuv8Buffer* BiplanarYuv8Buffer::buffer() const { return static_cast(buffer_.get()); } -I420Buffer::I420Buffer(rtc::scoped_refptr buffer) +I420Buffer::I420Buffer(webrtc::scoped_refptr buffer) : PlanarYuv8Buffer(buffer) {} I420ABuffer::I420ABuffer( - rtc::scoped_refptr buffer) + webrtc::scoped_refptr buffer) : I420Buffer(buffer) {} unsigned int I420ABuffer::stride_a() const { @@ -208,16 +208,16 @@ webrtc::I420ABufferInterface* I420ABuffer::buffer() const { return static_cast(buffer_.get()); } -I422Buffer::I422Buffer(rtc::scoped_refptr buffer) +I422Buffer::I422Buffer(webrtc::scoped_refptr buffer) : PlanarYuv8Buffer(buffer) {} -I444Buffer::I444Buffer(rtc::scoped_refptr buffer) +I444Buffer::I444Buffer(webrtc::scoped_refptr buffer) : PlanarYuv8Buffer(buffer) {} -I010Buffer::I010Buffer(rtc::scoped_refptr buffer) +I010Buffer::I010Buffer(webrtc::scoped_refptr buffer) : PlanarYuv16BBuffer(buffer) {} -NV12Buffer::NV12Buffer(rtc::scoped_refptr buffer) +NV12Buffer::NV12Buffer(webrtc::scoped_refptr buffer) : BiplanarYuv8Buffer(buffer) {} std::unique_ptr copy_i420_buffer( @@ -257,7 +257,7 @@ std::unique_ptr new_i010_buffer(int width, int stride_y, int stride_u, int stride_v) { - return std::make_unique(rtc::make_ref_counted( + return std::make_unique(webrtc::make_ref_counted( width, height, stride_y, stride_u, stride_v)); } diff --git a/webrtc-sys/src/video_track.cpp b/webrtc-sys/src/video_track.cpp index 45e07dcae..cd83a1640 100644 --- a/webrtc-sys/src/video_track.cpp +++ b/webrtc-sys/src/video_track.cpp @@ -36,7 +36,7 @@ namespace livekit { VideoTrack::VideoTrack(std::shared_ptr rtc_runtime, - rtc::scoped_refptr track) + webrtc::scoped_refptr track) : MediaStreamTrack(rtc_runtime, std::move(track)) {} VideoTrack::~VideoTrack() { @@ -49,7 +49,7 @@ VideoTrack::~VideoTrack() { void VideoTrack::add_sink(const std::shared_ptr& sink) const { webrtc::MutexLock lock(&mutex_); track()->AddOrUpdateSink(sink.get(), - rtc::VideoSinkWants()); // TODO(theomonnom): Expose + webrtc::VideoSinkWants()); // TODO(theomonnom): Expose // VideoSinkWants to Rust? sinks_.push_back(sink); } @@ -106,7 +106,7 @@ std::shared_ptr new_native_video_sink( VideoTrackSource::InternalSource::InternalSource( const VideoResolution& resolution) - : rtc::AdaptedVideoTrackSource(4), resolution_(resolution) {} + : webrtc::AdaptedVideoTrackSource(4), resolution_(resolution) {} VideoTrackSource::InternalSource::~InternalSource() {} @@ -114,7 +114,7 @@ bool VideoTrackSource::InternalSource::is_screencast() const { return false; } -absl::optional VideoTrackSource::InternalSource::needs_denoising() const { +std::optional VideoTrackSource::InternalSource::needs_denoising() const { return false; } @@ -137,9 +137,9 @@ bool VideoTrackSource::InternalSource::on_captured_frame( webrtc::MutexLock lock(&mutex_); int64_t aligned_timestamp_us = timestamp_aligner_.TranslateTimestamp( - frame.timestamp_us(), rtc::TimeMicros()); + frame.timestamp_us(), webrtc::TimeMicros()); - rtc::scoped_refptr buffer = + webrtc::scoped_refptr buffer = frame.video_frame_buffer(); if (resolution_.height == 0 || resolution_.width == 0) { @@ -161,7 +161,7 @@ bool VideoTrackSource::InternalSource::on_captured_frame( webrtc::VideoRotation rotation = frame.rotation(); if (apply_rotation() && rotation != webrtc::kVideoRotation_0) { - // If the buffer is I420, rtc::AdaptedVideoTrackSource will handle the + // If the buffer is I420, webrtc::AdaptedVideoTrackSource will handle the // rotation for us. buffer = buffer->ToI420(); } @@ -176,7 +176,7 @@ bool VideoTrackSource::InternalSource::on_captured_frame( } VideoTrackSource::VideoTrackSource(const VideoResolution& resolution) { - source_ = rtc::make_ref_counted(resolution); + source_ = webrtc::make_ref_counted(resolution); } VideoResolution VideoTrackSource::video_resolution() const { @@ -189,7 +189,7 @@ bool VideoTrackSource::on_captured_frame( return source_->on_captured_frame(rtc_frame); } -rtc::scoped_refptr VideoTrackSource::get() +webrtc::scoped_refptr VideoTrackSource::get() const { return source_; } diff --git a/webrtc-sys/src/webrtc.cpp b/webrtc-sys/src/webrtc.cpp index 7305741c5..0b9491ff8 100644 --- a/webrtc-sys/src/webrtc.cpp +++ b/webrtc-sys/src/webrtc.cpp @@ -26,8 +26,8 @@ #include "livekit/rtp_receiver.h" #include "livekit/rtp_sender.h" #include "livekit/video_track.h" -#include "rtc_base/helpers.h" #include "rtc_base/logging.h" +#include "rtc_base/crypto_random.h" #include "rtc_base/synchronization/mutex.h" #ifdef WEBRTC_WIN @@ -48,7 +48,7 @@ RtcRuntime::RtcRuntime() { // Not the best way to do it... webrtc::MutexLock lock(&g_mutex); if (g_release_counter == 0) { - RTC_CHECK(rtc::InitializeSSL()) << "Failed to InitializeSSL()"; + RTC_CHECK(webrtc::InitializeSSL()) << "Failed to InitializeSSL()"; #ifdef WEBRTC_WIN WSADATA data; @@ -58,13 +58,13 @@ RtcRuntime::RtcRuntime() { g_release_counter++; } - network_thread_ = rtc::Thread::CreateWithSocketServer(); + network_thread_ = webrtc::Thread::CreateWithSocketServer(); network_thread_->SetName("network_thread", &network_thread_); network_thread_->Start(); - worker_thread_ = rtc::Thread::Create(); + worker_thread_ = webrtc::Thread::Create(); worker_thread_->SetName("worker_thread", &worker_thread_); worker_thread_->Start(); - signaling_thread_ = rtc::Thread::Create(); + signaling_thread_ = webrtc::Thread::Create(); signaling_thread_->SetName("signaling_thread", &signaling_thread_); signaling_thread_->Start(); } @@ -80,7 +80,7 @@ RtcRuntime::~RtcRuntime() { webrtc::MutexLock lock(&g_mutex); g_release_counter--; if (g_release_counter == 0) { - RTC_CHECK(rtc::CleanupSSL()) << "Failed to CleanupSSL()"; + RTC_CHECK(webrtc::CleanupSSL()) << "Failed to CleanupSSL()"; #ifdef WEBRTC_WIN WSACleanup(); @@ -89,20 +89,20 @@ RtcRuntime::~RtcRuntime() { } } -rtc::Thread* RtcRuntime::network_thread() const { +webrtc::Thread* RtcRuntime::network_thread() const { return network_thread_.get(); } -rtc::Thread* RtcRuntime::worker_thread() const { +webrtc::Thread* RtcRuntime::worker_thread() const { return worker_thread_.get(); } -rtc::Thread* RtcRuntime::signaling_thread() const { +webrtc::Thread* RtcRuntime::signaling_thread() const { return signaling_thread_.get(); } std::shared_ptr RtcRuntime::get_or_create_media_stream_track( - rtc::scoped_refptr rtc_track) { + webrtc::scoped_refptr rtc_track) { webrtc::MutexLock lock(&mutex_); for (std::weak_ptr weak_existing_track : media_stream_tracks_) { @@ -118,7 +118,7 @@ std::shared_ptr RtcRuntime::get_or_create_media_stream_track( std::shared_ptr video_track = std::shared_ptr(new VideoTrack( shared_from_this(), - rtc::scoped_refptr( + webrtc::scoped_refptr( static_cast(rtc_track.get())))); media_stream_tracks_.push_back( @@ -128,7 +128,7 @@ std::shared_ptr RtcRuntime::get_or_create_media_stream_track( std::shared_ptr audio_track = std::shared_ptr(new AudioTrack( shared_from_this(), - rtc::scoped_refptr( + webrtc::scoped_refptr( static_cast(rtc_track.get())))); media_stream_tracks_.push_back( @@ -138,13 +138,13 @@ std::shared_ptr RtcRuntime::get_or_create_media_stream_track( } std::shared_ptr RtcRuntime::get_or_create_audio_track( - rtc::scoped_refptr track) { + webrtc::scoped_refptr track) { return std::static_pointer_cast( get_or_create_media_stream_track(track)); } std::shared_ptr RtcRuntime::get_or_create_video_track( - rtc::scoped_refptr track) { + webrtc::scoped_refptr track) { return std::static_pointer_cast( get_or_create_media_stream_track(track)); } @@ -152,15 +152,15 @@ std::shared_ptr RtcRuntime::get_or_create_video_track( LogSink::LogSink( rust::Fn fnc) : fnc_(fnc) { - rtc::LogMessage::AddLogToStream(this, rtc::LoggingSeverity::LS_VERBOSE); + webrtc::LogMessage::AddLogToStream(this, rtc::LoggingSeverity::LS_VERBOSE); } LogSink::~LogSink() { - rtc::LogMessage::RemoveLogToStream(this); + webrtc::LogMessage::RemoveLogToStream(this); } void LogSink::OnLogMessage(const std::string& message, - rtc::LoggingSeverity severity) { + webrtc::LoggingSeverity severity) { fnc_(rust::String::lossy(message), static_cast(severity)); } @@ -170,7 +170,7 @@ std::unique_ptr new_log_sink( } rust::String create_random_uuid() { - return rtc::CreateRandomUuid(); + return webrtc::CreateRandomUuid(); } } // namespace livekit diff --git a/yuv-sys/yuv_functions.txt b/yuv-sys/yuv_functions.txt index cecd256ba..4a57a51f0 100644 --- a/yuv-sys/yuv_functions.txt +++ b/yuv-sys/yuv_functions.txt @@ -1173,7 +1173,7 @@ NV21ToRGB24Row_AVX2 I422ToRGB565Row_AVX2 I422ToARGB1555Row_AVX2 I422ToARGB4444Row_AVX2 -I422ToRGB24Row_AVX2 +I422ToRGB24Row_AVX2f I444ToRGB24Row_AVX2 NV12ToRGB565Row_AVX2 RGB24ToYJRow_AVX2 @@ -1191,3 +1191,317 @@ RGBAToARGBRow_C AR64ToAB64Row_C YUY2ToARGBMatrix UYVYToARGBMatrix +TransposeWx8_C +TransposeUVWx8_C +TransposeWxH_C +TransposeUVWxH_C +TransposeWx8_16_C +TransposeWxH_16_C +Transpose4x4_32_C +TransposeUVWx8_Any_NEON +I444ToARGBRow_NEON +I444ToRGB24Row_NEON +HammingDistance_NEON +I422ToARGBRow_NEON +MergeARGBRow_Any_NEON +I444AlphaToARGBRow_NEON +I444AlphaToARGBRow_Any_NEON +I422AlphaToARGBRow_NEON +SumSquareError_NEON +I422ToRGBARow_NEON +I422ToRGB24Row_NEON +I422ToRGB565Row_NEON +I422ToARGB1555Row_NEON +I422ToARGB4444Row_NEON +I400ToARGBRow_NEON +J400ToARGBRow_NEON +I422AlphaToARGBRow_Any_NEON +MergeAR64Row_Any_NEON +MergeARGB16To8Row_Any_NEON +MergeXRGBRow_Any_NEON +I422ToYUY2Row_Any_NEON +I422ToUYVYRow_Any_NEON +I444ToRGB24Row_Any_NEON +I444ToARGBRow_Any_NEON +I422ToARGBRow_Any_NEON +I422ToRGBARow_Any_NEON +I422ToRGB24Row_Any_NEON +I422ToARGB4444Row_Any_NEON +I422ToARGB1555Row_Any_NEON +I422ToRGB565Row_Any_NEON +MergeXR30Row_Any_NEON +MergeXR30Row_10_Any_NEON +MergeXR64Row_Any_NEON +MergeXRGB16To8Row_Any_NEON +MergeUVRow_Any_NEON +NV21ToYUV24Row_Any_NEON +ARGBMultiplyRow_Any_NEON +ARGBAddRow_Any_NEON +ARGBSubtractRow_Any_NEON +SobelRow_Any_NEON +SobelToPlaneRow_Any_NEON +SobelXYRow_Any_NEON +YUY2ToNVUVRow_Any_NEON +NV12ToARGBRow_Any_NEON +NV21ToARGBRow_Any_NEON +NV12ToRGB24Row_Any_NEON +NV21ToRGB24Row_Any_NEON +NV12ToRGB565Row_Any_NEON +MergeUVRow_16_Any_NEON +CopyRow_Any_NEON +ARGBToRGB24Row_Any_NEON +ARGBToRAWRow_Any_NEON +ARGBToRGB565Row_Any_NEON +ARGBToARGB1555Row_Any_NEON +ARGBToARGB4444Row_Any_NEON +J400ToARGBRow_Any_NEON +RAWToRGB24Row_Any_NEON +ARGBToYRow_Any_NEON +ARGBToYJRow_Any_NEON +ABGRToYJRow_Any_NEON +RGBAToYJRow_Any_NEON +BGRAToYRow_Any_NEON +ABGRToYRow_Any_NEON +RGBAToYRow_Any_NEON +RGB24ToYRow_Any_NEON +RGB24ToYJRow_Any_NEON +RAWToYRow_Any_NEON +RAWToYJRow_Any_NEON +RGB565ToYRow_Any_NEON +NV12ToARGBRow_NEON +ARGB1555ToYRow_Any_NEON +NV21ToARGBRow_NEON +NV12ToRGB24Row_NEON +ARGB4444ToYRow_Any_NEON +NV21ToRGB24Row_NEON +NV12ToRGB565Row_NEON +ScaleRowDown2_NEON +ScaleRowDown2Linear_NEON +ScaleRowDown2Box_NEON +ScaleRowDown4_NEON +YUY2ToYRow_Any_NEON +UYVYToYRow_Any_NEON +YUY2ToARGBRow_NEON +AYUVToYRow_Any_NEON +SwapUVRow_Any_NEON +RGB24ToARGBRow_Any_NEON +RAWToARGBRow_Any_NEON +RAWToRGBARow_Any_NEON +RGB565ToARGBRow_Any_NEON +ARGB1555ToARGBRow_Any_NEON +ARGB4444ToARGBRow_Any_NEON +ARGBAttenuateRow_Any_NEON +ARGBExtractAlphaRow_Any_NEON +I400ToARGBRow_Any_NEON +ARGBToRGB565DitherRow_Any_NEON +ARGBShuffleRow_Any_NEON +ARGBToAR64Row_Any_NEON +ARGBToAB64Row_Any_NEON +AR64ToARGBRow_Any_NEON +AB64ToARGBRow_Any_NEON +UYVYToARGBRow_NEON +SplitUVRow_NEON +DetileRow_NEON +DetileRow_16_NEON +DetileSplitUVRow_NEON +ScaleRowDown4Box_NEON +ScaleRowDown34_NEON +ScaleRowDown34_0_Box_NEON +ScaleRowDown34_1_Box_NEON +ScaleRowDown38_NEON +ScaleRowDown38_3_Box_NEON +ScaleRowDown38_2_Box_NEON +ScaleRowUp2_Linear_NEON +ScaleRowUp2_Bilinear_NEON +ScaleRowUp2_Linear_12_NEON +ScaleRowUp2_Bilinear_12_NEON +ScaleRowUp2_Linear_16_NEON +ScaleRowUp2_Bilinear_16_NEON +ScaleUVRowUp2_Linear_NEON +ScaleUVRowUp2_Bilinear_NEON +Convert16To8Row_Any_NEON +MultiplyRow_16_Any_NEON +DivideRow_16_Any_NEON +HalfFloatRow_Any_NEON +ByteToFloatRow_Any_NEON +YUY2ToARGBRow_Any_NEON +UYVYToARGBRow_Any_NEON +InterpolateRow_Any_NEON +ScaleUVRowUp2_Linear_16_NEON +DetileToYUY2_NEON +UnpackMT2T_NEON +ScaleUVRowUp2_Bilinear_16_NEON +MergeUVRow_NEON +MergeUVRow_16_NEON +ScaleAddRow_NEON +InterpolateRow_16_Any_NEON +InterpolateRow_16To8_Any_NEON +MirrorRow_Any_NEON +MirrorUVRow_Any_NEON +ARGBMirrorRow_Any_NEON +RGB24MirrorRow_Any_NEON +SetRow_Any_NEON +ARGBSetRow_Any_NEON +SplitUVRow_Any_NEON +ARGBToUV444Row_Any_NEON +YUY2ToUV422Row_Any_NEON +UYVYToUV422Row_Any_NEON +SplitUVRow_16_Any_NEON +SplitRGBRow_Any_NEON +SplitXRGBRow_Any_NEON +SplitARGBRow_Any_NEON +ARGBToUVRow_Any_NEON +ARGBToUVJRow_Any_NEON +ABGRToUVJRow_Any_NEON +BGRAToUVRow_Any_NEON +ABGRToUVRow_Any_NEON +SplitRGBRow_NEON +RGBAToUVRow_Any_NEON +MergeRGBRow_NEON +RGB24ToUVRow_Any_NEON +RGB24ToUVJRow_Any_NEON +RAWToUVRow_Any_NEON +RAWToUVJRow_Any_NEON +RGB565ToUVRow_Any_NEON +ARGB1555ToUVRow_Any_NEON +ARGB4444ToUVRow_Any_NEON +YUY2ToUVRow_Any_NEON +UYVYToUVRow_Any_NEON +AYUVToUVRow_Any_NEON +AYUVToVURow_Any_NEON +DetileRow_Any_NEON +DetileRow_16_Any_NEON +DetileSplitUVRow_Any_NEON +DetileToYUY2_Any_NEON +SplitARGBRow_NEON +MergeARGBRow_NEON +SplitXRGBRow_NEON +MergeXRGBRow_NEON +MergeXR30Row_NEON +MergeXR30Row_10_NEON +MergeAR64Row_NEON +ScaleFilterCols_NEON +ScaleARGBRowDown2_NEON +ScaleARGBRowDown2Linear_NEON +ScaleARGBRowDown2Box_NEON +ScaleARGBRowDownEven_NEON +ScaleARGBRowDownEvenBox_NEON +ScaleARGBCols_NEON +ScaleARGBFilterCols_NEON +ScaleRowDown2Box_16_NEON +ScaleUVRowDown2_NEON +ScaleUVRowDown2Linear_NEON +ScaleUVRowDown2Box_NEON +ScaleUVRowDownEven_NEON +MergeXR64Row_NEON +MergeARGB16To8Row_NEON +TransposeUVWx8_NEON +MergeXRGB16To8Row_NEON +CopyRow_NEON +SetRow_NEON +ARGBSetRow_NEON +MirrorRow_NEON +MirrorUVRow_NEON +MirrorSplitUVRow_NEON +ARGBMirrorRow_NEON +RGB24MirrorRow_NEON +RGB24ToARGBRow_NEON +RAWToARGBRow_NEON +RAWToRGBARow_NEON +RAWToRGB24Row_NEON +RGB565ToARGBRow_NEON +ARGB1555ToARGBRow_NEON +ARGB4444ToARGBRow_NEON +ARGBToRGB24Row_NEON +ARGBToRAWRow_NEON +YUY2ToYRow_NEON +UYVYToYRow_NEON +YUY2ToUV422Row_NEON +UYVYToUV422Row_NEON +YUY2ToUVRow_NEON +UYVYToUVRow_NEON +YUY2ToNVUVRow_NEON +ARGBShuffleRow_NEON +I422ToYUY2Row_NEON +I422ToUYVYRow_NEON +ARGBToRGB565Row_NEON +ARGBToRGB565DitherRow_NEON +ARGBToARGB1555Row_NEON +ARGBToARGB4444Row_NEON +ARGBToAR64Row_NEON +ARGBToAB64Row_NEON +AR64ToARGBRow_NEON +AB64ToARGBRow_NEON +ARGBExtractAlphaRow_NEON +ARGBToUV444Row_NEON +ARGBToUVJ444Row_NEON +ARGBToUVRow_NEON +ARGBToUVJRow_NEON +ABGRToUVJRow_NEON +RGB24ToUVJRow_NEON +Transpose4x4_32_NEON +RAWToUVJRow_NEON +BGRAToUVRow_NEON +ABGRToUVRow_NEON +RGBAToUVRow_NEON +RGB24ToUVRow_NEON +RAWToUVRow_NEON +RGB565ToUVRow_NEON +ARGB1555ToUVRow_NEON +ARGB4444ToUVRow_NEON +RGB565ToYRow_NEON +ARGB1555ToYRow_NEON +ARGB4444ToYRow_NEON +ARGBToYRow_NEON +ARGBToYJRow_NEON +ABGRToYRow_NEON +ABGRToYJRow_NEON +RGBAToYRow_NEON +RGBAToYJRow_NEON +BGRAToYRow_NEON +RGB24ToYJRow_NEON +RAWToYJRow_NEON +RGB24ToYRow_NEON +RAWToYRow_NEON +InterpolateRow_NEON +InterpolateRow_16_NEON +InterpolateRow_16To8_NEON +ARGBBlendRow_NEON +ARGBAttenuateRow_NEON +ARGBQuantizeRow_NEON +ARGBShadeRow_NEON +ARGBGrayRow_NEON +ARGBSepiaRow_NEON +ARGBColorMatrixRow_NEON +ARGBMultiplyRow_NEON +ARGBAddRow_NEON +ARGBSubtractRow_NEON +SobelRow_NEON +SobelToPlaneRow_NEON +SobelXYRow_NEON +SobelXRow_NEON +SobelYRow_NEON +HalfFloatRow_NEON +ByteToFloatRow_NEON +ConvertFP16ToFP32Row_NEON +ConvertFP16ToFP32Column_NEON +ConvertFP32ToFP16Row_NEON +ScaleMaxSamples_NEON +ScaleSumSamples_NEON +ScaleSamples_NEON +GaussCol_NEON +GaussRow_NEON +GaussCol_F32_NEON +GaussRow_F32_NEON +NV21ToYUV24Row_NEON +AYUVToUVRow_NEON +AYUVToVURow_NEON +AYUVToYRow_NEON +SwapUVRow_NEON +HalfMergeUVRow_NEON +SplitUVRow_16_NEON +MultiplyRow_16_NEON +DivideRow_16_NEON +Convert16To8Row_NEON +TransposeWx8_Any_NEON +TransposeWx8_NEON \ No newline at end of file From 8bcbf55995f62286124b7b5b5915cbe505d56d81 Mon Sep 17 00:00:00 2001 From: Hamdan <96612374+s-hamdananwar@users.noreply.github.com> Date: Thu, 18 Sep 2025 16:13:05 -0500 Subject: [PATCH 245/274] fix: apply original participant fields in data messages (#709) * fix: apply original participant fields in data messages * code formatting --- livekit/src/rtc_engine/rtc_session.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/livekit/src/rtc_engine/rtc_session.rs b/livekit/src/rtc_engine/rtc_session.rs index a95c3aa27..0bd6f10a7 100644 --- a/livekit/src/rtc_engine/rtc_session.rs +++ b/livekit/src/rtc_engine/rtc_session.rs @@ -1042,15 +1042,10 @@ impl SessionInner { // Participant SID and identity used to be defined on user packet, but // they have been moved to the packet root. For backwards compatibility, // we take the user packet's values if the top-level fields are not set. - let participant_sid = participant_sid - .is_none() - .then_some(user.participant_sid) - .and_then(|sid| sid.try_into().ok()); - let participant_identity = participant_identity - .is_none() - .then_some(user.participant_identity) - .and_then(|identity| identity.try_into().ok()); - + let participant_sid = + participant_sid.or_else(|| user.participant_sid.try_into().ok()); + let participant_identity = + participant_identity.or_else(|| user.participant_identity.try_into().ok()); self.emitter.send(SessionEvent::Data { kind, participant_sid, From 56178e3c5e448dd3b1b4aad68ed4fe20c7179f02 Mon Sep 17 00:00:00 2001 From: Ben Cherry Date: Thu, 18 Sep 2025 21:16:50 -0400 Subject: [PATCH 246/274] Add send_bytes method (#691) * Add send_bytes * Document integration testing * Add data stream tests * Reorganize E2E tests * Define FFI messages for send bytes * Implement FFI bindings * Create example for send bytes * Warn if total_length option specified --------- Co-authored-by: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> --- examples/send_bytes/Cargo.lock | 3082 +++++++++++++++++ examples/send_bytes/Cargo.toml | 15 + examples/send_bytes/README.md | 43 + examples/send_bytes/src/main.rs | 76 + examples/send_bytes/src/packet.rs | 56 + livekit-ffi/Cargo.toml | 1 + livekit-ffi/README.md | 9 + livekit-ffi/protocol/data_stream.proto | 22 + livekit-ffi/protocol/ffi.proto | 9 +- livekit-ffi/src/livekit.proto.rs | 51 +- livekit-ffi/src/server/participant.rs | 20 + livekit-ffi/src/server/requests.rs | 12 + livekit/src/room/data_stream/outgoing.rs | 49 + .../src/room/participant/local_participant.rs | 17 + livekit/tests/README.md | 10 + livekit/tests/common/e2e.rs | 56 + livekit/tests/common/mod.rs | 60 +- livekit/tests/data_channel_test.rs | 22 +- livekit/tests/data_stream_test.rs | 105 + 19 files changed, 3641 insertions(+), 74 deletions(-) create mode 100644 examples/send_bytes/Cargo.lock create mode 100644 examples/send_bytes/Cargo.toml create mode 100644 examples/send_bytes/README.md create mode 100644 examples/send_bytes/src/main.rs create mode 100644 examples/send_bytes/src/packet.rs create mode 100644 livekit-ffi/README.md create mode 100644 livekit/tests/README.md create mode 100644 livekit/tests/common/e2e.rs create mode 100644 livekit/tests/data_stream_test.rs diff --git a/examples/send_bytes/Cargo.lock b/examples/send_bytes/Cargo.lock new file mode 100644 index 000000000..a957f3e22 --- /dev/null +++ b/examples/send_bytes/Cargo.lock @@ -0,0 +1,3082 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "anyhow" +version = "1.0.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" + +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb812ffb58524bdd10860d7d974e2f01cc0950c2438a74ee5ec2e2280c6c4ffa" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +dependencies = [ + "async-channel 2.5.0", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite", + "once_cell", +] + +[[package]] +name = "async-io" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19634d6336019ef220f09fd31168ce5c184b295cbf80345437cc36094ef223ca" +dependencies = [ + "async-lock", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix 1.0.8", + "slab", + "windows-sys 0.60.2", +] + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener 5.4.0", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-native-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9343dc5acf07e79ff82d0c37899f079db3534d99f189a1837c8e549c99405bec" +dependencies = [ + "futures-util", + "native-tls", + "thiserror", + "url", +] + +[[package]] +name = "async-std" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "730294c1c08c2e0f85759590518f6333f0d5a0a766a27d519c1b244c3dfd8a24" +dependencies = [ + "async-channel 1.9.0", + "async-global-executor", + "async-io", + "async-lock", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-tungstenite" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cca750b12e02c389c1694d35c16539f88b8bbaa5945934fdc1b41a776688589" +dependencies = [ + "async-native-tls", + "async-std", + "futures-io", + "futures-util", + "log", + "pin-project-lite", + "tungstenite 0.21.0", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bitfield-struct" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3ca019570363e800b05ad4fd890734f28ac7b72f563ad8a35079efb793616f8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel 2.5.0", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "bmrng" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54df9073108f1558f90ae6c5bf5ab9c917c4185f5527b280c87a993cbead0ac" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "bumpalo" +version = "3.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c764d619ca78fccbf3069b37bd7af92577f044bb15236036662d79b6559f25b7" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "bzip2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "jobserver", + "libc", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets 0.52.6", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "colored" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "combine" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cxx" +version = "1.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c15f3b597018782655a05d417f28bac009f6eb60f4b6703eb818998c1aaa16a" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81699747d109bba60bd6f87e7cb24b626824b8427b32f199b95c7faa06ee3dc9" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a7eb4c4fd18505f5a935f9c2ee77780350dcdb56da7cd037634e806141c5c43" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d914fcc6452d133236ee067a9538be25ba6a644a450e1a6c617da84bf029854" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "data-encoding" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" + +[[package]] +name = "deranged" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d630bccd429a5bb5a64b5e94f693bfc48c9f8566418fda4c494cc94f911f87cc" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "either" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" + +[[package]] +name = "encoding_rs" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "env_filter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener 5.4.0", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "flate2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-lite" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets 0.52.6", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "h2" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.11", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "http" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.11", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http 0.2.11", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.5", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.11", + "hyper", + "rustls", + "tokio", + "tokio-rustls", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "io-uring" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" +dependencies = [ + "bitflags 2.9.4", + "cfg-if", + "libc", +] + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "jiff" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d699bc6dfc879fb1bf9bdff0d4c56f0884fc6f0d0eb0fba397a6d00cd9a6b85e" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d16e75759ee0aa64c57a56acbf43916987b20c77373cb7e808979e02b93c9f9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "jsonwebtoken" +version = "9.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ae10193d25051e74945f1ea2d0b42e03cc3b890f7e4cc5faa44997d808193f" +dependencies = [ + "base64", + "js-sys", + "ring", + "serde", + "serde_json", +] + +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.175" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" + +[[package]] +name = "libloading" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +dependencies = [ + "cfg-if", + "windows-targets 0.52.6", +] + +[[package]] +name = "libwebrtc" +version = "0.3.14" +dependencies = [ + "cxx", + "jni", + "js-sys", + "lazy_static", + "livekit-protocol", + "livekit-runtime", + "log", + "parking_lot", + "serde", + "serde_json", + "thiserror", + "tokio", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webrtc-sys", +] + +[[package]] +name = "link-cplusplus" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d240c6f7e1ba3a28b0249f774e6a9dd0175054b52dfbb61b16eb8505c3785c9" +dependencies = [ + "cc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "livekit" +version = "0.7.18" +dependencies = [ + "bmrng", + "bytes", + "chrono", + "futures-util", + "lazy_static", + "libloading", + "libwebrtc", + "livekit-api", + "livekit-protocol", + "livekit-runtime", + "log", + "parking_lot", + "prost", + "semver", + "serde", + "serde_json", + "thiserror", + "tokio", +] + +[[package]] +name = "livekit-api" +version = "0.4.6" +dependencies = [ + "async-tungstenite", + "base64", + "futures-util", + "http 0.2.11", + "jsonwebtoken", + "livekit-protocol", + "livekit-runtime", + "log", + "parking_lot", + "pbjson-types", + "prost", + "rand 0.9.2", + "reqwest", + "scopeguard", + "serde", + "serde_json", + "sha2", + "thiserror", + "tokio", + "tokio-tungstenite", + "url", +] + +[[package]] +name = "livekit-protocol" +version = "0.4.0" +dependencies = [ + "futures-util", + "livekit-runtime", + "parking_lot", + "pbjson", + "pbjson-types", + "prost", + "prost-types", + "serde", + "thiserror", + "tokio", +] + +[[package]] +name = "livekit-runtime" +version = "0.4.0" +dependencies = [ + "tokio", + "tokio-stream", +] + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +dependencies = [ + "value-bag", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.59.0", +] + +[[package]] +name = "multimap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" + +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "openssl" +version = "0.10.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" +dependencies = [ + "bitflags 2.9.4", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.5", +] + +[[package]] +name = "password-hash" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "pbjson" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1030c719b0ec2a2d25a5df729d6cff1acf3cc230bf766f4f97833591f7577b90" +dependencies = [ + "base64", + "serde", +] + +[[package]] +name = "pbjson-build" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2580e33f2292d34be285c5bc3dba5259542b083cfad6037b6d70345f24dcb735" +dependencies = [ + "heck", + "itertools", + "prost", + "prost-types", +] + +[[package]] +name = "pbjson-types" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18f596653ba4ac51bdecbb4ef6773bc7f56042dc13927910de1684ad3d32aa12" +dependencies = [ + "bytes", + "chrono", + "pbjson", + "pbjson-build", + "prost", + "prost-build", + "serde", +] + +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest", + "hmac", + "password-hash", + "sha2", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "petgraph" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "polling" +version = "3.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee9b2fa7a4517d2c91ff5bc6c297a427a96749d15f98fcdbb22c05571a4d4b7" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix 1.0.8", + "windows-sys 0.60.2", +] + +[[package]] +name = "portable-atomic" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "prettyplease" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c55e02e35260070b6f716a2423c2ff1c3bb1642ddca6f99e1f26d06268a0e2d2" +dependencies = [ + "bytes", + "heck", + "itertools", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn", + "tempfile", + "which", +] + +[[package]] +name = "prost-derive" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "193898f59edcf43c26227dcd4c8427f00d99d61e95dcde58dabd49fa291d470e" +dependencies = [ + "prost", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.12", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.1", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "regex" +version = "1.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "reqwest" +version = "0.11.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http 0.2.11", + "http-body", + "hyper", + "hyper-rustls", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls", + "rustls-native-certs", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tokio-rustls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.12", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.9.4", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +dependencies = [ + "bitflags 2.9.4", + "errno", + "libc", + "linux-raw-sys 0.9.4", + "windows-sys 0.60.2", +] + +[[package]] +name = "rustls" +version = "0.21.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "ryu" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "scratch" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152" + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" + +[[package]] +name = "send_bytes" +version = "0.1.0" +dependencies = [ + "bitfield-struct", + "colored", + "env_logger", + "livekit", + "log", + "rand 0.9.2", + "tokio", +] + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.143" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" + +[[package]] +name = "socket2" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "2.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67" +dependencies = [ + "cfg-if", + "fastrand", + "rustix 0.38.44", + "windows-sys 0.52.0", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83bde6f1ec10e72d583d91623c939f623002284ef622b87de38cfd546cbf2031" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.47.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +dependencies = [ + "backtrace", + "bytes", + "io-uring", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "slab", + "socket2 0.6.0", + "tokio-macros", + "windows-sys 0.59.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" +dependencies = [ + "futures-util", + "log", + "native-tls", + "tokio", + "tokio-native-tls", + "tungstenite 0.20.1", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tungstenite" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 0.2.11", + "httparse", + "log", + "native-tls", + "rand 0.8.5", + "sha1", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.3.1", + "httparse", + "log", + "native-tls", + "rand 0.8.5", + "sha1", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "value-bag" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "walkdir" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" + +[[package]] +name = "web-sys" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webrtc-sys" +version = "0.3.11" +dependencies = [ + "cc", + "cxx", + "cxx-build", + "glob", + "log", + "webrtc-sys-build", +] + +[[package]] +name = "webrtc-sys-build" +version = "0.3.7" +dependencies = [ + "anyhow", + "fs2", + "regex", + "reqwest", + "scratch", + "semver", + "zip", +] + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix 0.38.44", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags 2.9.4", +] + +[[package]] +name = "zip" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +dependencies = [ + "aes", + "byteorder", + "bzip2", + "constant_time_eq", + "crc32fast", + "crossbeam-utils", + "flate2", + "hmac", + "pbkdf2", + "sha1", + "time", + "zstd", +] + +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.9+zstd.1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/examples/send_bytes/Cargo.toml b/examples/send_bytes/Cargo.toml new file mode 100644 index 000000000..f80f11521 --- /dev/null +++ b/examples/send_bytes/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "send_bytes" +version = "0.1.0" +edition = "2024" + +[dependencies] +tokio = { version = "1.47.1", features = ["full"] } +livekit = { path = "../../livekit", features = ["native-tls"] } +log = "0.4.28" +env_logger = "0.11.8" +bitfield-struct = "0.11.0" +rand = "0.9.2" +colored = "3.0.0" + +[workspace] \ No newline at end of file diff --git a/examples/send_bytes/README.md b/examples/send_bytes/README.md new file mode 100644 index 000000000..11cfaca5a --- /dev/null +++ b/examples/send_bytes/README.md @@ -0,0 +1,43 @@ +# Send Bytes + +Example demonstrating the `send_bytes` method for sending a custom packet to participants in a room. + +## Usage + +1. Run the example in sender mode: + +```sh +export LIVEKIT_URL="..." +export LIVEKIT_TOKEN="" +cargo run -- sender +``` + +2. In a second terminal, run the example in receiver mode: + +```sh +export LIVEKIT_URL="..." +export LIVEKIT_TOKEN="" +cargo run +``` + +## Custom Packet + +This example uses the following hypothetical 4-byte packet structure to teleoperate 16 discrete LED indicators by setting their power states and RGB values: + +```mermaid +--- +title: "LED Control Packet" +config: + packet: + bitsPerRow: 8 +--- +packet ++2: "Version" ++5: "Channel" ++1: "On" ++8: "Red" ++8: "Green" ++8: "Blue" +``` + +The [_bitfield-struct_](https://crates.io/crates/bitfield-struct) crate is used to create a type-safe wrapper for getting and setting the bitfields by name. diff --git a/examples/send_bytes/src/main.rs b/examples/send_bytes/src/main.rs new file mode 100644 index 000000000..dd7448d22 --- /dev/null +++ b/examples/send_bytes/src/main.rs @@ -0,0 +1,76 @@ +use livekit::{Room, RoomEvent, RoomOptions, StreamByteOptions, StreamReader}; +use packet::LedControlPacket; +use rand::Rng; +use std::{env, error::Error, time::Duration}; +use tokio::{sync::mpsc::UnboundedReceiver, time::sleep}; + +mod packet; + +const LED_CONTROL_TOPIC: &str = "led-control"; + +#[tokio::main] +async fn main() -> Result<(), Box> { + env_logger::init(); + + let url = env::var("LIVEKIT_URL").expect("LIVEKIT_URL is not set"); + let token = env::var("LIVEKIT_TOKEN").expect("LIVEKIT_TOKEN is not set"); + + let is_sender = env::args().nth(1).map_or(false, |arg| arg == "sender"); + + let (room, rx) = Room::connect(&url, &token, RoomOptions::default()).await?; + println!("Connected to room: {} - {}", room.name(), room.sid().await); + + if is_sender { run_sender(room).await } else { run_receiver(room, rx).await } +} + +async fn run_sender(room: Room) -> Result<(), Box> { + println!("Running as sender"); + let mut rng = rand::rng(); + + loop { + // Send control packets with randomized channel and color. + let packet = packet::LedControlPacket::new() + .with_version(1) + .with_channel(rng.random_range(0..16)) + .with_is_on(true) + .with_red(rng.random()) + .with_green(rng.random()) + .with_blue(rng.random()); + + println!("[tx] {}", packet); + + let options = StreamByteOptions { topic: LED_CONTROL_TOPIC.into(), ..Default::default() }; + let be_bytes = packet.into_bits().to_be_bytes(); + room.local_participant().send_bytes(&be_bytes, options).await?; + + sleep(Duration::from_millis(500)).await; + } +} + +async fn run_receiver( + _room: Room, + mut rx: UnboundedReceiver, +) -> Result<(), Box> { + println!("Running as receiver"); + println!("Waiting for LED control packets…"); + while let Some(event) = rx.recv().await { + match event { + RoomEvent::ByteStreamOpened { reader, topic, participant_identity: _ } => { + if topic != LED_CONTROL_TOPIC { + continue; + }; + let Some(reader) = reader.take() else { continue }; + + let Ok(be_bytes) = reader.read_all().await?[..4].try_into() else { + log::warn!("Unexpected packet length"); + continue; + }; + let packet = LedControlPacket::from(u32::from_be_bytes(be_bytes)); + + println!("[rx] {}", packet); + } + _ => {} + } + } + Ok(()) +} diff --git a/examples/send_bytes/src/packet.rs b/examples/send_bytes/src/packet.rs new file mode 100644 index 000000000..efe727c99 --- /dev/null +++ b/examples/send_bytes/src/packet.rs @@ -0,0 +1,56 @@ +use bitfield_struct::bitfield; +use colored::Colorize; +use std::fmt::Display; + +/// Custom 4-byte packet structure used for controlling LED +/// state through a LiveKit room. +#[bitfield(u32)] +pub struct LedControlPacket { + /// Packet version (0-4). + #[bits(2)] + pub version: u8, + /// Which LED is being controlled (0-15). + #[bits(5)] + pub channel: u8, + /// Whether or not the channel is on. + #[bits(1)] + pub is_on: bool, + /// Red intensity (0-255). + #[bits(8)] + pub red: u8, + /// Green intensity (0-255). + #[bits(8)] + pub green: u8, + /// Blue intensity (0-255). + #[bits(8)] + pub blue: u8, +} + +impl Display for LedControlPacket { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let color_display = if colored::control::SHOULD_COLORIZE.should_colorize() { + " ".on_truecolor(self.red(), self.green(), self.blue()) + } else { + // Display RGB value if terminal color is disabled. + format!("rgb({:>3}, {:>3}, {:>3})", self.red(), self.green(), self.blue()).into() + }; + write!(f, "Channel {:02} => {}", self.channel(), color_display) + } +} + +#[cfg(test)] +mod tests { + use super::LedControlPacket; + + #[test] + fn test_bit_representation() { + let packet = LedControlPacket::new() + .with_version(1) + .with_channel(4) + .with_is_on(true) + .with_red(31) + .with_green(213) + .with_blue(249); + assert_eq!(packet.into_bits(), 0xF9D51F91); + } +} diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index 5b6857f3a..dc56ae659 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" repository = "https://github.com/livekit/rust-sdks" +readme = "README.md" [features] default = ["rustls-tls-native-roots"] diff --git a/livekit-ffi/README.md b/livekit-ffi/README.md new file mode 100644 index 000000000..fd4d98b58 --- /dev/null +++ b/livekit-ffi/README.md @@ -0,0 +1,9 @@ +# LiveKit FFI + +Foreign function interface (FFI) bindings for LiveKit, used to support the following client SDKs: + +- [Python](https://github.com/livekit/python-sdks) +- [NodeJS](https://github.com/livekit/node-sdks) +- [Unity](https://github.com/livekit/client-sdk-unity) + +This crate is compiled as dynamic library, allowing client languages to invoke APIs from the core [_livekit_](https://crates.io/crates/livekit) crate. diff --git a/livekit-ffi/protocol/data_stream.proto b/livekit-ffi/protocol/data_stream.proto index 0b137c447..bc1076ec4 100644 --- a/livekit-ffi/protocol/data_stream.proto +++ b/livekit-ffi/protocol/data_stream.proto @@ -156,6 +156,28 @@ message StreamSendFileCallback { } } +// MARK: - Send bytes + +// Sends bytes over a data stream. +message StreamSendBytesRequest { + required uint64 local_participant_handle = 1; + + required StreamByteOptions options = 2; + + // Bytes to send. + required bytes bytes = 3; +} +message StreamSendBytesResponse { + required uint64 async_id = 1; +} +message StreamSendBytesCallback { + required uint64 async_id = 1; + oneof result { + ByteStreamInfo info = 2; + StreamError error = 3; + } +} + // MARK: - Send text // Sends text over a data stream. diff --git a/livekit-ffi/protocol/ffi.proto b/livekit-ffi/protocol/ffi.proto index 7e58123d4..f1bc8873a 100644 --- a/livekit-ffi/protocol/ffi.proto +++ b/livekit-ffi/protocol/ffi.proto @@ -148,7 +148,9 @@ message FfiRequest { TextStreamWriterWriteRequest text_stream_write = 65; TextStreamWriterCloseRequest text_stream_close = 66; - // NEXT_ID: 67 + StreamSendBytesRequest send_bytes = 67; + + // NEXT_ID: 68 } } @@ -243,7 +245,9 @@ message FfiResponse { TextStreamWriterWriteResponse text_stream_write = 64; TextStreamWriterCloseResponse text_stream_close = 65; - // NEXT_ID: 66 + StreamSendBytesResponse send_bytes = 66; + + // NEXT_ID: 67 } } @@ -298,6 +302,7 @@ message FfiEvent { TextStreamWriterWriteCallback text_stream_writer_write = 38; TextStreamWriterCloseCallback text_stream_writer_close = 39; StreamSendTextCallback send_text = 40; + StreamSendBytesCallback send_bytes = 41; } } diff --git a/livekit-ffi/src/livekit.proto.rs b/livekit-ffi/src/livekit.proto.rs index 48bf8dda6..f898ac284 100644 --- a/livekit-ffi/src/livekit.proto.rs +++ b/livekit-ffi/src/livekit.proto.rs @@ -2447,6 +2447,45 @@ pub mod stream_send_file_callback { Error(super::StreamError), } } +// MARK: - Send bytes + +/// Sends bytes over a data stream. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct StreamSendBytesRequest { + #[prost(uint64, required, tag="1")] + pub local_participant_handle: u64, + #[prost(message, required, tag="2")] + pub options: StreamByteOptions, + /// Bytes to send. + #[prost(bytes="vec", required, tag="3")] + pub bytes: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct StreamSendBytesResponse { + #[prost(uint64, required, tag="1")] + pub async_id: u64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct StreamSendBytesCallback { + #[prost(uint64, required, tag="1")] + pub async_id: u64, + #[prost(oneof="stream_send_bytes_callback::Result", tags="2, 3")] + pub result: ::core::option::Option, +} +/// Nested message and enum types in `StreamSendBytesCallback`. +pub mod stream_send_bytes_callback { + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Result { + #[prost(message, tag="2")] + Info(super::ByteStreamInfo), + #[prost(message, tag="3")] + Error(super::StreamError), + } +} // MARK: - Send text /// Sends text over a data stream. @@ -4889,7 +4928,7 @@ pub struct RpcMethodInvocationEvent { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiRequest { - #[prost(oneof="ffi_request::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 48, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66")] + #[prost(oneof="ffi_request::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 48, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67")] pub message: ::core::option::Option, } /// Nested message and enum types in `FfiRequest`. @@ -5037,13 +5076,15 @@ pub mod ffi_request { TextStreamWrite(super::TextStreamWriterWriteRequest), #[prost(message, tag="66")] TextStreamClose(super::TextStreamWriterCloseRequest), + #[prost(message, tag="67")] + SendBytes(super::StreamSendBytesRequest), } } /// This is the output of livekit_ffi_request function. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiResponse { - #[prost(oneof="ffi_response::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 47, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65")] + #[prost(oneof="ffi_response::Message", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 47, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66")] pub message: ::core::option::Option, } /// Nested message and enum types in `FfiResponse`. @@ -5189,6 +5230,8 @@ pub mod ffi_response { TextStreamWrite(super::TextStreamWriterWriteResponse), #[prost(message, tag="65")] TextStreamClose(super::TextStreamWriterCloseResponse), + #[prost(message, tag="66")] + SendBytes(super::StreamSendBytesResponse), } } /// To minimize complexity, participant events are not included in the protocol. @@ -5197,7 +5240,7 @@ pub mod ffi_response { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FfiEvent { - #[prost(oneof="ffi_event::Message", tags="1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40")] + #[prost(oneof="ffi_event::Message", tags="1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41")] pub message: ::core::option::Option, } /// Nested message and enum types in `FfiEvent`. @@ -5285,6 +5328,8 @@ pub mod ffi_event { TextStreamWriterClose(super::TextStreamWriterCloseCallback), #[prost(message, tag="40")] SendText(super::StreamSendTextCallback), + #[prost(message, tag="41")] + SendBytes(super::StreamSendBytesCallback), } } /// Stop all rooms synchronously (Do we need async here?). diff --git a/livekit-ffi/src/server/participant.rs b/livekit-ffi/src/server/participant.rs index 5b04954b1..adadae88d 100644 --- a/livekit-ffi/src/server/participant.rs +++ b/livekit-ffi/src/server/participant.rs @@ -177,6 +177,26 @@ impl FfiParticipant { Ok(proto::StreamSendTextResponse { async_id }) } + pub fn send_bytes( + &self, + server: &'static FfiServer, + request: proto::StreamSendBytesRequest, + ) -> FfiResult { + let async_id = server.next_id(); + let local = self.guard_local_participant()?; + + let handle = server.async_runtime.spawn(async move { + let result = match local.send_bytes(&request.bytes, request.options.into()).await { + Ok(info) => proto::stream_send_bytes_callback::Result::Info(info.into()), + Err(err) => proto::stream_send_bytes_callback::Result::Error(err.into()), + }; + let callback = proto::StreamSendBytesCallback { async_id, result: Some(result) }; + let _ = server.send_event(proto::ffi_event::Message::SendBytes(callback)); + }); + server.watch_panic(handle); + Ok(proto::StreamSendBytesResponse { async_id }) + } + pub fn stream_bytes( &self, server: &'static FfiServer, diff --git a/livekit-ffi/src/server/requests.rs b/livekit-ffi/src/server/requests.rs index c45dba855..21d868b7a 100644 --- a/livekit-ffi/src/server/requests.rs +++ b/livekit-ffi/src/server/requests.rs @@ -1100,6 +1100,15 @@ fn on_send_file( ffi_participant.send_file(server, request) } +fn on_send_bytes( + server: &'static FfiServer, + request: proto::StreamSendBytesRequest, +) -> FfiResult { + let ffi_participant = + server.retrieve_handle::(request.local_participant_handle)?.clone(); + ffi_participant.send_bytes(server, request) +} + fn on_send_text( server: &'static FfiServer, request: proto::StreamSendTextRequest, @@ -1383,6 +1392,9 @@ pub fn handle_request( proto::ffi_request::Message::SendFile(request) => { proto::ffi_response::Message::SendFile(on_send_file(server, request)?) } + proto::ffi_request::Message::SendBytes(request) => { + proto::ffi_response::Message::SendBytes(on_send_bytes(server, request)?) + } proto::ffi_request::Message::SendText(request) => { proto::ffi_response::Message::SendText(on_send_text(server, request)?) } diff --git a/livekit/src/room/data_stream/outgoing.rs b/livekit/src/room/data_stream/outgoing.rs index 68f18dcf2..5a96888d2 100644 --- a/livekit/src/room/data_stream/outgoing.rs +++ b/livekit/src/room/data_stream/outgoing.rs @@ -386,6 +386,55 @@ impl OutgoingStreamManager { Ok(info) } + /// Send bytes to participants in the room. + /// + /// This method sends an in-memory blob of bytes to participants in the room + /// as a byte stream. It opens a stream using the provided options, writes the + /// entire buffer, and closes the stream before returning. + /// + /// The `total_length` in the header is set from the provided data and is not + /// overridable by `options.total_length`. + pub async fn send_bytes( + &self, + data: impl AsRef<[u8]>, + options: StreamByteOptions, + ) -> StreamResult { + if options.total_length.is_some() { + log::warn!("Ignoring total_length option specified for send_bytes"); + } + let bytes = data.as_ref(); + + let byte_header = proto::data_stream::ByteHeader { name: options.name.unwrap_or_default() }; + let header = proto::data_stream::Header { + stream_id: options.id.unwrap_or_else(|| create_random_uuid()), + timestamp: Utc::now().timestamp_millis(), + topic: options.topic, + mime_type: options.mime_type.unwrap_or_else(|| BYTE_MIME_TYPE.to_owned()), + total_length: Some(bytes.len() as u64), // not overridable + encryption_type: proto::encryption::Type::None.into(), + attributes: options.attributes, + content_header: Some(proto::data_stream::header::ContentHeader::ByteHeader( + byte_header.clone(), + )), + }; + + let open_options = RawStreamOpenOptions { + header: header.clone(), + destination_identities: options.destination_identities, + packet_tx: self.packet_tx.clone(), + }; + let writer = ByteStreamWriter { + info: Arc::new(ByteStreamInfo::from_headers(header, byte_header)), + stream: Arc::new(Mutex::new(RawStream::open(open_options).await?)), + }; + + let info = (*writer.info).clone(); + writer.write(bytes).await?; + writer.close().await?; + + Ok(info) + } + pub async fn send_file( &self, path: impl AsRef, diff --git a/livekit/src/room/participant/local_participant.rs b/livekit/src/room/participant/local_participant.rs index 4ed2998b6..982463e1d 100644 --- a/livekit/src/room/participant/local_participant.rs +++ b/livekit/src/room/participant/local_participant.rs @@ -951,6 +951,23 @@ impl LocalParticipant { self.session().unwrap().outgoing_stream_manager.send_file(path, options).await } + /// Send an in-memory blob of bytes to participants in the room. + /// + /// This method sends a provided byte slice as a byte stream. + /// + /// # Arguments + /// + /// * `data` - The bytes to send. + /// * `options` - Configuration options for the byte stream, including topic and + /// destination participants. + pub async fn send_bytes( + &self, + data: impl AsRef<[u8]>, + options: StreamByteOptions, + ) -> StreamResult { + self.session().unwrap().outgoing_stream_manager.send_bytes(data, options).await + } + /// Stream text incrementally to participants in the room. /// /// This method allows sending text data in chunks as it becomes available. diff --git a/livekit/tests/README.md b/livekit/tests/README.md new file mode 100644 index 000000000..a31f2d297 --- /dev/null +++ b/livekit/tests/README.md @@ -0,0 +1,10 @@ +# Integration Tests + +Some test cases depend on a LiveKit server and thus are not enabled by default; +to run them, start a local LiveKit server in development mode, and enable the +E2E test feature: + +```sh +livekit-server --dev +cargo test --features __lk-e2e-test +``` diff --git a/livekit/tests/common/e2e.rs b/livekit/tests/common/e2e.rs new file mode 100644 index 000000000..676c758c4 --- /dev/null +++ b/livekit/tests/common/e2e.rs @@ -0,0 +1,56 @@ +use anyhow::{Context, Result}; +use futures_util::future::try_join_all; +use libwebrtc::native::create_random_uuid; +use livekit::{Room, RoomEvent, RoomOptions}; +use livekit_api::access_token::{AccessToken, VideoGrants}; +use std::{env, time::Duration}; +use tokio::sync::mpsc::UnboundedReceiver; + +struct TestEnvironment { + api_key: String, + api_secret: String, + server_url: String, +} + +impl TestEnvironment { + /// Reads API key, secret, and server URL from the environment, using the + /// development defaults for values that are not present. + pub fn from_env_or_defaults() -> Self { + Self { + api_key: env::var("LIVEKIT_API_KEY").unwrap_or("devkey".into()), + api_secret: env::var("LIVEKIT_API_SECRET").unwrap_or("secret".into()), + server_url: env::var("LIVEKIT_URL").unwrap_or("http://localhost:7880".into()), + } + } +} + +/// Creates the specified number of connections to a shared room for testing. +pub async fn test_rooms(count: usize) -> Result)>> { + let test_env = TestEnvironment::from_env_or_defaults(); + let room_name = format!("test_room_{}", create_random_uuid()); + + let tokens = (0..count) + .into_iter() + .map(|id| -> Result { + let grants = + VideoGrants { room_join: true, room: room_name.clone(), ..Default::default() }; + Ok(AccessToken::with_api_key(&test_env.api_key, &test_env.api_secret) + .with_ttl(Duration::from_secs(30 * 60)) // 30 minutes + .with_grants(grants) + .with_identity(&format!("p{}", id)) + .to_jwt() + .context("Failed to generate JWT")?) + }) + .collect::>>()?; + + let rooms = try_join_all(tokens.into_iter().map(|token| { + let server_url = test_env.server_url.clone(); + async move { + let options = RoomOptions::default(); + Room::connect(&server_url, &token, options).await.context("Failed to connect to room") + } + })) + .await?; + + Ok(rooms) +} diff --git a/livekit/tests/common/mod.rs b/livekit/tests/common/mod.rs index 676c758c4..25c465db0 100644 --- a/livekit/tests/common/mod.rs +++ b/livekit/tests/common/mod.rs @@ -1,56 +1,6 @@ -use anyhow::{Context, Result}; -use futures_util::future::try_join_all; -use libwebrtc::native::create_random_uuid; -use livekit::{Room, RoomEvent, RoomOptions}; -use livekit_api::access_token::{AccessToken, VideoGrants}; -use std::{env, time::Duration}; -use tokio::sync::mpsc::UnboundedReceiver; +#[cfg(feature = "__lk-e2e-test")] +/// Utilities for end-to-end testing with a LiveKit server. +mod e2e; -struct TestEnvironment { - api_key: String, - api_secret: String, - server_url: String, -} - -impl TestEnvironment { - /// Reads API key, secret, and server URL from the environment, using the - /// development defaults for values that are not present. - pub fn from_env_or_defaults() -> Self { - Self { - api_key: env::var("LIVEKIT_API_KEY").unwrap_or("devkey".into()), - api_secret: env::var("LIVEKIT_API_SECRET").unwrap_or("secret".into()), - server_url: env::var("LIVEKIT_URL").unwrap_or("http://localhost:7880".into()), - } - } -} - -/// Creates the specified number of connections to a shared room for testing. -pub async fn test_rooms(count: usize) -> Result)>> { - let test_env = TestEnvironment::from_env_or_defaults(); - let room_name = format!("test_room_{}", create_random_uuid()); - - let tokens = (0..count) - .into_iter() - .map(|id| -> Result { - let grants = - VideoGrants { room_join: true, room: room_name.clone(), ..Default::default() }; - Ok(AccessToken::with_api_key(&test_env.api_key, &test_env.api_secret) - .with_ttl(Duration::from_secs(30 * 60)) // 30 minutes - .with_grants(grants) - .with_identity(&format!("p{}", id)) - .to_jwt() - .context("Failed to generate JWT")?) - }) - .collect::>>()?; - - let rooms = try_join_all(tokens.into_iter().map(|token| { - let server_url = test_env.server_url.clone(); - async move { - let options = RoomOptions::default(); - Room::connect(&server_url, &token, options).await.context("Failed to connect to room") - } - })) - .await?; - - Ok(rooms) -} +#[cfg(feature = "__lk-e2e-test")] +pub use e2e::*; diff --git a/livekit/tests/data_channel_test.rs b/livekit/tests/data_channel_test.rs index ba155aa8f..921386f31 100644 --- a/livekit/tests/data_channel_test.rs +++ b/livekit/tests/data_channel_test.rs @@ -1,20 +1,14 @@ -#![allow(unused_imports)] -use crate::common::test_rooms; -use anyhow::{anyhow, Result}; -use livekit::{DataPacket, RoomEvent, SimulateScenario}; -use std::{sync::Arc, time::Duration}; -use tokio::{sync::oneshot, time}; +#[cfg(feature = "__lk-e2e-test")] +use { + crate::common::test_rooms, + anyhow::{anyhow, Result}, + livekit::{DataPacket, RoomEvent, SimulateScenario}, + std::{sync::Arc, time::Duration}, + tokio::{sync::oneshot, time}, +}; mod common; -// These tests depend on a LiveKit server, and thus are not enabled by default; -// to run them, start a local LiveKit server in development mode, and enable the -// E2E test feature: -// -// > livekit-server --dev -// > cargo test --features __lk-e2e-test -// - #[cfg(feature = "__lk-e2e-test")] #[tokio::test] async fn test_reliable_retry() -> Result<()> { diff --git a/livekit/tests/data_stream_test.rs b/livekit/tests/data_stream_test.rs new file mode 100644 index 000000000..5540fa8ce --- /dev/null +++ b/livekit/tests/data_stream_test.rs @@ -0,0 +1,105 @@ +#[cfg(feature = "__lk-e2e-test")] +use { + crate::common::test_rooms, + anyhow::{anyhow, Context, Ok, Result}, + chrono::{TimeDelta, Utc}, + livekit::{ + OperationType, RoomEvent, StreamByteOptions, StreamReader, StreamTextOptions, + TextStreamInfo, + }, + std::{sync::Mutex, time::Duration}, + tokio::{time::timeout, try_join}, +}; + +mod common; + +#[cfg(feature = "__lk-e2e-test")] +#[tokio::test] +async fn test_send_bytes() -> Result<()> { + let mut rooms = test_rooms(2).await?; + let (sending_room, _) = rooms.pop().unwrap(); + let (_, mut receiving_event_rx) = rooms.pop().unwrap(); + let sender_identity = sending_room.local_participant().identity(); + + const BYTES_TO_SEND: &[u8] = &[0xFA; 16]; + + let send_text = async move { + let options = StreamByteOptions { topic: "some-topic".into(), ..Default::default() }; + let stream_info = + sending_room.local_participant().send_bytes(BYTES_TO_SEND, options).await?; + + assert!(!stream_info.id.is_empty()); + assert!( + stream_info.timestamp.signed_duration_since(Utc::now()).abs() <= TimeDelta::seconds(1) + ); + assert!(stream_info.total_length.is_some()); + assert_eq!(stream_info.mime_type, "application/octet-stream"); + assert_eq!(stream_info.topic, "some-topic"); + + Ok(()) + }; + let receive_text = async move { + while let Some(event) = receiving_event_rx.recv().await { + let RoomEvent::ByteStreamOpened { reader, topic, participant_identity } = event else { + continue; + }; + assert_eq!(topic, "some-topic"); + assert_eq!(participant_identity, sender_identity); + + let Some(reader) = reader.take() else { + return Err(anyhow!("Failed to take reader")); + }; + assert_eq!(reader.read_all().await?, BYTES_TO_SEND); + break; + } + Ok(()) + }; + + timeout(Duration::from_secs(5), async { try_join!(send_text, receive_text) }).await??; + Ok(()) +} + +#[cfg(feature = "__lk-e2e-test")] +#[tokio::test] +async fn test_send_text() -> Result<()> { + let mut rooms = test_rooms(2).await?; + let (sending_room, _) = rooms.pop().unwrap(); + let (_, mut receiving_event_rx) = rooms.pop().unwrap(); + let sender_identity = sending_room.local_participant().identity(); + + const TEXT_TO_SEND: &str = "some-text"; + + let send_text = async move { + let options = StreamTextOptions { topic: "some-topic".into(), ..Default::default() }; + let stream_info = sending_room.local_participant().send_text(TEXT_TO_SEND, options).await?; + + assert!(!stream_info.id.is_empty()); + assert!( + stream_info.timestamp.signed_duration_since(Utc::now()).abs() <= TimeDelta::seconds(1) + ); + assert!(stream_info.total_length.is_some()); + assert_eq!(stream_info.mime_type, "text/plain"); + assert_eq!(stream_info.topic, "some-topic"); + + Ok(()) + }; + let receive_text = async move { + while let Some(event) = receiving_event_rx.recv().await { + let RoomEvent::TextStreamOpened { reader, topic, participant_identity } = event else { + continue; + }; + assert_eq!(topic, "some-topic"); + assert_eq!(participant_identity, sender_identity); + + let Some(reader) = reader.take() else { + return Err(anyhow!("Failed to take reader")); + }; + assert_eq!(reader.read_all().await?, TEXT_TO_SEND); + break; + } + Ok(()) + }; + + timeout(Duration::from_secs(5), async { try_join!(send_text, receive_text) }).await??; + Ok(()) +} From a38271f1db7920659bbdaf88ce54632702645b9e Mon Sep 17 00:00:00 2001 From: CloudWebRTC Date: Thu, 25 Sep 2025 08:35:44 +0800 Subject: [PATCH 247/274] fix Builds/E2E Tests CI. (#715) * fix tests CI. * Update builds.yml * fix build for ios. * add VideoResolution::default(). * revert changes. * fmt. --- .github/workflows/builds.yml | 5 +++++ libwebrtc/src/video_source.rs | 9 ++++++++- webrtc-sys/build.rs | 1 + 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index 99acf50c4..cbb194093 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -75,6 +75,11 @@ jobs: sudo apt update -y sudo apt install -y libssl-dev libx11-dev libgl1-mesa-dev libxext-dev + - name: Install VA-API/NVIDIA drivers for linux x64 build + if: ${{ matrix.os == 'ubuntu-latest'}} + run: | + sudo apt install -y libva-dev libdrm-dev libnvidia-compute-570 libnvidia-decode-570 nvidia-cuda-dev -y + - uses: actions-rs/toolchain@v1 with: toolchain: stable diff --git a/libwebrtc/src/video_source.rs b/libwebrtc/src/video_source.rs index 4efb9e365..ff2d8826f 100644 --- a/libwebrtc/src/video_source.rs +++ b/libwebrtc/src/video_source.rs @@ -16,12 +16,19 @@ use livekit_protocol::enum_dispatch; use crate::imp::video_source as vs_imp; -#[derive(Default, Debug, Clone)] +#[derive(Debug, Clone)] pub struct VideoResolution { pub width: u32, pub height: u32, } +impl Default for VideoResolution { + // Default to 720p + fn default() -> Self { + VideoResolution { width: 1280, height: 720 } + } +} + #[non_exhaustive] #[derive(Debug, Clone)] pub enum RtcVideoSource { diff --git a/webrtc-sys/build.rs b/webrtc-sys/build.rs index e2a3dcdfd..03bdbd700 100644 --- a/webrtc-sys/build.rs +++ b/webrtc-sys/build.rs @@ -219,6 +219,7 @@ fn main() { .flag("-std=c++20"); } "ios" => { + println!("cargo:rustc-link-lib=framework=Foundation"); println!("cargo:rustc-link-lib=framework=CoreFoundation"); println!("cargo:rustc-link-lib=framework=AVFoundation"); println!("cargo:rustc-link-lib=framework=CoreAudio"); From 7688e492c6bb79009731496a3c630d10cf75b7fe Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Thu, 25 Sep 2025 11:49:37 +1000 Subject: [PATCH 248/274] Do not modify raw packets (#714) --- livekit/src/room/mod.rs | 2 +- .../src/room/participant/local_participant.rs | 38 ++++++++++++++----- livekit/src/rtc_engine/mod.rs | 4 +- livekit/src/rtc_engine/rtc_session.rs | 12 ++++-- 4 files changed, 40 insertions(+), 16 deletions(-) diff --git a/livekit/src/room/mod.rs b/livekit/src/room/mod.rs index de72cb808..93fbaea9d 100644 --- a/livekit/src/room/mod.rs +++ b/livekit/src/room/mod.rs @@ -1730,7 +1730,7 @@ async fn outgoing_data_stream_task( loop { tokio::select! { Ok((packet, responder)) = packet_rx.recv() => { - let result = engine.publish_data(packet, DataPacketKind::Reliable).await; + let result = engine.publish_data(packet, DataPacketKind::Reliable, false).await; let _ = responder.respond(result); }, _ = close_rx.recv() => { diff --git a/livekit/src/room/participant/local_participant.rs b/livekit/src/room/participant/local_participant.rs index 982463e1d..d757f09f5 100644 --- a/livekit/src/room/participant/local_participant.rs +++ b/livekit/src/room/participant/local_participant.rs @@ -407,7 +407,7 @@ impl LocalParticipant { ..Default::default() }; - match self.inner.rtc_engine.publish_data(data, DataPacketKind::Reliable).await { + match self.inner.rtc_engine.publish_data(data, DataPacketKind::Reliable, false).await { Ok(_) => Ok(ChatMessage::from(chat_message)), Err(e) => Err(Into::into(e)), } @@ -433,7 +433,7 @@ impl LocalParticipant { ..Default::default() }; - match self.inner.rtc_engine.publish_data(data, DataPacketKind::Reliable).await { + match self.inner.rtc_engine.publish_data(data, DataPacketKind::Reliable, false).await { Ok(_) => Ok(ChatMessage::from(proto_msg)), Err(e) => Err(Into::into(e)), } @@ -477,7 +477,7 @@ impl LocalParticipant { true => DataPacketKind::Reliable, false => DataPacketKind::Lossy, }; - self.inner.rtc_engine.publish_data(packet, kind).await.map_err(Into::into) + self.inner.rtc_engine.publish_data(packet, kind, true).await.map_err(Into::into) } pub async fn publish_data(&self, packet: DataPacket) -> RoomResult<()> { @@ -498,7 +498,7 @@ impl LocalParticipant { ..Default::default() }; - self.inner.rtc_engine.publish_data(data, kind).await.map_err(Into::into) + self.inner.rtc_engine.publish_data(data, kind, false).await.map_err(Into::into) } pub fn set_data_channel_buffered_amount_low_threshold( @@ -553,7 +553,11 @@ impl LocalParticipant { value: Some(proto::data_packet::Value::Transcription(transcription_packet)), ..Default::default() }; - self.inner.rtc_engine.publish_data(data, DataPacketKind::Reliable).await.map_err(Into::into) + self.inner + .rtc_engine + .publish_data(data, DataPacketKind::Reliable, false) + .await + .map_err(Into::into) } pub async fn publish_dtmf(&self, dtmf: SipDTMF) -> RoomResult<()> { @@ -567,7 +571,11 @@ impl LocalParticipant { ..Default::default() }; - self.inner.rtc_engine.publish_data(data, DataPacketKind::Reliable).await.map_err(Into::into) + self.inner + .rtc_engine + .publish_data(data, DataPacketKind::Reliable, false) + .await + .map_err(Into::into) } async fn publish_rpc_request(&self, rpc_request: RpcRequest) -> RoomResult<()> { @@ -587,7 +595,11 @@ impl LocalParticipant { ..Default::default() }; - self.inner.rtc_engine.publish_data(data, DataPacketKind::Reliable).await.map_err(Into::into) + self.inner + .rtc_engine + .publish_data(data, DataPacketKind::Reliable, false) + .await + .map_err(Into::into) } async fn publish_rpc_response(&self, rpc_response: RpcResponse) -> RoomResult<()> { @@ -611,7 +623,11 @@ impl LocalParticipant { ..Default::default() }; - self.inner.rtc_engine.publish_data(data, DataPacketKind::Reliable).await.map_err(Into::into) + self.inner + .rtc_engine + .publish_data(data, DataPacketKind::Reliable, false) + .await + .map_err(Into::into) } async fn publish_rpc_ack(&self, rpc_ack: RpcAck) -> RoomResult<()> { @@ -625,7 +641,11 @@ impl LocalParticipant { ..Default::default() }; - self.inner.rtc_engine.publish_data(data, DataPacketKind::Reliable).await.map_err(Into::into) + self.inner + .rtc_engine + .publish_data(data, DataPacketKind::Reliable, false) + .await + .map_err(Into::into) } pub(crate) async fn update_track_subscription_permissions(&self) { diff --git a/livekit/src/rtc_engine/mod.rs b/livekit/src/rtc_engine/mod.rs index 363bd6eb2..f3014e156 100644 --- a/livekit/src/rtc_engine/mod.rs +++ b/livekit/src/rtc_engine/mod.rs @@ -246,13 +246,13 @@ impl RtcEngine { &self, data: proto::DataPacket, kind: DataPacketKind, + is_raw_packet: bool, ) -> EngineResult<()> { let (session, _r_lock) = { let (handle, _r_lock) = self.inner.wait_reconnection().await?; (handle.session.clone(), _r_lock) }; - - session.publish_data(data, kind).await + session.publish_data(data, kind, is_raw_packet).await } pub async fn simulate_scenario(&self, scenario: SimulateScenario) -> EngineResult<()> { diff --git a/livekit/src/rtc_engine/rtc_session.rs b/livekit/src/rtc_engine/rtc_session.rs index 0bd6f10a7..6d95adaf9 100644 --- a/livekit/src/rtc_engine/rtc_session.rs +++ b/livekit/src/rtc_engine/rtc_session.rs @@ -213,7 +213,7 @@ enum DataChannelEventDetail { #[derive(Debug)] struct PublishPacketRequest { - /// Unencoded data packewt. + /// Unencoded data packet. packet: proto::DataPacket, /// Notifies the caller once the request has been fulfilled. @@ -488,8 +488,9 @@ impl RtcSession { &self, data: proto::DataPacket, kind: DataPacketKind, + is_raw_packet: bool, ) -> Result<(), EngineError> { - self.inner.publish_data(data, kind).await + self.inner.publish_data(data, kind, is_raw_packet).await } pub async fn restart(&self) -> EngineResult { @@ -1339,12 +1340,15 @@ impl SessionInner { self: &Arc, mut packet: proto::DataPacket, kind: DataPacketKind, + is_raw_packet: bool, ) -> Result<(), EngineError> { self.ensure_publisher_connected(kind).await?; // Populate local participant info fields - packet.participant_identity = self.participant_info.identity.to_string(); - packet.participant_sid = self.participant_info.sid.to_string(); + if !is_raw_packet { + packet.participant_identity = self.participant_info.identity.to_string(); + packet.participant_sid = self.participant_info.sid.to_string(); + } let (completion_tx, completion_rx) = oneshot::channel(); let ev = DataChannelEvent { From 59c82467e47231caf8ad4273c9fc248164b2ea65 Mon Sep 17 00:00:00 2001 From: CloudWebRTC Date: Thu, 25 Sep 2025 10:00:56 +0800 Subject: [PATCH 249/274] Update Rust toolchain installation method (#716) * Update Rust toolchain installation method Replaced actions-rs/toolchain with rustup for toolchain installation. * Update builds.yml * Add workflow_dispatch trigger to builds.yml --- .github/workflows/builds.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index cbb194093..a7b864c80 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -25,6 +25,7 @@ on: paths: - livekit/** - libwebrtc/** + workflow_dispatch: env: CARGO_TERM_COLOR: always @@ -80,10 +81,9 @@ jobs: run: | sudo apt install -y libva-dev libdrm-dev libnvidia-compute-570 libnvidia-decode-570 nvidia-cuda-dev -y - - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: ${{ matrix.target }} + - name: Install Rust toolchain. + run: | + rustup target add ${{ matrix.target }} - name: Build (Cargo) if: ${{ !contains(matrix.target, 'android') }} From 5989ef46a42f4397ba73ebe13222f6bed61d94e0 Mon Sep 17 00:00:00 2001 From: CloudWebRTC Date: Thu, 25 Sep 2025 11:56:17 +0800 Subject: [PATCH 250/274] Update Rust toolchain installation method (#717) Replaced actions-rs/toolchain with rustup target add. --- .github/workflows/ffi-builds.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ffi-builds.yml b/.github/workflows/ffi-builds.yml index 59dce6ec3..d7a7d0943 100644 --- a/.github/workflows/ffi-builds.yml +++ b/.github/workflows/ffi-builds.yml @@ -116,10 +116,9 @@ jobs: with: submodules: true - - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: ${{ matrix.target }} + - name: Install Rust toolchain. + run: | + rustup target add ${{ matrix.target }} - name: Set up QEMU if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' }} From 70141f4a8879071903af634a50538541c28b25fc Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Thu, 25 Sep 2025 18:10:04 +1000 Subject: [PATCH 251/274] Fix intermittently failing E2E reliability test (#718) * Introduce test-log as a dev dependency * Add participant name to generated test token * Create room tests * Wait for participant visibility * Simplify E2E reliability test * Remove unused imports --- Cargo.lock | 237 ++++++++++++++++++++++++++++- livekit/Cargo.toml | 1 + livekit/tests/common/e2e.rs | 35 ++++- livekit/tests/data_channel_test.rs | 91 ++++++----- livekit/tests/data_stream_test.rs | 9 +- livekit/tests/room_test.rs | 52 +++++++ 6 files changed, 358 insertions(+), 67 deletions(-) create mode 100644 livekit/tests/room_test.rs diff --git a/Cargo.lock b/Cargo.lock index f46a20ba7..dbbff8a54 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -52,6 +52,56 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +dependencies = [ + "windows-sys 0.60.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.60.2", +] + [[package]] name = "anyhow" version = "1.0.99" @@ -512,6 +562,12 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + [[package]] name = "combine" version = "4.6.6" @@ -787,6 +843,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "env_filter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +dependencies = [ + "log", +] + [[package]] name = "env_logger" version = "0.10.1" @@ -800,6 +865,18 @@ dependencies = [ "termcolor", ] +[[package]] +name = "env_logger" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "log", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -1411,6 +1488,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "isahc" version = "1.7.2" @@ -1554,7 +1637,7 @@ name = "libwebrtc" version = "0.3.14" dependencies = [ "cxx", - "env_logger", + "env_logger 0.10.1", "jni", "js-sys", "lazy_static", @@ -1626,6 +1709,7 @@ dependencies = [ "semver", "serde", "serde_json", + "test-log", "thiserror", "tokio", ] @@ -1668,7 +1752,7 @@ dependencies = [ "console-subscriber", "dashmap", "downcast-rs", - "env_logger", + "env_logger 0.10.1", "futures-util", "imgproc", "jni", @@ -1727,9 +1811,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" dependencies = [ "value-bag", ] @@ -1821,6 +1905,16 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-traits" version = "0.2.17" @@ -1855,6 +1949,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + [[package]] name = "openssl" version = "0.10.61" @@ -1909,6 +2009,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "parking" version = "2.2.0" @@ -2803,6 +2909,28 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "test-log" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e33b98a582ea0be1168eba097538ee8dd4bbe0f2b01b22ac92ea30054e5be7b" +dependencies = [ + "env_logger 0.11.8", + "test-log-macros", + "tracing-subscriber", +] + +[[package]] +name = "test-log-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451b374529930d7601b1eef8d32bc79ae870b6079b069401709c2a8bf9e75f36" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.50", +] + [[package]] name = "thiserror" version = "1.0.51" @@ -3083,6 +3211,17 @@ dependencies = [ "tracing", ] +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + [[package]] name = "tracing-subscriber" version = "0.3.18" @@ -3090,6 +3229,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "matchers", + "nu-ansi-term", "once_cell", "parking_lot", "regex", @@ -3097,6 +3237,7 @@ dependencies = [ "thread_local", "tracing", "tracing-core", + "tracing-log", ] [[package]] @@ -3202,6 +3343,12 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "valuable" version = "0.1.0" @@ -3359,7 +3506,7 @@ dependencies = [ "cc", "cxx", "cxx-build", - "env_logger", + "env_logger 0.10.1", "glob", "log", "webrtc-sys-build", @@ -3430,6 +3577,12 @@ dependencies = [ "windows-targets 0.52.0", ] +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + [[package]] name = "windows-sys" version = "0.45.0" @@ -3457,6 +3610,15 @@ dependencies = [ "windows-targets 0.52.0", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.3", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -3502,6 +3664,23 @@ dependencies = [ "windows_x86_64_msvc 0.52.0", ] +[[package]] +name = "windows-targets" +version = "0.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -3520,6 +3699,12 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -3538,6 +3723,12 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -3556,6 +3747,18 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -3574,6 +3777,12 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -3592,6 +3801,12 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -3610,6 +3825,12 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -3628,6 +3849,12 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "winreg" version = "0.50.0" diff --git a/livekit/Cargo.toml b/livekit/Cargo.toml index bbaae8e33..21d412fb2 100644 --- a/livekit/Cargo.toml +++ b/livekit/Cargo.toml @@ -47,3 +47,4 @@ bmrng = "0.5.2" [dev-dependencies] anyhow = "1.0.99" +test-log = "0.2.18" diff --git a/livekit/tests/common/e2e.rs b/livekit/tests/common/e2e.rs index 676c758c4..baeb566b9 100644 --- a/livekit/tests/common/e2e.rs +++ b/livekit/tests/common/e2e.rs @@ -1,10 +1,13 @@ use anyhow::{Context, Result}; -use futures_util::future::try_join_all; +use chrono::Utc; use libwebrtc::native::create_random_uuid; use livekit::{Room, RoomEvent, RoomOptions}; use livekit_api::access_token::{AccessToken, VideoGrants}; use std::{env, time::Duration}; -use tokio::sync::mpsc::UnboundedReceiver; +use tokio::{ + sync::mpsc::UnboundedReceiver, + time::{self, timeout}, +}; struct TestEnvironment { api_key: String, @@ -38,19 +41,35 @@ pub async fn test_rooms(count: usize) -> Result>>()?; - let rooms = try_join_all(tokens.into_iter().map(|token| { + let mut rooms = Vec::with_capacity(count); + for token in tokens { let server_url = test_env.server_url.clone(); - async move { - let options = RoomOptions::default(); - Room::connect(&server_url, &token, options).await.context("Failed to connect to room") + let options = RoomOptions::default(); + rooms.push( + Room::connect(&server_url, &token, options) + .await + .context("Failed to connect to room")?, + ); + } + + // Wait for participant visibility across all room connections. When using a + // local SFU, this takes significantly longer and can lead to intermittently failing tests. + let all_connected_time = Utc::now(); + let wait_participant_visibility = async { + while rooms.iter().any(|(room, _)| room.remote_participants().len() != count - 1) { + time::sleep(Duration::from_millis(10)).await; } - })) - .await?; + log::info!("All participants visible after {}", Utc::now() - all_connected_time); + }; + timeout(Duration::from_secs(5), wait_participant_visibility) + .await + .context("Not all participants became visible")?; Ok(rooms) } diff --git a/livekit/tests/data_channel_test.rs b/livekit/tests/data_channel_test.rs index 921386f31..ae0bf89b7 100644 --- a/livekit/tests/data_channel_test.rs +++ b/livekit/tests/data_channel_test.rs @@ -1,79 +1,74 @@ #[cfg(feature = "__lk-e2e-test")] use { crate::common::test_rooms, - anyhow::{anyhow, Result}, + anyhow::{Ok, Result}, livekit::{DataPacket, RoomEvent, SimulateScenario}, std::{sync::Arc, time::Duration}, - tokio::{sync::oneshot, time}, + tokio::{ + time::{self, timeout}, + try_join, + }, }; mod common; #[cfg(feature = "__lk-e2e-test")] -#[tokio::test] +#[test_log::test(tokio::test)] async fn test_reliable_retry() -> Result<()> { + use anyhow::Context; + const ITERATIONS: usize = 128; const PAYLOAD_SIZE: usize = 4096; - // Set up test rooms let mut rooms = test_rooms(2).await?; - let (sending_room, _) = rooms.pop().unwrap(); let (receiving_room, mut receiving_event_rx) = rooms.pop().unwrap(); - - let sending_room = Arc::new(sending_room); - let receiving_room = Arc::new(receiving_room); + let (sending_room, _) = rooms.pop().unwrap(); let receiving_identity = receiving_room.local_participant().identity(); - let (fulfill, expectation) = oneshot::channel(); + let sending_room = Arc::new(sending_room); tokio::spawn({ let sending_room = sending_room.clone(); async move { time::sleep(Duration::from_millis(200)).await; _ = sending_room.simulate_scenario(SimulateScenario::SignalReconnect).await; - println!("Reconnecting sending room"); - } - }); - tokio::spawn({ - let receiving_room = receiving_room.clone(); - async move { - time::sleep(Duration::from_millis(400)).await; - _ = receiving_room.simulate_scenario(SimulateScenario::SignalReconnect).await; - println!("Reconnecting receiving room"); + log::info!("Reconnecting sending room"); } }); - tokio::spawn({ - let fulfill = fulfill; - async move { - let mut packets_received = 0; - while let Some(event) = receiving_event_rx.recv().await { - if let RoomEvent::DataReceived { payload, .. } = event { - assert_eq!(payload.len(), PAYLOAD_SIZE); - packets_received += 1; - if packets_received == ITERATIONS { - fulfill.send(()).ok(); - break; - } - } - } - } + tokio::spawn(async move { + time::sleep(Duration::from_millis(400)).await; + _ = receiving_room.simulate_scenario(SimulateScenario::SignalReconnect).await; + log::info!("Reconnecting receiving room"); }); - for _ in 0..ITERATIONS { - let packet = DataPacket { - reliable: true, - payload: [0xFA; PAYLOAD_SIZE].to_vec(), - destination_identities: vec![receiving_identity.clone()], - ..Default::default() - }; - sending_room.local_participant().publish_data(packet).await?; - time::sleep(Duration::from_millis(10)).await; - } + let send_packets = async move { + for _ in 0..ITERATIONS { + let packet = DataPacket { + reliable: true, + payload: [0xFA; PAYLOAD_SIZE].to_vec(), + destination_identities: vec![receiving_identity.clone()], + ..Default::default() + }; + sending_room.local_participant().publish_data(packet).await?; + time::sleep(Duration::from_millis(10)).await; + } + Ok(()) + }; - match time::timeout(Duration::from_secs(15), expectation).await { - Ok(Ok(())) => Ok(()), - Ok(Err(_)) => Err(anyhow!("Not all packets were received")), - Err(_) => Err(anyhow!("Timed out waiting for packets")), - } + let receive_packets = async move { + let mut packets_received = 0; + while let Some(event) = receiving_event_rx.recv().await { + let RoomEvent::DataReceived { .. } = event else { continue }; + packets_received += 1; + if packets_received == ITERATIONS { + break; + } + } + Ok(()) + }; + timeout(Duration::from_secs(15), async { try_join!(send_packets, receive_packets) }) + .await? + .context("Not all packets received before timeout")?; + Ok(()) } diff --git a/livekit/tests/data_stream_test.rs b/livekit/tests/data_stream_test.rs index 5540fa8ce..c15ba6af9 100644 --- a/livekit/tests/data_stream_test.rs +++ b/livekit/tests/data_stream_test.rs @@ -1,13 +1,10 @@ #[cfg(feature = "__lk-e2e-test")] use { crate::common::test_rooms, - anyhow::{anyhow, Context, Ok, Result}, + anyhow::{anyhow, Ok, Result}, chrono::{TimeDelta, Utc}, - livekit::{ - OperationType, RoomEvent, StreamByteOptions, StreamReader, StreamTextOptions, - TextStreamInfo, - }, - std::{sync::Mutex, time::Duration}, + livekit::{RoomEvent, StreamByteOptions, StreamReader, StreamTextOptions}, + std::time::Duration, tokio::{time::timeout, try_join}, }; diff --git a/livekit/tests/room_test.rs b/livekit/tests/room_test.rs new file mode 100644 index 000000000..82a96d042 --- /dev/null +++ b/livekit/tests/room_test.rs @@ -0,0 +1,52 @@ +#[cfg(feature = "__lk-e2e-test")] +use { + anyhow::Result, + chrono::{TimeDelta, TimeZone, Utc}, + common::test_rooms, + livekit::{ConnectionState, ParticipantKind}, +}; + +mod common; + +#[cfg(feature = "__lk-e2e-test")] +#[test_log::test(tokio::test)] +async fn test_connect() -> Result<()> { + let (room, _) = test_rooms(1).await?.pop().unwrap(); + + assert_eq!(room.connection_state(), ConnectionState::Connected); + assert!(room.name().starts_with("test_room_")); + assert!(room.remote_participants().is_empty()); + + let creation_time = Utc.timestamp_opt(room.creation_time(), 0).unwrap(); + assert!(creation_time.signed_duration_since(Utc::now()).abs() <= TimeDelta::seconds(10)); + + let local_participant = room.local_participant(); + assert!(local_participant.sid().as_str().starts_with("PA_")); + assert_eq!(local_participant.identity().as_str(), "p0"); + assert_eq!(local_participant.name(), "Participant 0"); + assert_eq!(local_participant.kind(), ParticipantKind::Standard); + + Ok(()) +} + +#[cfg(feature = "__lk-e2e-test")] +#[test_log::test(tokio::test)] +async fn test_connect_multiple() -> Result<()> { + let mut rooms = test_rooms(2).await?; + + let (second_room, _) = rooms.pop().unwrap(); + let (first_room, _) = rooms.pop().unwrap(); + + assert_eq!(first_room.name(), second_room.name(), "Participants are in different rooms"); + + assert!(second_room + .remote_participants() + .get(&first_room.local_participant().identity()) + .is_some()); + assert!(first_room + .remote_participants() + .get(&second_room.local_participant().identity()) + .is_some()); + + Ok(()) +} From fed9cc619623670cbc8a10f79be817d8aae8c4e5 Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Sat, 27 Sep 2025 11:15:54 +1000 Subject: [PATCH 252/274] Use info level for test logging (#720) --- .github/workflows/tests.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1e214e666..3e64fc72e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -73,10 +73,14 @@ jobs: - name: Test (no E2E) if: ${{ !matrix.e2e-testing }} + env: + RUST_LOG: info run: cargo +nightly test --release --verbose --target ${{ matrix.target }} -- --nocapture - name: Test (with E2E) if: ${{ matrix.e2e-testing }} + env: + RUST_LOG: info run: cargo +nightly test --release --verbose --target ${{ matrix.target }} --features __lk-e2e-test -- --nocapture From 81a5de09324abd29a6829d44badf0701e328d6aa Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Sat, 27 Sep 2025 16:12:21 +1000 Subject: [PATCH 253/274] Implement Display and Error for RpcError (#719) --- livekit/src/room/participant/rpc.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/livekit/src/room/participant/rpc.rs b/livekit/src/room/participant/rpc.rs index 34e043efb..75df7af1b 100644 --- a/livekit/src/room/participant/rpc.rs +++ b/livekit/src/room/participant/rpc.rs @@ -4,7 +4,7 @@ use crate::room::participant::ParticipantIdentity; use livekit_protocol::RpcError as RpcError_Proto; -use std::time::Duration; +use std::{error::Error, fmt::Display, time::Duration}; /// Parameters for performing an RPC call #[derive(Debug, Clone)] @@ -84,6 +84,13 @@ impl RpcError { } } +impl Display for RpcError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "RPC Error: {} ({})", self.message, self.code) + } +} +impl Error for RpcError {} + #[derive(Debug, Clone, Copy)] pub enum RpcErrorCode { ApplicationError = 1500, From a5af4bb37b05210c0f1e78545657f520f2234983 Mon Sep 17 00:00:00 2001 From: CloudWebRTC Date: Mon, 29 Sep 2025 09:54:17 +0800 Subject: [PATCH 254/274] nvidia codec improve (#721) * chore: lower the log level for vaapi/nvidia codec. * fix compile. * fix benchmark for libwebrtc m137. * Modify the Cuda Context singleton. * Avoid crashes caused by decoding failure. * Removed unnecessary null checks. * tidy. * fix. * check the cuda device count. * fix. --- .../NvCodec/NvCodec/NvDecoder/NvDecoder.cpp | 2 +- webrtc-sys/src/nvidia/cuda_context.cpp | 80 ++++++++++++++++++- webrtc-sys/src/nvidia/cuda_context.h | 9 ++- webrtc-sys/src/nvidia/h264_encoder_impl.cpp | 1 - .../src/nvidia/nvidia_decoder_factory.cpp | 12 ++- .../src/nvidia/nvidia_decoder_factory.h | 2 +- .../src/nvidia/nvidia_encoder_factory.cpp | 9 +-- .../src/nvidia/nvidia_encoder_factory.h | 2 +- webrtc-sys/src/vaapi/h264_encoder_impl.cpp | 1 - webrtc-sys/src/vaapi/vaapi_display_drm.cpp | 4 +- .../src/vaapi/vaapi_encoder_factory.cpp | 8 +- webrtc-sys/test/CMakeLists.txt | 30 +++---- webrtc-sys/test/benchmark.cc | 33 ++++---- webrtc-sys/test/benchmark.h | 6 +- webrtc-sys/test/benchmark_nvidia.cc | 6 +- webrtc-sys/test/benchmark_nvidia.h | 2 +- webrtc-sys/test/benchmark_openh264.cc | 4 +- webrtc-sys/test/benchmark_openh264.h | 2 +- webrtc-sys/test/benchmark_vaapi.cc | 4 +- webrtc-sys/test/benchmark_vaapi.h | 2 +- webrtc-sys/test/test_main.cc | 6 +- 21 files changed, 150 insertions(+), 75 deletions(-) diff --git a/webrtc-sys/src/nvidia/NvCodec/NvCodec/NvDecoder/NvDecoder.cpp b/webrtc-sys/src/nvidia/NvCodec/NvCodec/NvDecoder/NvDecoder.cpp index 1ee70c428..d0ebde515 100644 --- a/webrtc-sys/src/nvidia/NvCodec/NvCodec/NvDecoder/NvDecoder.cpp +++ b/webrtc-sys/src/nvidia/NvCodec/NvCodec/NvDecoder/NvDecoder.cpp @@ -521,7 +521,7 @@ int NvDecoder::HandlePictureDecode(CUVIDPICPARAMS *pPicParams) { } m_nPicNumInDecodeOrder[pPicParams->CurrPicIdx] = m_nDecodePicCnt++; CUDA_DRVAPI_CALL(cuCtxPushCurrent(m_cuContext)); - NVDEC_API_CALL(cuvidDecodePicture(m_hDecoder, pPicParams)); + cuvidDecodePicture(m_hDecoder, pPicParams); if (m_bForce_zero_latency && ((!pPicParams->field_pic_flag) || (pPicParams->second_field))) { CUVIDPARSERDISPINFO dispInfo; diff --git a/webrtc-sys/src/nvidia/cuda_context.cpp b/webrtc-sys/src/nvidia/cuda_context.cpp index 82c21df2b..006e15519 100644 --- a/webrtc-sys/src/nvidia/cuda_context.cpp +++ b/webrtc-sys/src/nvidia/cuda_context.cpp @@ -61,16 +61,58 @@ static bool load_cuda_modules() { return true; } +static bool check_cuda_device() { + int device_count = 0; + int driver_version = 0; + + CUCTX_CUDA_CALL_ERROR(cuDriverGetVersion(&driver_version)); + if (kRequiredDriverVersion > driver_version) { + RTC_LOG(LS_ERROR) + << "CUDA driver version is not higher than the required version. " + << driver_version; + return false; + } + + CUresult result = cuInit(0); + if (result != CUDA_SUCCESS) { + RTC_LOG(LS_ERROR) << "Failed to initialize CUDA."; + return false; + } + + result = cuDeviceGetCount(&device_count); + if (result != CUDA_SUCCESS) { + RTC_LOG(LS_ERROR) << "Failed to get CUDA device count."; + return false; + } + + if (device_count == 0) { + RTC_LOG(LS_ERROR) << "No CUDA devices found."; + return false; + } + + return true; +} + +CudaContext* CudaContext::GetInstance() { + static CudaContext instance; + return &instance; +} + +bool CudaContext::IsAvailable() { + return load_cuda_modules() && check_cuda_device(); +} + bool CudaContext::Initialize() { // Initialize CUDA context bool success = load_cuda_modules(); if (!success) { - std::cout << "Failed to load CUDA modules. maybe the NVIDIA driver is not installed?" << std::endl; + RTC_LOG(LS_ERROR) << "Failed to load CUDA modules. maybe the NVIDIA driver " + "is not installed?"; return false; } - int numDevices = 0; + int num_devices = 0; CUdevice cu_device = 0; CUcontext context = nullptr; @@ -84,7 +126,23 @@ bool CudaContext::Initialize() { return false; } - CUCTX_CUDA_CALL_ERROR(cuInit(0)); + CUresult result = cuInit(0); + if (result != CUDA_SUCCESS) { + RTC_LOG(LS_ERROR) << "Failed to initialize CUDA."; + return false; + } + + result = cuDeviceGetCount(&num_devices); + if (result != CUDA_SUCCESS) { + RTC_LOG(LS_ERROR) << "Failed to get CUDA device count."; + return false; + } + + if (num_devices == 0) { + RTC_LOG(LS_ERROR) << "No CUDA devices found."; + return false; + } + CUCTX_CUDA_CALL_ERROR(cuDeviceGet(&cu_device, 0)); char device_name[80]; @@ -104,6 +162,22 @@ bool CudaContext::Initialize() { return true; } +CUcontext CudaContext::GetContext() const { + RTC_DCHECK(cu_context_ != nullptr); + // Ensure the context is current + CUcontext current; + if (cuCtxGetCurrent(¤t) != CUDA_SUCCESS) { + throw; + } + if (cu_context_ == current) { + return cu_context_; + } + if (cuCtxSetCurrent(cu_context_) != CUDA_SUCCESS) { + throw; + } + return cu_context_; +} + void CudaContext::Shutdown() { // Shutdown CUDA context if (cu_context_) { diff --git a/webrtc-sys/src/nvidia/cuda_context.h b/webrtc-sys/src/nvidia/cuda_context.h index 6011c10ee..2c6622d6f 100644 --- a/webrtc-sys/src/nvidia/cuda_context.h +++ b/webrtc-sys/src/nvidia/cuda_context.h @@ -8,12 +8,15 @@ namespace livekit { class CudaContext { public: CudaContext() = default; - ~CudaContext() { Shutdown(); } + ~CudaContext() = default; + static bool IsAvailable(); + + static CudaContext* GetInstance(); bool Initialize(); bool IsInitialized() const { return cu_context_ != nullptr; } - CUcontext GetContext() const { return cu_context_; } - CUdevice GetDevice() const { return cu_device_; } + CUcontext GetContext() const; + void Shutdown(); private: diff --git a/webrtc-sys/src/nvidia/h264_encoder_impl.cpp b/webrtc-sys/src/nvidia/h264_encoder_impl.cpp index cd8ed881c..dade3f0d3 100644 --- a/webrtc-sys/src/nvidia/h264_encoder_impl.cpp +++ b/webrtc-sys/src/nvidia/h264_encoder_impl.cpp @@ -248,7 +248,6 @@ int32_t NvidiaH264EncoderImpl::InitEncode( int32_t NvidiaH264EncoderImpl::RegisterEncodeCompleteCallback( EncodedImageCallback* callback) { - RTC_DCHECK(callback); encoded_image_callback_ = callback; return WEBRTC_VIDEO_CODEC_OK; } diff --git a/webrtc-sys/src/nvidia/nvidia_decoder_factory.cpp b/webrtc-sys/src/nvidia/nvidia_decoder_factory.cpp index 2d0b397c8..00fde1026 100644 --- a/webrtc-sys/src/nvidia/nvidia_decoder_factory.cpp +++ b/webrtc-sys/src/nvidia/nvidia_decoder_factory.cpp @@ -3,7 +3,6 @@ #include #include -#include #include "cuda_context.h" #include "h264_decoder_impl.h" @@ -65,7 +64,7 @@ std::vector SupportedNvDecoderCodecs(CUcontext context) { } NvidiaVideoDecoderFactory::NvidiaVideoDecoderFactory() - : cu_context_(std::make_unique()) { + : cu_context_(livekit::CudaContext::GetInstance()) { if (cu_context_->Initialize()) { supported_formats_ = SupportedNvDecoderCodecs(cu_context_->GetContext()); } else { @@ -78,12 +77,11 @@ NvidiaVideoDecoderFactory::NvidiaVideoDecoderFactory() NvidiaVideoDecoderFactory::~NvidiaVideoDecoderFactory() {} bool NvidiaVideoDecoderFactory::IsSupported() { - // Check if the CUDA context can be initialized. - auto cu_context = std::make_unique(); - if (!cu_context->Initialize()) { - std::cout << "Failed to initialize CUDA context." << std::endl; + if (!livekit::CudaContext::IsAvailable()) { + RTC_LOG(LS_WARNING) << "Cuda Context is not available."; return false; } + std::cout << "Nvidia Decoder is supported." << std::endl; return true; } @@ -96,7 +94,7 @@ std::unique_ptr NvidiaVideoDecoderFactory::Create( if (format.IsSameCodec(supported_format)) { // If the format is supported, create and return the encoder. if (!cu_context_) { - cu_context_ = std::make_unique(); + cu_context_ = livekit::CudaContext::GetInstance(); if (!cu_context_->Initialize()) { RTC_LOG(LS_ERROR) << "Failed to initialize CUDA context."; return nullptr; diff --git a/webrtc-sys/src/nvidia/nvidia_decoder_factory.h b/webrtc-sys/src/nvidia/nvidia_decoder_factory.h index 28094467b..979a432d4 100644 --- a/webrtc-sys/src/nvidia/nvidia_decoder_factory.h +++ b/webrtc-sys/src/nvidia/nvidia_decoder_factory.h @@ -26,7 +26,7 @@ class NvidiaVideoDecoderFactory : public VideoDecoderFactory { private: std::vector supported_formats_; - std::unique_ptr cu_context_; + livekit::CudaContext* cu_context_; }; } // namespace webrtc diff --git a/webrtc-sys/src/nvidia/nvidia_encoder_factory.cpp b/webrtc-sys/src/nvidia/nvidia_encoder_factory.cpp index 8d4e563f2..a30c96f9f 100644 --- a/webrtc-sys/src/nvidia/nvidia_encoder_factory.cpp +++ b/webrtc-sys/src/nvidia/nvidia_encoder_factory.cpp @@ -1,7 +1,6 @@ #include "nvidia_encoder_factory.h" #include -#include #include "cuda_context.h" #include "h264_encoder_impl.h" @@ -30,10 +29,8 @@ NvidiaVideoEncoderFactory::NvidiaVideoEncoderFactory() { NvidiaVideoEncoderFactory::~NvidiaVideoEncoderFactory() {} bool NvidiaVideoEncoderFactory::IsSupported() { - // Check if the CUDA context can be initialized. - auto cu_context = std::make_unique(); - if (!cu_context->Initialize()) { - std::cout << "Failed to initialize CUDA context." << std::endl; + if (!livekit::CudaContext::IsAvailable()) { + RTC_LOG(LS_WARNING) << "Cuda Context is not available."; return false; } @@ -49,7 +46,7 @@ std::unique_ptr NvidiaVideoEncoderFactory::Create( if (format.IsSameCodec(supported_format)) { // If the format is supported, create and return the encoder. if (!cu_context_) { - cu_context_ = std::make_unique(); + cu_context_ = livekit::CudaContext::GetInstance(); if (!cu_context_->Initialize()) { RTC_LOG(LS_ERROR) << "Failed to initialize CUDA context."; return nullptr; diff --git a/webrtc-sys/src/nvidia/nvidia_encoder_factory.h b/webrtc-sys/src/nvidia/nvidia_encoder_factory.h index b18c49ca0..785924d01 100644 --- a/webrtc-sys/src/nvidia/nvidia_encoder_factory.h +++ b/webrtc-sys/src/nvidia/nvidia_encoder_factory.h @@ -34,7 +34,7 @@ class NvidiaVideoEncoderFactory : public VideoEncoderFactory { private: std::vector supported_formats_; - std::unique_ptr cu_context_; + livekit::CudaContext* cu_context_ = nullptr; }; } // namespace webrtc diff --git a/webrtc-sys/src/vaapi/h264_encoder_impl.cpp b/webrtc-sys/src/vaapi/h264_encoder_impl.cpp index b2232bdcd..dfa75d335 100644 --- a/webrtc-sys/src/vaapi/h264_encoder_impl.cpp +++ b/webrtc-sys/src/vaapi/h264_encoder_impl.cpp @@ -163,7 +163,6 @@ int32_t VAAPIH264EncoderWrapper::InitEncode( int32_t VAAPIH264EncoderWrapper::RegisterEncodeCompleteCallback( EncodedImageCallback* callback) { - RTC_DCHECK(callback); encoded_image_callback_ = callback; return WEBRTC_VIDEO_CODEC_OK; } diff --git a/webrtc-sys/src/vaapi/vaapi_display_drm.cpp b/webrtc-sys/src/vaapi/vaapi_display_drm.cpp index ca382a769..e6b02911e 100644 --- a/webrtc-sys/src/vaapi/vaapi_display_drm.cpp +++ b/webrtc-sys/src/vaapi/vaapi_display_drm.cpp @@ -4,7 +4,6 @@ #include #include #include -#include #ifdef IN_LIBVA #include "va/drm/va_drm.h" @@ -111,7 +110,8 @@ namespace livekit { bool VaapiDisplayDrm::Open() { va_display_ = va_open_display_drm(&drm_fd_); if (!va_display_) { - std::cout << "Failed to open VA display. Maybe the AMD video driver or libva-dev/libdrm-dev is not installed?" << std::endl; + RTC_LOG(LS_ERROR) << "Failed to open VA drm display. Maybe the AMD video " + "driver or libva-dev/libdrm-dev is not installed?"; return false; } return true; diff --git a/webrtc-sys/src/vaapi/vaapi_encoder_factory.cpp b/webrtc-sys/src/vaapi/vaapi_encoder_factory.cpp index 83c29a645..3a5f41d9b 100644 --- a/webrtc-sys/src/vaapi/vaapi_encoder_factory.cpp +++ b/webrtc-sys/src/vaapi/vaapi_encoder_factory.cpp @@ -1,17 +1,17 @@ #include "vaapi_encoder_factory.h" #include +#include #include "h264_encoder_impl.h" - -#include +#include "rtc_base/logging.h" #if defined(WIN32) #include "vaapi_display_win32.h" using VaapiDisplay = livekit::VaapiDisplayWin32; #elif defined(__linux__) #include "vaapi_display_drm.h" -using VaapiDisplay = livekit::VaapiDisplayDrm ; +using VaapiDisplay = livekit::VaapiDisplayDrm; #endif namespace webrtc { @@ -41,7 +41,7 @@ bool VAAPIVideoEncoderFactory::IsSupported() { // This could involve checking if the VAAPI display can be opened. VaapiDisplay vaapi_display; if (!vaapi_display.Open()) { - std::cerr << "Failed to open VAAPI display." << std::endl; + RTC_LOG(LS_WARNING) << "Failed to open VAAPI display."; return false; } diff --git a/webrtc-sys/test/CMakeLists.txt b/webrtc-sys/test/CMakeLists.txt index 07af2c706..ed47a648e 100644 --- a/webrtc-sys/test/CMakeLists.txt +++ b/webrtc-sys/test/CMakeLists.txt @@ -41,7 +41,7 @@ add_definitions(-DWEBRTC_LIBRARY_IMPL) add_definitions(-DWEBRTC_ENABLE_AVX2) include_directories( - "${CMAKE_CURRENT_SOURCE_DIR}/../libwebrtc/linux-x64-release/include" + "${CMAKE_CURRENT_SOURCE_DIR}/../libwebrtc/linux-x64-debug/include" "${CMAKE_CURRENT_SOURCE_DIR}/../libwebrtc/linux-x64-release/include/third_party/abseil-cpp" "${CMAKE_CURRENT_SOURCE_DIR}/../libwebrtc/linux-x64-release/include/third_party/libyuv/include" "${CMAKE_CURRENT_SOURCE_DIR}/../src/nvidia/NvCodec/include" @@ -63,19 +63,19 @@ add_executable(${BINARY_NAME} "fileutils.cc" "cpu/cpu_linux.cc" - #"benchmark_nvidia.cc" - #"../src/nvidia/NvCodec/NvCodec/NvDecoder/NvDecoder.cpp" - #"../src/nvidia/NvCodec/NvCodec/NvEncoder/NvEncoder.cpp" - #"../src/nvidia/NvCodec/NvCodec/NvEncoder/NvEncoderCuda.cpp" - #"../src/nvidia/h264_encoder_impl.cpp" - #"../src/nvidia/h264_decoder_impl.cpp" - #"../src/nvidia/nvidia_decoder_factory.cpp" - #"../src/nvidia/nvidia_encoder_factory.cpp" - #"../src/nvidia/cuda_context.cpp" - #"../src/nvidia/implib/libcuda.so.init.c" - #"../src/nvidia/implib/libcuda.so.tramp.S" - #"../src/nvidia/implib/libnvcuvid.so.init.c" - #"../src/nvidia/implib/libnvcuvid.so.tramp.S" + "benchmark_nvidia.cc" + "../src/nvidia/NvCodec/NvCodec/NvDecoder/NvDecoder.cpp" + "../src/nvidia/NvCodec/NvCodec/NvEncoder/NvEncoder.cpp" + "../src/nvidia/NvCodec/NvCodec/NvEncoder/NvEncoderCuda.cpp" + "../src/nvidia/h264_encoder_impl.cpp" + "../src/nvidia/h264_decoder_impl.cpp" + "../src/nvidia/nvidia_decoder_factory.cpp" + "../src/nvidia/nvidia_encoder_factory.cpp" + "../src/nvidia/cuda_context.cpp" + "../src/nvidia/implib/libcuda.so.init.c" + "../src/nvidia/implib/libcuda.so.tramp.S" + "../src/nvidia/implib/libnvcuvid.so.init.c" + "../src/nvidia/implib/libnvcuvid.so.tramp.S" "benchmark_vaapi.cc" "../src/vaapi/vaapi_display_drm.cpp" @@ -89,4 +89,4 @@ add_executable(${BINARY_NAME} ) target_link_libraries(${BINARY_NAME} ${CMAKE_THREAD_LIBS_INIT}) -target_link_libraries(${BINARY_NAME} dl) \ No newline at end of file +target_link_libraries(${BINARY_NAME} dl) diff --git a/webrtc-sys/test/benchmark.cc b/webrtc-sys/test/benchmark.cc index 5ae4df619..b66e2ec96 100644 --- a/webrtc-sys/test/benchmark.cc +++ b/webrtc-sys/test/benchmark.cc @@ -87,13 +87,15 @@ VideoEncodeCompleteCallback::OnEncodedImage( Benchmark::Benchmark() : _resultsFileName(webrtc::test::OutputPath() + "benchmark.txt"), - _codecName("Default") {} + _codecName("Default"), + _env(webrtc::CreateEnvironment()) {} Benchmark::Benchmark(std::string name, std::string description) : _name(name), _description(description), _resultsFileName(webrtc::test::OutputPath() + "benchmark.txt"), - _codecName("Default") {} + _codecName("Default"), + _env(webrtc::CreateEnvironment()) {} Benchmark::Benchmark(std::string name, std::string description, @@ -103,7 +105,8 @@ Benchmark::Benchmark(std::string name, _description(description), _resultsFileName(resultsFileName), _codecName(codecName), - _cpu(webrtc::CpuWrapper::CreateCpu()) {} + _cpu(webrtc::CpuWrapper::CreateCpu()), + _env(webrtc::CreateEnvironment()) {} void Benchmark::Perform() { std::vector sources; @@ -113,12 +116,12 @@ void Benchmark::Perform() { sources.push_back(new const VideoSource( webrtc::test::ProjectRootPath() + "resources/FourPeople_1280x720_30.yuv", kWHD)); - sources.push_back( - new const VideoSource(webrtc::test::ProjectRootPath() + - "resources/Big_Buck_Bunny_1920x1080_30.yuv", - kWFullHD)); + //sources.push_back( + // new const VideoSource(webrtc::test::ProjectRootPath() + + // "resources/Big_Buck_Bunny_1920x1080_30.yuv", + // kWFullHD)); - const VideoSize size[] = {kWHD, kWFullHD}; + const VideoSize size[] = {kWHD}; const int frameRate[] = {30}; // Specifies the framerates for which to perform a speed test. const bool speedTestMask[] = {true}; @@ -231,12 +234,12 @@ void Benchmark::Perform() { } void Benchmark::PerformNormalTest() { - _encoder = GetNewEncoder(); + _encoder = GetNewEncoder(_env); _lengthSourceFrame = _target->GetFrameLength(); CodecSettings(_target->GetWidth(), _target->GetHeight(), _target->GetFrameRate(), _bitRate); Setup(); - std::unique_ptr waitEvent = std::make_unique(); + std::unique_ptr waitEvent = std::make_unique(); //_inputVideoBuffer.VerifyAndAllocate(_lengthSourceFrame); _encoder->InitEncode(&_inst, 4, 1440); CodecSpecific_InitBitrate(); @@ -301,7 +304,7 @@ void Benchmark::Teardown() { } void Benchmark::CodecSpecific_InitBitrate() { - webrtc::SimulcastRateAllocator init_allocator(_inst); + webrtc::SimulcastRateAllocator init_allocator(_env,_inst); if (_bitRate == 0) { VideoBitrateAllocation allocation = @@ -328,7 +331,7 @@ bool Benchmark::Encode() { return true; } // TODO: build video frame from buffer ptr. - rtc::scoped_refptr buffer( + webrtc::scoped_refptr buffer( webrtc::I420Buffer::Create(_inst.width, _inst.height)); buffer->InitializeData(); @@ -346,7 +349,7 @@ bool Benchmark::Encode() { return true; } _encodeCompleteTime = 0; - _encodeTimes[inputVideoBuffer.timestamp()] = tGetTime(); + _encodeTimes[inputVideoBuffer.rtp_timestamp()] = tGetTime(); std::vector frame_types(1, VideoFrameType::kVideoFrameDelta); // check SLI queue @@ -387,9 +390,9 @@ bool Benchmark::Encode() { if (_encodeCompleteTime > 0) { _totalEncodeTime += - _encodeCompleteTime - _encodeTimes[inputVideoBuffer.timestamp()]; + _encodeCompleteTime - _encodeTimes[inputVideoBuffer.rtp_timestamp()]; } else { - _totalEncodeTime += tGetTime() - _encodeTimes[inputVideoBuffer.timestamp()]; + _totalEncodeTime += tGetTime() - _encodeTimes[inputVideoBuffer.rtp_timestamp()]; } assert(ret >= 0); return false; diff --git a/webrtc-sys/test/benchmark.h b/webrtc-sys/test/benchmark.h index a86f6b737..2ae2900b9 100644 --- a/webrtc-sys/test/benchmark.h +++ b/webrtc-sys/test/benchmark.h @@ -18,6 +18,7 @@ #include #include "cpu/cpu_linux.h" +#include "api/environment/environment_factory.h" #include "modules/include/module_common_types.h" #include "modules/video_coding/include/video_codec_interface.h" #include "rtc_base/synchronization/mutex.h" @@ -98,7 +99,7 @@ class Benchmark { std::string description, std::string resultsFileName, std::string codecName); - virtual webrtc::VideoEncoder* GetNewEncoder() = 0; + virtual webrtc::VideoEncoder* GetNewEncoder(webrtc::Environment &env) = 0; virtual void PerformNormalTest(); virtual void CodecSpecific_InitBitrate(); static const char* GetMagicStr() { return "#!benchmark1.0"; } @@ -167,7 +168,7 @@ class Benchmark { std::string _inname; std::string _outname; webrtc::VideoEncoder* _encoder; - webrtc::VideoDecoder* _decoder; + //webrtc::VideoDecoder* _decoder; uint32_t _bitRate; bool _appendNext = false; int _framecnt; @@ -207,6 +208,7 @@ class Benchmark { uint64_t _lastDecPictureId = 0; std::list _signalPLI; webrtc::CpuWrapper* _cpu; + webrtc::Environment _env; }; #endif // WEBRTC_MODULES_VIDEO_CODING_CODECS_TEST_FRAWEWORK_BENCHMARK_H_ diff --git a/webrtc-sys/test/benchmark_nvidia.cc b/webrtc-sys/test/benchmark_nvidia.cc index c5e5dbef2..168988bfb 100644 --- a/webrtc-sys/test/benchmark_nvidia.cc +++ b/webrtc-sys/test/benchmark_nvidia.cc @@ -24,7 +24,7 @@ NvidiaBenchmark::NvidiaBenchmark(std::string name, : Benchmark(name, description, resultsFileName, "nvidia_bitstream_output.h264") {} -VideoEncoder* NvidiaBenchmark::GetNewEncoder() { +VideoEncoder* NvidiaBenchmark::GetNewEncoder(webrtc::Environment &env) { if (!NvidiaVideoEncoderFactory::IsSupported()) { fprintf(stderr, "NVIDIA is not supported on this system.\n"); return nullptr; @@ -34,13 +34,13 @@ VideoEncoder* NvidiaBenchmark::GetNewEncoder() { _factory = std::make_unique(); } std::map baselineParameters = { - {"profile-level-id", "4d0032"}, + {"profile-level-id", "42e01f"}, {"level-asymmetry-allowed", "1"}, {"packetization-mode", "1"}, }; auto format = SdpVideoFormat("H264", baselineParameters); - auto enc = _factory->Create(webrtc::CreateEnvironment(), format); + auto enc = _factory->Create(env, format); if (!enc) { fprintf(stderr, "Failed to create H264 encoder.\n"); return nullptr; diff --git a/webrtc-sys/test/benchmark_nvidia.h b/webrtc-sys/test/benchmark_nvidia.h index 167fb383f..3c9271743 100644 --- a/webrtc-sys/test/benchmark_nvidia.h +++ b/webrtc-sys/test/benchmark_nvidia.h @@ -16,7 +16,7 @@ class NvidiaBenchmark : public Benchmark { } protected: - webrtc::VideoEncoder* GetNewEncoder() override; + webrtc::VideoEncoder* GetNewEncoder(webrtc::Environment &env) override; private: std::unique_ptr _encoder; diff --git a/webrtc-sys/test/benchmark_openh264.cc b/webrtc-sys/test/benchmark_openh264.cc index 59a2adbcd..383f969f6 100644 --- a/webrtc-sys/test/benchmark_openh264.cc +++ b/webrtc-sys/test/benchmark_openh264.cc @@ -23,8 +23,8 @@ OpenH264Benchmark::OpenH264Benchmark(std::string name, std::string resultsFileName) : Benchmark(name, description, resultsFileName, "openh264_bitstream_output.h264") {} -VideoEncoder* OpenH264Benchmark::GetNewEncoder() { - auto enc = CreateH264Encoder(webrtc::CreateEnvironment()); +VideoEncoder* OpenH264Benchmark::GetNewEncoder(webrtc::Environment &env) { + auto enc = CreateH264Encoder(env); if (!enc) { fprintf(stderr, "Failed to create H264 encoder.\n"); return nullptr; diff --git a/webrtc-sys/test/benchmark_openh264.h b/webrtc-sys/test/benchmark_openh264.h index 36caed5dd..f1eb1abb9 100644 --- a/webrtc-sys/test/benchmark_openh264.h +++ b/webrtc-sys/test/benchmark_openh264.h @@ -15,7 +15,7 @@ class OpenH264Benchmark : public Benchmark { } protected: - webrtc::VideoEncoder* GetNewEncoder() override; + webrtc::VideoEncoder* GetNewEncoder(webrtc::Environment &env) override; private: std::unique_ptr _encoder; diff --git a/webrtc-sys/test/benchmark_vaapi.cc b/webrtc-sys/test/benchmark_vaapi.cc index 030e734cb..7c3f0971b 100644 --- a/webrtc-sys/test/benchmark_vaapi.cc +++ b/webrtc-sys/test/benchmark_vaapi.cc @@ -23,7 +23,7 @@ VaapiBenchmark::VaapiBenchmark(std::string name, std::string resultsFileName) : Benchmark(name, description, resultsFileName, "vaapi_bitstream_output.h264") {} -VideoEncoder* VaapiBenchmark::GetNewEncoder() { +VideoEncoder* VaapiBenchmark::GetNewEncoder(webrtc::Environment &env) { if (!VAAPIVideoEncoderFactory::IsSupported()) { fprintf(stderr, "VAAPI is not supported on this system.\n"); return nullptr; @@ -38,7 +38,7 @@ VideoEncoder* VaapiBenchmark::GetNewEncoder() { }; auto format = SdpVideoFormat("H264", baselineParameters); - auto enc = _factory->Create(webrtc::CreateEnvironment(), format); + auto enc = _factory->Create(env, format); if (!enc) { fprintf(stderr, "Failed to create H264 encoder.\n"); return nullptr; diff --git a/webrtc-sys/test/benchmark_vaapi.h b/webrtc-sys/test/benchmark_vaapi.h index e72a44964..95ec057b9 100644 --- a/webrtc-sys/test/benchmark_vaapi.h +++ b/webrtc-sys/test/benchmark_vaapi.h @@ -16,7 +16,7 @@ class VaapiBenchmark : public Benchmark { } protected: - webrtc::VideoEncoder* GetNewEncoder() override; + webrtc::VideoEncoder* GetNewEncoder(webrtc::Environment &env) override; private: std::unique_ptr _encoder; diff --git a/webrtc-sys/test/test_main.cc b/webrtc-sys/test/test_main.cc index 1623e6903..6b8209f6d 100644 --- a/webrtc-sys/test/test_main.cc +++ b/webrtc-sys/test/test_main.cc @@ -1,4 +1,4 @@ -//#include "benchmark_nvidia.h" +#include "benchmark_nvidia.h" #include "benchmark_openh264.h" #include "benchmark_vaapi.h" #include "stdio.h" @@ -6,8 +6,8 @@ int main(int argc, char** argv) { std::vector benchmarks; - //benchmarks.push_back(new NvidiaBenchmark()); - benchmarks.push_back(new VaapiBenchmark()); + benchmarks.push_back(new NvidiaBenchmark()); + //benchmarks.push_back(new VaapiBenchmark()); benchmarks.push_back(new OpenH264Benchmark()); for (auto benchmark : benchmarks) { From 02150915f7215345cf24f19aeee05f9e7e3c8123 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 29 Sep 2025 13:43:27 +0800 Subject: [PATCH 255/274] chore: release (#704) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- Cargo.lock | 18 +++++++++--------- Cargo.toml | 18 +++++++++--------- imgproc/CHANGELOG.md | 6 ++++++ imgproc/Cargo.toml | 2 +- libwebrtc/CHANGELOG.md | 6 ++++++ libwebrtc/Cargo.toml | 2 +- livekit-api/CHANGELOG.md | 6 ++++++ livekit-api/Cargo.toml | 2 +- livekit-ffi/CHANGELOG.md | 16 ++++++++++++++++ livekit-ffi/Cargo.toml | 2 +- livekit-protocol/CHANGELOG.md | 6 ++++++ livekit-protocol/Cargo.toml | 2 +- livekit/CHANGELOG.md | 15 +++++++++++++++ livekit/Cargo.toml | 2 +- webrtc-sys/CHANGELOG.md | 11 +++++++++++ webrtc-sys/Cargo.toml | 2 +- webrtc-sys/build/CHANGELOG.md | 6 ++++++ webrtc-sys/build/Cargo.toml | 2 +- yuv-sys/CHANGELOG.md | 6 ++++++ yuv-sys/Cargo.toml | 2 +- 20 files changed, 105 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dbbff8a54..bb5756d6b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1417,7 +1417,7 @@ dependencies = [ [[package]] name = "imgproc" -version = "0.3.13" +version = "0.3.14" dependencies = [ "yuv-sys", ] @@ -1634,7 +1634,7 @@ dependencies = [ [[package]] name = "libwebrtc" -version = "0.3.14" +version = "0.3.15" dependencies = [ "cxx", "env_logger 0.10.1", @@ -1690,7 +1690,7 @@ checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "livekit" -version = "0.7.18" +version = "0.7.19" dependencies = [ "anyhow", "bmrng", @@ -1716,7 +1716,7 @@ dependencies = [ [[package]] name = "livekit-api" -version = "0.4.6" +version = "0.4.7" dependencies = [ "async-tungstenite", "base64", @@ -1746,7 +1746,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.12.33" +version = "0.12.34" dependencies = [ "bytes", "console-subscriber", @@ -1773,7 +1773,7 @@ dependencies = [ [[package]] name = "livekit-protocol" -version = "0.4.0" +version = "0.5.0" dependencies = [ "futures-util", "livekit-runtime", @@ -3501,7 +3501,7 @@ checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" [[package]] name = "webrtc-sys" -version = "0.3.11" +version = "0.3.12" dependencies = [ "cc", "cxx", @@ -3514,7 +3514,7 @@ dependencies = [ [[package]] name = "webrtc-sys-build" -version = "0.3.7" +version = "0.3.8" dependencies = [ "anyhow", "fs2", @@ -3876,7 +3876,7 @@ dependencies = [ [[package]] name = "yuv-sys" -version = "0.3.8" +version = "0.3.9" dependencies = [ "bindgen", "cc", diff --git a/Cargo.toml b/Cargo.toml index fcf72fcc6..d5d5dfa5e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,14 +15,14 @@ members = [ ] [workspace.dependencies] -imgproc = { version = "0.3.13", path = "imgproc" } -yuv-sys = { version = "0.3.8", path = "yuv-sys" } -libwebrtc = { version = "0.3.14", path = "libwebrtc" } -livekit-api = { version = "0.4.6", path = "livekit-api" } -livekit-ffi = { version = "0.12.33", path = "livekit-ffi" } -livekit-protocol = { version = "0.4.0", path = "livekit-protocol" } +imgproc = { version = "0.3.14", path = "imgproc" } +yuv-sys = { version = "0.3.9", path = "yuv-sys" } +libwebrtc = { version = "0.3.15", path = "libwebrtc" } +livekit-api = { version = "0.4.7", path = "livekit-api" } +livekit-ffi = { version = "0.12.34", path = "livekit-ffi" } +livekit-protocol = { version = "0.5.0", path = "livekit-protocol" } livekit-runtime = { version = "0.4.0", path = "livekit-runtime" } -livekit = { version = "0.7.18", path = "livekit" } +livekit = { version = "0.7.19", path = "livekit" } soxr-sys = { version = "0.1.0", path = "soxr-sys" } -webrtc-sys-build = { version = "0.3.7", path = "webrtc-sys/build" } -webrtc-sys = { version = "0.3.11", path = "webrtc-sys" } +webrtc-sys-build = { version = "0.3.8", path = "webrtc-sys/build" } +webrtc-sys = { version = "0.3.12", path = "webrtc-sys" } diff --git a/imgproc/CHANGELOG.md b/imgproc/CHANGELOG.md index 7dac518b8..03089bb69 100644 --- a/imgproc/CHANGELOG.md +++ b/imgproc/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.3.14](https://github.com/livekit/rust-sdks/compare/rust-sdks/imgproc@0.3.13...rust-sdks/imgproc@0.3.14) - 2025-09-29 + +### Other + +- updated the following local packages: yuv-sys + ## [0.3.13](https://github.com/livekit/rust-sdks/compare/rust-sdks/imgproc@0.3.12...rust-sdks/imgproc@0.3.13) - 2025-09-09 ### Other diff --git a/imgproc/Cargo.toml b/imgproc/Cargo.toml index c08caaf64..1c247458a 100644 --- a/imgproc/Cargo.toml +++ b/imgproc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "imgproc" -version = "0.3.13" +version = "0.3.14" edition = "2021" authors = ["Theo Monnom "] license = "MIT OR Apache-2.0" diff --git a/libwebrtc/CHANGELOG.md b/libwebrtc/CHANGELOG.md index c57fb1315..34788c0d6 100644 --- a/libwebrtc/CHANGELOG.md +++ b/libwebrtc/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.3.15](https://github.com/livekit/rust-sdks/compare/rust-sdks/libwebrtc@0.3.14...rust-sdks/libwebrtc@0.3.15) - 2025-09-29 + +### Fixed + +- fix Builds/E2E Tests CI. ([#715](https://github.com/livekit/rust-sdks/pull/715)) + ## [0.3.14](https://github.com/livekit/rust-sdks/compare/rust-sdks/libwebrtc@0.3.13...rust-sdks/libwebrtc@0.3.14) - 2025-09-09 ### Other diff --git a/libwebrtc/Cargo.toml b/libwebrtc/Cargo.toml index 06c5c5299..59df55c59 100644 --- a/libwebrtc/Cargo.toml +++ b/libwebrtc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libwebrtc" -version = "0.3.14" +version = "0.3.15" edition = "2021" homepage = "https://livekit.io" license = "Apache-2.0" diff --git a/livekit-api/CHANGELOG.md b/livekit-api/CHANGELOG.md index 64bcc0d93..71da43d1b 100644 --- a/livekit-api/CHANGELOG.md +++ b/livekit-api/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.4.7](https://github.com/livekit/rust-sdks/compare/rust-sdks/livekit-api@0.4.6...rust-sdks/livekit-api@0.4.7) - 2025-09-29 + +### Other + +- updated the following local packages: livekit-protocol + ## [0.4.6](https://github.com/livekit/rust-sdks/compare/rust-sdks/livekit-api@0.4.5...rust-sdks/livekit-api@0.4.6) - 2025-09-03 ### Other diff --git a/livekit-api/Cargo.toml b/livekit-api/Cargo.toml index 1caeb6069..a564ec48b 100644 --- a/livekit-api/Cargo.toml +++ b/livekit-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-api" -version = "0.4.6" +version = "0.4.7" license = "Apache-2.0" description = "Rust Server SDK for LiveKit" edition = "2021" diff --git a/livekit-ffi/CHANGELOG.md b/livekit-ffi/CHANGELOG.md index dddaa3a59..e3a990864 100644 --- a/livekit-ffi/CHANGELOG.md +++ b/livekit-ffi/CHANGELOG.md @@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.12.34](https://github.com/livekit/rust-sdks/compare/rust-sdks/livekit-ffi@0.12.33...rust-sdks/livekit-ffi@0.12.34) - 2025-09-29 + +### Fixed + +- apply original participant fields in data messages ([#709](https://github.com/livekit/rust-sdks/pull/709)) + +### Other + +- Add send_bytes method ([#691](https://github.com/livekit/rust-sdks/pull/691)) +- Implement Display and Error for RpcError ([#719](https://github.com/livekit/rust-sdks/pull/719)) +- Fix intermittently failing E2E reliability test ([#718](https://github.com/livekit/rust-sdks/pull/718)) +- Do not modify raw packets ([#714](https://github.com/livekit/rust-sdks/pull/714)) +- Disable opus red for e2ee enabled clients ([#706](https://github.com/livekit/rust-sdks/pull/706)) +- Upgrade protocol to v1.41.0 ([#703](https://github.com/livekit/rust-sdks/pull/703)) +- Upgrade libwebrtc to m137. ([#696](https://github.com/livekit/rust-sdks/pull/696)) + ## [0.12.33](https://github.com/livekit/rust-sdks/compare/rust-sdks/livekit-ffi@0.12.32...rust-sdks/livekit-ffi@0.12.33) - 2025-09-09 ### Other diff --git a/livekit-ffi/Cargo.toml b/livekit-ffi/Cargo.toml index dc56ae659..a6d2ef0e0 100644 --- a/livekit-ffi/Cargo.toml +++ b/livekit-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-ffi" -version = "0.12.33" +version = "0.12.34" edition = "2021" license = "Apache-2.0" description = "FFI interface for bindings in other languages" diff --git a/livekit-protocol/CHANGELOG.md b/livekit-protocol/CHANGELOG.md index 918790197..be49e50ce 100644 --- a/livekit-protocol/CHANGELOG.md +++ b/livekit-protocol/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [0.5.0](https://github.com/livekit/rust-sdks/compare/rust-sdks/livekit-protocol@0.4.0...rust-sdks/livekit-protocol@0.5.0) - 2025-09-29 + +### Other + +- Upgrade protocol to v1.41.0 ([#703](https://github.com/livekit/rust-sdks/pull/703)) + ## [0.4.0](https://github.com/livekit/rust-sdks/compare/rust-sdks/livekit-protocol@0.3.10...rust-sdks/livekit-protocol@0.4.0) - 2025-06-17 ### Other diff --git a/livekit-protocol/Cargo.toml b/livekit-protocol/Cargo.toml index 91c49ff72..6697fda22 100644 --- a/livekit-protocol/Cargo.toml +++ b/livekit-protocol/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit-protocol" -version = "0.4.0" +version = "0.5.0" edition = "2021" license = "Apache-2.0" description = "Livekit protocol and utilities for the Rust SDK" diff --git a/livekit/CHANGELOG.md b/livekit/CHANGELOG.md index e92dda36b..aa51753cd 100644 --- a/livekit/CHANGELOG.md +++ b/livekit/CHANGELOG.md @@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.7.19](https://github.com/livekit/rust-sdks/compare/rust-sdks/livekit@0.7.18...rust-sdks/livekit@0.7.19) - 2025-09-29 + +### Fixed + +- apply original participant fields in data messages ([#709](https://github.com/livekit/rust-sdks/pull/709)) + +### Other + +- Implement Display and Error for RpcError ([#719](https://github.com/livekit/rust-sdks/pull/719)) +- Fix intermittently failing E2E reliability test ([#718](https://github.com/livekit/rust-sdks/pull/718)) +- Do not modify raw packets ([#714](https://github.com/livekit/rust-sdks/pull/714)) +- Add send_bytes method ([#691](https://github.com/livekit/rust-sdks/pull/691)) +- Disable opus red for e2ee enabled clients ([#706](https://github.com/livekit/rust-sdks/pull/706)) +- Upgrade protocol to v1.41.0 ([#703](https://github.com/livekit/rust-sdks/pull/703)) + ## [0.7.18](https://github.com/livekit/rust-sdks/compare/rust-sdks/livekit@0.7.17...rust-sdks/livekit@0.7.18) - 2025-09-09 ### Other diff --git a/livekit/Cargo.toml b/livekit/Cargo.toml index 21d412fb2..41f7208e5 100644 --- a/livekit/Cargo.toml +++ b/livekit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "livekit" -version = "0.7.18" +version = "0.7.19" edition = "2021" license = "Apache-2.0" description = "Rust Client SDK for LiveKit" diff --git a/webrtc-sys/CHANGELOG.md b/webrtc-sys/CHANGELOG.md index 94feecc8b..58724355e 100644 --- a/webrtc-sys/CHANGELOG.md +++ b/webrtc-sys/CHANGELOG.md @@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.3.12](https://github.com/livekit/rust-sdks/compare/rust-sdks/webrtc-sys@0.3.11...rust-sdks/webrtc-sys@0.3.12) - 2025-09-29 + +### Fixed + +- fix Builds/E2E Tests CI. ([#715](https://github.com/livekit/rust-sdks/pull/715)) + +### Other + +- nvidia codec improve ([#721](https://github.com/livekit/rust-sdks/pull/721)) +- Upgrade libwebrtc to m137. ([#696](https://github.com/livekit/rust-sdks/pull/696)) + ## [0.3.11](https://github.com/livekit/rust-sdks/compare/rust-sdks/webrtc-sys@0.3.10...rust-sdks/webrtc-sys@0.3.11) - 2025-09-09 ### Other diff --git a/webrtc-sys/Cargo.toml b/webrtc-sys/Cargo.toml index 949759a3a..5585ee591 100644 --- a/webrtc-sys/Cargo.toml +++ b/webrtc-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "webrtc-sys" -version = "0.3.11" +version = "0.3.12" edition = "2021" homepage = "https://livekit.io" license = "Apache-2.0" diff --git a/webrtc-sys/build/CHANGELOG.md b/webrtc-sys/build/CHANGELOG.md index d1233689b..317f4914b 100644 --- a/webrtc-sys/build/CHANGELOG.md +++ b/webrtc-sys/build/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [0.3.8](https://github.com/livekit/rust-sdks/compare/rust-sdks/webrtc-sys-build@0.3.7...rust-sdks/webrtc-sys-build@0.3.8) - 2025-09-29 + +### Other + +- Upgrade libwebrtc to m137. ([#696](https://github.com/livekit/rust-sdks/pull/696)) + ## [0.3.7](https://github.com/livekit/rust-sdks/compare/rust-sdks/webrtc-sys-build@0.3.6...rust-sdks/webrtc-sys-build@0.3.7) - 2025-06-17 ### Fixed diff --git a/webrtc-sys/build/Cargo.toml b/webrtc-sys/build/Cargo.toml index 2039d6671..e2502ae82 100644 --- a/webrtc-sys/build/Cargo.toml +++ b/webrtc-sys/build/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "webrtc-sys-build" -version = "0.3.7" +version = "0.3.8" edition = "2021" license = "Apache-2.0" description = "Build utilities when working with libwebrtc" diff --git a/yuv-sys/CHANGELOG.md b/yuv-sys/CHANGELOG.md index 63fbb702a..a58694c08 100644 --- a/yuv-sys/CHANGELOG.md +++ b/yuv-sys/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.3.9](https://github.com/livekit/rust-sdks/compare/rust-sdks/yuv-sys@0.3.8...rust-sdks/yuv-sys@0.3.9) - 2025-09-29 + +### Other + +- Upgrade libwebrtc to m137. ([#696](https://github.com/livekit/rust-sdks/pull/696)) + ## [0.3.8](https://github.com/livekit/rust-sdks/compare/rust-sdks/yuv-sys@0.3.7...rust-sdks/yuv-sys@0.3.8) - 2025-09-09 ### Other diff --git a/yuv-sys/Cargo.toml b/yuv-sys/Cargo.toml index c5ce8e0e3..e99043b51 100644 --- a/yuv-sys/Cargo.toml +++ b/yuv-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yuv-sys" -version = "0.3.8" +version = "0.3.9" edition = "2021" authors = ["Theo Monnom "] license = "MIT OR Apache-2.0" From 562855909af96647af455c1d4e7dba98746c522c Mon Sep 17 00:00:00 2001 From: Pepe Date: Fri, 21 Feb 2025 13:03:36 +0100 Subject: [PATCH 256/274] [dn-changes] changes done by DN --- .../ffi-stage-template.yml | 123 ++++++++++++++ .../ffi-builds-templates/use-rust.yml | 18 ++ .azure/pipelines/ffi-builds.yml | 60 +++++++ .azure/pipelines/pipeline.yml | 36 ++++ .azure/pipelines/prepare-conan-profile.yml | 96 +++++++++++ .../webrtc-stage-template.yml | 160 ++++++++++++++++++ .azure/pipelines/webrtc-builds.yml | 24 +++ .github/workflows/ffi-builds.yml | 4 +- .gitignore | 2 +- Cargo.lock | 40 +++++ README.md | 88 ++++++++++ livekit-ffi/conan_assets/conanfile.py | 76 +++++++++ livekit-ffi/generate_conan_build.bat | 114 +++++++++++++ livekit-ffi/generate_conan_build.sh | 114 +++++++++++++ livekit-ffi/include/livekit_ffi.h | 3 + webrtc-sys/build/Cargo.toml | 5 +- webrtc-sys/build/src/lib.rs | 34 +++- webrtc-sys/libwebrtc/build_windows.cmd | 32 +++- webrtc-sys/src/android.cpp | 8 +- 19 files changed, 1019 insertions(+), 18 deletions(-) create mode 100644 .azure/pipelines/ffi-builds-templates/ffi-stage-template.yml create mode 100644 .azure/pipelines/ffi-builds-templates/use-rust.yml create mode 100644 .azure/pipelines/ffi-builds.yml create mode 100644 .azure/pipelines/pipeline.yml create mode 100644 .azure/pipelines/prepare-conan-profile.yml create mode 100644 .azure/pipelines/webrtc-builds-templates/webrtc-stage-template.yml create mode 100644 .azure/pipelines/webrtc-builds.yml create mode 100644 livekit-ffi/conan_assets/conanfile.py create mode 100644 livekit-ffi/generate_conan_build.bat create mode 100644 livekit-ffi/generate_conan_build.sh diff --git a/.azure/pipelines/ffi-builds-templates/ffi-stage-template.yml b/.azure/pipelines/ffi-builds-templates/ffi-stage-template.yml new file mode 100644 index 000000000..681a425af --- /dev/null +++ b/.azure/pipelines/ffi-builds-templates/ffi-stage-template.yml @@ -0,0 +1,123 @@ +parameters: + - name: stageName + type: string + - name: jobName + type: string + - name: os + type: string + - name: buildScript + type: string + - name: envVars + type: object + default: {} + - name: target + type: string + - name: platform + type: string + - name: name + type: string + - name: dylib + type: string + - name: jar + type: string + - name: pool_name + type: string + - name: artifact + type: string + - name: dependency + type: string + - name: arch + type: string + +stages: +- stage: ${{ parameters.stageName }} + dependsOn: ${{ parameters.dependency }} + jobs: + - job: ${{ parameters.jobName }} + pool: + name: ${{ parameters.pool_name }} + steps: + - task: DownloadPipelineArtifact@2 + inputs: + artifactName: ${{ parameters.artifact }} + targetPath: '$(Pipeline.Workspace)/webrtc-output' + displayName: 'DOWNLOAD | Download artifact' + + - template: 'use-rust.yml' + parameters: + rustup_toolchain: stable + rustup_target: ${{ parameters.target }} + + - task: Bash@3 + displayName: 'Set environment variables' + inputs: + targetType: 'inline' + script: | + echo "##vso[task.setvariable variable=CARGO_TERM_COLOR]always" + if [ "${AGENT_OS}" != "Windows_NT" ]; then + echo "##vso[task.setvariable variable=cargo_home]$HOME/.cargo" + echo "##vso[task.setvariable variable=rustup_home]$HOME/.rustup" + fi + + - script: ${{ parameters.buildScript }} + displayName: 'Build on ${{ parameters.stageName }}' + condition: and(succeeded(), ne( variables['Agent.OS'], 'Windows_NT' )) + env: + LK_CUSTOM_WEBRTC: '$(Pipeline.Workspace)/webrtc-output' + + - pwsh: ${{ parameters.buildScript }} + displayName: 'Build on ${{ parameters.stageName }}' + condition: and(succeeded(), eq( variables['Agent.OS'], 'Windows_NT' )) + env: + LK_CUSTOM_WEBRTC: '$(Pipeline.Workspace)/webrtc-output' + + - task: Bash@3 + displayName: 'Copy/Build licenses' + inputs: + targetType: 'inline' + script: | + echo "# livekit" > TEMP_LICENSE.md + echo "``` + cat LICENSE >> TEMP_LICENSE.md + echo "```" + cat livekit-ffi/WEBRTC_LICENSE.md >> TEMP_LICENSE.md + mkdir -p target/${{ parameters.target }}/release + mv TEMP_LICENSE.md target/${{ parameters.target }}/release/LICENSE.md + + - task: Bash@3 + displayName: 'Prepare conan package (Unix for Android)' + condition: and(succeeded(), eq('${{ parameters.platform }}', 'android')) + inputs: + targetType: 'inline' + script: | + cd target/${{ parameters.target }}/release/ + mkdir -p $(Pipeline.Workspace)/${{ parameters.name }}/lib/android/${{ parameters.arch }} + cp ${{ parameters.dylib }} ${{ parameters.jar }} $(Pipeline.Workspace)/${{ parameters.name }}/lib/android/${{ parameters.arch }} + + - task: PowerShell@2 + displayName: 'Zip artifact (Windows)' + condition: and(succeeded(), eq('${{ parameters.os }}', 'windows-latest')) + inputs: + targetType: 'inline' + script: | + Set-Location "target/${{ parameters.target }}/release/" + $null = New-Item -ItemType Directory -Path "$(Pipeline.Workspace)/${{ parameters.name }}/lib/windows" -Force + $files = @("${{ parameters.dylib }}", "${{ parameters.jar }}") | Where-Object { $_ -and (Test-Path $_) } + + if ($files.Count -gt 0) { + Copy-Item -Path $files -Destination "$(Pipeline.Workspace)/${{ parameters.name }}/lib/windows" + } + + - task: CopyFiles@2 + inputs: + sourceFolder: '$(Pipeline.Workspace)/${{ parameters.name }}/' + contents: '**' + targetFolder: $(Build.ArtifactStagingDirectory) + displayName: 'Copy results' + + - task: PublishPipelineArtifact@1 + inputs: + targetPath: '$(Build.ArtifactStagingDirectory)' + artifact: ${{ parameters.name }} + publishLocation: 'pipeline' + displayName: 'PUBLISH | Publish results' diff --git a/.azure/pipelines/ffi-builds-templates/use-rust.yml b/.azure/pipelines/ffi-builds-templates/use-rust.yml new file mode 100644 index 000000000..814b5bfca --- /dev/null +++ b/.azure/pipelines/ffi-builds-templates/use-rust.yml @@ -0,0 +1,18 @@ +parameters: + - name: rustup_toolchain + type: string + default: stable + - name: rustup_target + type: string + +steps: + - script: | + curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain ${{ parameters.rustup_toolchain }} + echo "##vso[task.prependpath]$HOME/.cargo/bin" + displayName: Install rust + condition: ne( variables['Agent.OS'], 'Windows_NT' ) + + + - script: | + rustup target add ${{ parameters.rustup_target }} + displayName: Verify Rust installation diff --git a/.azure/pipelines/ffi-builds.yml b/.azure/pipelines/ffi-builds.yml new file mode 100644 index 000000000..591a9fcda --- /dev/null +++ b/.azure/pipelines/ffi-builds.yml @@ -0,0 +1,60 @@ +stages: +- template: ffi-builds-templates/ffi-stage-template.yml + parameters: + dependency: webrtc_win_x64 + stageName: Build_Windows_x64 + jobName: Windows_x64 + os: windows-latest + target: x86_64-pc-windows-msvc + platform: windows + jar: '' + dylib: livekit_ffi.dll + name: ffi-windows-x86_64 + artifact: webrtc-win-x64-release + pool_name: agent-pool-windows2022-vmss + envVars: ${{ parameters.envVars }} + arch: '' + buildScript: | + cd livekit-ffi + cargo build --release --target x86_64-pc-windows-msvc + +- template: ffi-builds-templates/ffi-stage-template.yml + parameters: + dependency: webrtc_android_arm + stageName: Build_Android_ARMV7 + jobName: Android_ARMV7 + os: ubuntu-latest + target: armv7-linux-androideabi + platform: android + jar: libwebrtc.jar + dylib: liblivekit_ffi.so + name: ffi-android-armv7 + artifact: webrtc-android-arm-release + pool_name: agent-pool-ubuntu2204-vmss + envVars: ${{ parameters.envVars }} + arch: armeabi-v7a + buildScript: | + cd livekit-ffi/ + cargo install cargo-ndk + cargo ndk --bindgen --target armv7-linux-androideabi build --release --no-default-features --features "rustls-tls-webpki-roots" + +- template: ffi-builds-templates/ffi-stage-template.yml + parameters: + dependency: webrtc_android_arm64 + stageName: Build_Android_ARMV8 + jobName: Android_ARMV8 + os: ubuntu-latest + target: aarch64-linux-android + platform: android + jar: libwebrtc.jar + dylib: liblivekit_ffi.so + name: ffi-android-arm64 + artifact: webrtc-android-arm64-release + pool_name: agent-pool-ubuntu2204-vmss + envVars: ${{ parameters.envVars }} + arch: arm64-v8a + buildScript: | + cd livekit-ffi/ + cargo install cargo-ndk + cargo ndk --bindgen --target aarch64-linux-android build --release --no-default-features --features "rustls-tls-webpki-roots" + diff --git a/.azure/pipelines/pipeline.yml b/.azure/pipelines/pipeline.yml new file mode 100644 index 000000000..9b93f07ca --- /dev/null +++ b/.azure/pipelines/pipeline.yml @@ -0,0 +1,36 @@ +# Azure DevOps Pipeline + +trigger: + tags: + include: + - "*" + branches: + exclude: + - '*' + +resources: + repositories: + - repository: ci + type: github + endpoint: "forayo | PAT repo, admin:repo_hook" + name: DisplayNote/qt-conan-ci + ref: refs/tags/2.0.0 + +variables: + - name: python.version + value: '3.9' + - group: broadcast-environment-variables + +stages: + - template: common/setup.yml@ci + + - template: webrtc-builds.yml + + - template: ffi-builds.yml + + - template: prepare-conan-profile.yml + parameters: + dependencies: ['Build_Windows_x64', 'Build_Android_ARMV7', 'Build_Android_ARMV8'] + artifacts: ['ffi-android-arm64', 'ffi-android-armv7', 'ffi-windows-x86_64'] + packageName: livekit-ffi + conanProfiles: ['msvc19.x86_64', 'android.arm64-v8a', 'android.armeabi-v7a'] diff --git a/.azure/pipelines/prepare-conan-profile.yml b/.azure/pipelines/prepare-conan-profile.yml new file mode 100644 index 000000000..cfacd48e1 --- /dev/null +++ b/.azure/pipelines/prepare-conan-profile.yml @@ -0,0 +1,96 @@ +parameters: +- name: artifacts + type: object + default: [] +- name: packageName + type: string +- name: dependencies + type: object + default: [] +- name: conanProfiles + type: object + default: [] + +stages: + - stage: deploy_dn_develop + dependsOn: ${{ parameters.dependencies }} + displayName: 'DEPLOY | develop' + condition: succeeded() + jobs: + - deployment: CreatePackageDevelop + displayName: 'DEPLOY | develop' + pool: + #Using Microsoft-hosted agents + #vmImage: 'ubuntu-22.04' + #Using DN-hosted agent (Self hosted agent) + #name: az-self-hosted-ubuntu2204 + #Using DN pool snapshot based of Self hosted agent + name: agent-pool-ubuntu2204-vmss + environment: 'conan-libs-dn-develop' + strategy: + runOnce: + deploy: + steps: + + - checkout: self + clean: true + submodules: recursive + persistCredentials: true + + - ${{ each artifact in parameters.artifacts }}: + - task: DownloadPipelineArtifact@2 + inputs: + artifactName: ${{ artifact }} + targetPath: '$(Build.ArtifactStagingDirectory)' + displayName: 'Download artifact ${{ artifact }}' + + - template: common/python.yml@ci + + - template: common/conan/config.yml@ci + + - task: Bash@3 + displayName: 'Prepare conan package' + condition: succeeded() + inputs: + targetType: 'inline' + script: | + ls -l '$(Build.ArtifactStagingDirectory)' + cp $(Build.SourcesDirectory)/dn_livekit_ffi/livekit-ffi/conan_assets/conanfile.py '$(Build.ArtifactStagingDirectory)' + mkdir -p '$(Build.ArtifactStagingDirectory)/include' + cp $(Build.SourcesDirectory)/dn_livekit_ffi/livekit-ffi/include/livekit_ffi.h '$(Build.ArtifactStagingDirectory)/include/livekit_ffi.h' + + + - ${{ each profile in parameters.conanProfiles }}: + - template: common/conan/create.yml@ci + parameters: + channel: 'dn/develop' + conanfile: '$(Build.ArtifactStagingDirectory)/conanfile.py' + packageName: ${{ parameters.packageName }} + buildFolder: $(Build.ArtifactStagingDirectory) + profile: ${{ profile }} + buildType: 'debug' + version: $(Build.BuildNumber) + + - template: common/conan/create.yml@ci + parameters: + channel: 'dn/develop' + conanfile: '$(Build.ArtifactStagingDirectory)/conanfile.py' + packageName: ${{ parameters.packageName }} + buildFolder: $(Build.ArtifactStagingDirectory) + profile: ${{ profile }} + buildType: 'release' + version: $(Build.BuildNumber) + + - template: common/conan/upload.yml@ci + parameters: + channel: 'dn/develop' + packageName: ${{ parameters.packageName }} + version: $(Build.BuildNumber) + skipUpload: ${{ startsWith(variables['Build.SourceBranch'], 'refs/tags/') }} + + - task: PublishPipelineArtifact@1 + displayName: 'Publish conan package' + inputs: + targetPath: '$(Build.ArtifactStagingDirectory)' + artifact: 'conan-package' + publishLocation: 'pipeline' diff --git a/.azure/pipelines/webrtc-builds-templates/webrtc-stage-template.yml b/.azure/pipelines/webrtc-builds-templates/webrtc-stage-template.yml new file mode 100644 index 000000000..31b5b19ae --- /dev/null +++ b/.azure/pipelines/webrtc-builds-templates/webrtc-stage-template.yml @@ -0,0 +1,160 @@ +# Azure DevOps Pipeline +parameters: + - name: pool_name + type: string + - name: target_name + type: string + - name: arch + type: string + - name: os + type: string + - name: cmd + type: string + +stages: +- stage: webrtc_${{ parameters.target_name }}_${{ parameters.arch }} + displayName: Build WebRTC - ${{ parameters.target_name }}-${{ parameters.arch }} + dependsOn: [] + jobs: + - job: BuildJob + displayName: Build WebRTC + pool: + name: ${{ parameters.pool_name }} + steps: + - task: Bash@3 + displayName: Setup variables + inputs: + targetType: 'inline' + script: | + echo "Target Name: ${{ parameters.target_name }}" + echo "Architecture: ${{ parameters.arch }}" + DEFAULT_OUT=${{ parameters.target_name }}-${{ parameters.arch }} + OUT=$DEFAULT_OUT-release + echo "##vso[task.setvariable variable=OUT]$OUT" + + - task: Bash@3 + displayName: Info + inputs: + targetType: 'inline' + script: | + echo "OutName: $(OUT)" + + - task: CmdLine@2 + displayName: Info + condition: eq('${{ parameters.os }}', 'windows-latest') + inputs: + targetType: 'inline' + script: | + echo "OutName: $(OUT)" + + - task: UsePythonVersion@0 + displayName: Use Python $(python.version) + inputs: + versionSpec: '$(python.version)' + architecture: 'x64' + + - task: Bash@3 + displayName: Install setuptools + inputs: + targetType: 'inline' + script: pip3 install setuptools + + - task: Bash@3 + displayName: Install linux dependencies + condition: eq('${{ parameters.os }}', 'ubuntu-latest') + inputs: + targetType: 'inline' + script: | + sudo apt update -y + sudo apt install -y ninja-build pkg-config openjdk-11-jdk + + - task: Bash@3 + displayName: Install macos dependencies + condition: eq('${{ parameters.os }}', 'macos-13') + inputs: + targetType: 'inline' + script: brew install ninja + + - task: PowerShell@2 + displayName: Install windows dependencies + condition: eq('${{ parameters.os }}', 'windows-latest') + inputs: + targetType: 'inline' + script: | + Invoke-WebRequest -Uri "https://github.com/ninja-build/ninja/releases/latest/download/ninja-win.zip" -OutFile ninja.zip + Expand-Archive -Path ninja.zip -DestinationPath $(System.DefaultWorkingDirectory)\ninja + Write-Host "##vso[task.prependpath]$(System.DefaultWorkingDirectory)\ninja" + + - task: Bash@3 + displayName: Print ninja version + inputs: + targetType: 'inline' + script: ninja --version + + - checkout: self + submodules: true + + - task: Bash@3 + displayName: Target OS + inputs: + targetType: 'inline' + script: echo -e "\ntarget_os = [\"${{ parameters.target_name}}\"]" >> .gclient + workingDirectory: $(System.DefaultWorkingDirectory)/webrtc-sys/libwebrtc + + - task: Bash@3 + displayName: Build WebRTC (Unix) + condition: ne('${{ parameters.os }}', 'windows-latest') + inputs: + targetType: 'inline' + script: ${{ parameters.cmd }} --arch ${{ parameters.arch }} --profile release + workingDirectory: $(System.DefaultWorkingDirectory)/webrtc-sys/libwebrtc + + - task: CmdLine@2 + displayName: Build WebRTC (Windows) + condition: eq('${{ parameters.os }}', 'windows-latest') + inputs: + script: ${{ parameters.cmd }} --arch ${{ parameters.arch }} --profile release + workingDirectory: $(System.DefaultWorkingDirectory)\webrtc-sys\libwebrtc + + - task: Bash@3 + displayName: List directories + #condition: ne('${{ parameters.os }}', 'windows-latest') + inputs: + targetType: 'inline' + script: ls -l + workingDirectory: $(System.DefaultWorkingDirectory)/webrtc-sys/libwebrtc/$(OUT) + + # - task: PowerShell@2 + # displayName: Build WebRTC (Windows) + # condition: eq('${{ parameters.os }}', 'windows-latest') + # inputs: + # targetType: 'inline' + # script: | + # $env:PATH = "$(System.DefaultWorkingDirectory)\ninja;$env:PATH" + # cmd /c "${{ parameters.cmd }} --arch ${{ parameters.arch }} --profile release" + # workingDirectory: $(System.DefaultWorkingDirectory)/webrtc-sys/libwebrtc + + - task: CmdLine@2 + displayName: Debug | Check directory existence + condition: eq('${{ parameters.os }}', 'windows-latest') + inputs: + script: | + if exist "$(System.DefaultWorkingDirectory)\webrtc-sys\libwebrtc\$(OUT)" ( + echo "Directory exists: $(System.DefaultWorkingDirectory)\webrtc-sys\libwebrtc\$(OUT)" + ) else ( + echo "Directory does not exist: $(System.DefaultWorkingDirectory)\webrtc-sys\libwebrtc\$(OUT)" + ) + + - task: CopyFiles@2 + inputs: + sourceFolder: '$(System.DefaultWorkingDirectory)/webrtc-sys/libwebrtc/$(OUT)' + contents: '**/*' + targetFolder: $(Build.ArtifactStagingDirectory) + displayName: 'PUBLISH | Copy results' + + - task: PublishPipelineArtifact@1 + inputs: + targetPath: '$(Build.ArtifactStagingDirectory)' + artifact: webrtc-$(OUT) + publishLocation: 'pipeline' + displayName: 'PUBLISH | Publish results' diff --git a/.azure/pipelines/webrtc-builds.yml b/.azure/pipelines/webrtc-builds.yml new file mode 100644 index 000000000..711671df4 --- /dev/null +++ b/.azure/pipelines/webrtc-builds.yml @@ -0,0 +1,24 @@ +stages: +- template: webrtc-builds-templates/webrtc-stage-template.yml + parameters: + target_name: win + os: windows-latest + pool_name: agent-pool-windows2022-vmss + cmd: build_windows.cmd + arch: x64 + +- template: webrtc-builds-templates/webrtc-stage-template.yml + parameters: + target_name: android + os: ubuntu-latest + pool_name: agent-pool-ubuntu2204-vmss + cmd: ./build_android.sh + arch: arm + +- template: webrtc-builds-templates/webrtc-stage-template.yml + parameters: + target_name: android + os: ubuntu-latest + cmd: ./build_android.sh + pool_name: agent-pool-ubuntu2204-vmss + arch: arm64 diff --git a/.github/workflows/ffi-builds.yml b/.github/workflows/ffi-builds.yml index d7a7d0943..253df8220 100644 --- a/.github/workflows/ffi-builds.yml +++ b/.github/workflows/ffi-builds.yml @@ -182,14 +182,14 @@ jobs: - name: Zip artifact (Unix) if: ${{ matrix.os != 'windows-latest' && matrix.platform != 'android'}} run: | - cp livekit-ffi/include/livekit_ffi.h target/${{ matrix.target }}/release/ + cp livekit-ffi/include/livekit_ffi.h target/${{ matrix.target }}/release/ cd target/${{ matrix.target }}/release/ zip ${{ github.workspace }}/${{ matrix.name }}.zip ${{ matrix.dylib }} livekit_ffi.h LICENSE.md - name: Zip artifact (Unix for Android) if: ${{ matrix.os != 'windows-latest' && matrix.platform == 'android'}} run: | - cp livekit-ffi/include/livekit_ffi.h target/${{ matrix.target }}/release/ + cp livekit-ffi/include/livekit_ffi.h target/${{ matrix.target }}/release/ cd target/${{ matrix.target }}/release/ zip ${{ github.workspace }}/${{ matrix.name }}.zip ${{ matrix.dylib }} ${{ matrix.jar }} livekit_ffi.h LICENSE.md diff --git a/.gitignore b/.gitignore index b37534470..c258a2708 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,4 @@ target /.idea soxr-sys/test-input.wav soxr-sys/test-output.wav -.DS_Store \ No newline at end of file +.DS_Store diff --git a/Cargo.lock b/Cargo.lock index bb5756d6b..07ff1662d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -630,6 +630,15 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "copy_dir" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "543d1dd138ef086e2ff05e3a48cf9da045da2033d16f8538fd76b86cd49b2ca3" +dependencies = [ + "walkdir", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -956,6 +965,18 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +[[package]] +name = "filetime" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "windows-sys 0.52.0", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -1012,6 +1033,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "futures" version = "0.3.30" @@ -2887,6 +2914,17 @@ dependencies = [ "libc", ] +[[package]] +name = "tar" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c65998313f8e17d0d553d28f91a0df93e4dbbbf770279c7bc21ca0f09ea1a1f6" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "tempfile" version = "3.8.1" @@ -3518,10 +3556,12 @@ version = "0.3.8" dependencies = [ "anyhow", "fs2", + "fs_extra", "regex", "reqwest", "scratch", "semver", + "tar", "zip", ] diff --git a/README.md b/README.md index 6503e3b70..688cda532 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,91 @@ +# LiveKit-FFI Fork for WebRTC with livekit.org.webrtc Prefix + +This repository is a fork of [livekit-ffi](https://github.com/livekit/livekit-ffi) to enable the use of WebRTC with the `livekit.org.webrtc` prefix in the Android library. This allows integrating two different WebRTC libraries within the same project. + +## Cloning and Checking Out a Tag + +To get started, clone the repository and switch to the desired tag: + +```sh +git clone https://github.com/DisplayNote/dn_livekit_ffi.git +cd dn_livekit_ffi +git checkout +``` + +## Applying Changes + +To get new changes from upstream branch we must to: + +Create a new branch from `main` called `support/ffi-vx.xx.xx` in order to get the changes there. +```sh +git checkout -b support/ffi-v0.13.0 +``` + +Get all new changes from upstream: +```sh +git fetch upstream --tags +``` + +Do a rebase: +```sh +git rebase ffi-v0.13.0 +``` + +Then you must to solve conflicts, then, you must to compile, first webrtc and then ffi + +```sh +git rebase --apply +``` + +## Generating Conan Build Directory + +Once changes are applied and webrtc is built, generate the Conan build directory, which will contain the necessary files for uploading to Conan. + +To get the webrtc zip file generated you must to set the environ `LK_ARTIFACT_WEBRTC` where will be the path where the webrtc zip generated is located. + +### Windows: +```sh +generate_conan_build.bat --platform windows +generate_conan_build.bat --platform android +``` + +### Linux: +```sh +./generate_conan_build.sh --platform android +``` +**Note:** Windows builds cannot be compiled from Linux. + +After execution, a `livekit-ffi_conan` directory will be created at the root of the project, containing the necessary files for Conan package export and upload. +You must be to insert the correct version to upload in your `conanfile.py` + +## Exporting and Uploading to Conan + +To export the package for different profiles, execute the following command: + +```sh +conan export-pkg . livekit-ffi/0.7.2@dn/stable -pr android.arm64-v8a.debug -f && +conan export-pkg . livekit-ffi/0.7.2@dn/stable -pr android.arm64-v8a.release -f && +conan export-pkg . livekit-ffi/0.7.2@dn/stable -pr android.armeabi-v7a.debug -f && +conan export-pkg . livekit-ffi/0.7.2@dn/stable -pr android.armeabi-v7a.release -f && +conan export-pkg . livekit-ffi/0.7.2@dn/stable -pr msvc19.x86_64.debug -f && +conan export-pkg . livekit-ffi/0.7.2@dn/stable -pr msvc19.x86_64.release -f +``` + +Finally, upload the package to the Conan repository: + +```sh +conan upload livekit-ffi/0.7.2@dn/stable -r dn --all +``` + +Replace `0.7.2` with the appropriate tag version as needed. + +--- + +This fork ensures compatibility with projects requiring multiple WebRTC implementations while maintaining seamless integration with Conan package management. + +--- + + diff --git a/livekit-ffi/conan_assets/conanfile.py b/livekit-ffi/conan_assets/conanfile.py new file mode 100644 index 000000000..eede19f74 --- /dev/null +++ b/livekit-ffi/conan_assets/conanfile.py @@ -0,0 +1,76 @@ +from conans import ConanFile, CMake, tools + + +class LivekitFfiConan(ConanFile): + name = "livekit-ffi" + license = "None" + author = "jfjalburquerque" + url = "None" + description = "Livekit ffi package" + settings = "os", "compiler", "build_type", "arch" + options = {"shared": [True, False], "fPIC": [True, False]} + default_options = {"shared": False, "fPIC": True} + generators = "cmake" + + def getEnvs(self): + pass + + def package_info(self): + self.cpp_info.includedirs = ["include"] + + if self.settings.os == 'Android': + if self.settings.arch == 'armv7': + self.cpp_info.libdirs = ["lib/armeabi-v7a"] + elif self.settings.arch == 'armv8': + self.cpp_info.libdirs = ["lib/arm64-v8a"] + elif self.settings.os == "Windows": + self.cpp_info.libdirs = ["lib"] + else: + self.cpp_info.libdirs = ["lib"] + + self.cpp_info.libs = tools.collect_libs(self) + + def package(self): + self.copy("*", dst="include", src="include") + + if self.settings.os == "Android": + if self.settings.arch == "armv8": + lib_folder = "lib/android/arm64-v8a" + dst_lib_folder = "lib/arm64-v8a" + elif self.settings.arch == "armv7": + lib_folder = "lib/android/armeabi-v7a" + dst_lib_folder = "lib/armeabi-v7a" + + self.copy("*.a", dst=dst_lib_folder, src=lib_folder, keep_path=False) + self.copy("*.so", dst=dst_lib_folder, src=lib_folder, keep_path=False) + self.copy("*.jar", dst=dst_lib_folder, src=lib_folder, keep_path=False) + + if self.settings.os == "Windows": + lib_folder = "lib/windows" + dst_lib_folder = "lib" + self.copy("*.lib", dst=dst_lib_folder, src=lib_folder, keep_path=False) + self.copy("*.dll", dst=dst_lib_folder, src=lib_folder, keep_path=False) + else: + self.copy("*.a", dst="lib", src="lib", keep_path=False) + self.copy("*.so", dst="lib", src="lib", keep_path=False) + self.copy("*.dylib", dst="lib", src="lib", keep_path=False) + self.copy("*.lib", dst="lib", src="lib", keep_path=False) + self.copy("*.dll", dst="bin", src="bin", keep_path=False) + + + self.copy("LICENSE", dst="licenses", src=".") + + + # REQ VIA QMAKE, + #def requirements(self): + # qt_exact_requirements(self) + + def imports(self): + dest = os.getenv("CONAN_IMPORT_DEST_PATH", "bin") + self.copy("*", dst=dest, src="lib") + self.copy("*", dst="lib", src="lib") + self.copy("*", dst="include", src="include") + self.copy("*.qch", dst="doc", src="doc") + + def build(self): + pass diff --git a/livekit-ffi/generate_conan_build.bat b/livekit-ffi/generate_conan_build.bat new file mode 100644 index 000000000..d6cef0d4e --- /dev/null +++ b/livekit-ffi/generate_conan_build.bat @@ -0,0 +1,114 @@ +@echo off +setlocal enabledelayedexpansion + +echo Script started with arguments: %* + +:: Verificar si hay argumentos +if "%~1"=="" ( + echo Error: No arguments provided. + goto :usage +) + +:: Manejo de argumentos +set "platform=" +if /i "%~1"=="--platform" ( + if "%~2"=="" ( + echo Error: No platform specified. + goto :usage + ) + set "platform=%~2" +) else ( + echo Error: Invalid argument %~1 + goto :usage +) + +echo Selected platform: %platform% + +:: Guardar el directorio actual +set "initial_path=%CD%" +cd /d "%~dp0" + +:: Llamar a la función de compilación según la plataforma +if /i "%platform%"=="windows" ( + call :build_windows +) else if /i "%platform%"=="android" ( + call :build_android +) else ( + echo Error: Invalid platform %platform%. + goto :usage +) + +:: Volver al directorio inicial +cd /d "%initial_path%" +exit /b 0 + +:: Función de ayuda +:usage +echo Usage: %~nx0 --platform ^ +echo Example: %~nx0 --platform windows +exit /b 1 + +:: Función para compilar en Windows +:build_windows +echo Building for Windows... +cargo clean +cargo build --release --target x86_64-pc-windows-msvc +call :create_folder_structure "x86_64-pc-windows-msvc" +exit /b 0 + +:: Función para compilar en Android +:build_android +echo Building for Android... +if not defined ANDROID_NDK_HOME ( + echo Error: ANDROID_NDK_HOME is not set. Please set it before running the script. + exit /b 1 +) + +:: Build armv8 (arm64) +cargo clean +cargo ndk --bindgen --target aarch64-linux-android build --release --no-default-features --features "rustls-tls-webpki-roots" +call :create_folder_structure "aarch64-linux-android" + +:: Build armv7 (32-bit) +cargo clean +cargo ndk --bindgen --target armv7-linux-androideabi build --release --no-default-features --features "rustls-tls-webpki-roots" +call :create_folder_structure "armv7-linux-androideabi" +exit /b 0 + +:: Función para crear la estructura de carpetas +:create_folder_structure +set "target=%~1" + +if "%target%"=="" ( + echo Error: Target not specified. + exit /b 1 +) + +set "conan_dir=..\livekit-ffi_conan" +if not exist "%conan_dir%\lib" mkdir "%conan_dir%\lib" +if not exist "%conan_dir%\include" mkdir "%conan_dir%\include" + +set "conan_assets_path=%~dp0conan_assets" +copy "%conan_assets_path%\conanfile.py" "%conan_dir%\conanfile.py" >nul + +if "%target%"=="x86_64-pc-windows-msvc" ( + if not exist "%conan_dir%\lib\windows" mkdir "%conan_dir%\lib\windows" + copy "..\target\%target%\release\livekit_ffi.dll" "%conan_dir%\lib\windows\" >nul + copy "..\target\%target%\release\livekit_ffi.dll.lib" "%conan_dir%\lib\windows\livekit_ffi.lib" >nul +) else if "%target%"=="aarch64-linux-android" ( + if not exist "%conan_dir%\lib\android\arm64-v8a" mkdir "%conan_dir%\lib\android\arm64-v8a" + copy "..\target\%target%\release\liblivekit_ffi.so" "%conan_dir%\lib\android\arm64-v8a\" >nul + copy "..\target\%target%\release\libwebrtc.jar" "%conan_dir%\lib\android\arm64-v8a\" >nul +) else if "%target%"=="armv7-linux-androideabi" ( + if not exist "%conan_dir%\lib\android\armeabi-v7a" mkdir "%conan_dir%\lib\android\armeabi-v7a" + copy "..\target\%target%\release\liblivekit_ffi.so" "%conan_dir%\lib\android\armeabi-v7a\" >nul + copy "..\target\%target%\release\libwebrtc.jar" "%conan_dir%\lib\android\armeabi-v7a\" >nul +) else ( + echo Error: Unrecognized target: %target% + exit /b 1 +) + +copy "%~dp0include\livekit_ffi.h" "%conan_dir%\include\" >nul + +echo Folder structure created and files copied successfully for %target%. +exit /b 0 diff --git a/livekit-ffi/generate_conan_build.sh b/livekit-ffi/generate_conan_build.sh new file mode 100644 index 000000000..a5bb0f996 --- /dev/null +++ b/livekit-ffi/generate_conan_build.sh @@ -0,0 +1,114 @@ +#!/bin/bash + +set -e # Exit immediately on error + +usage() { + echo "Usage: $0 --platform " + echo "Example: $0 --platform windows" + exit 1 +} + +build_windows() { + cargo clean + cargo build --release --target x86_64-pc-windows-msvc + create_folder_structure "x86_64-pc-windows-msvc" +} + +build_android() { + if [ -z "$ANDROID_NDK_HOME" ]; then + echo "Error: ANDROID_NDK_HOME is not set. Please set it before running the script." + exit 1 + fi + + ln -sf "$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/lib/aarch64-unknown-linux-musl/{libunwind.so,libc++abi.a}" \ + "$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/lib/" + + # Build armv8 (arm64) + cargo clean + cargo ndk --bindgen --target aarch64-linux-android build --release --no-default-features --features "rustls-tls-webpki-roots" + create_folder_structure "aarch64-linux-android" + + # Build armv7 (32-bit) + cargo clean + cargo ndk --bindgen --target armv7-linux-androideabi build --release --no-default-features --features "rustls-tls-webpki-roots" + create_folder_structure "armv7-linux-androideabi" +} + +create_folder_structure() { + local target=$1 + + if [ -z "$target" ]; then + echo "Error: Target not specified" + return 1 + fi + + local conan_dir="../livekit-ffi_conan" + mkdir -p "$conan_dir/lib" "$conan_dir/include" + + local conan_assets_path="$script_path/conan_assets" + cp "$conan_assets_path/conanfile.py" "$conan_dir/conanfile.py" + + case "$target" in + x86_64-pc-windows-msvc) + mkdir -p "$conan_dir/lib/windows" + cp "../target/$target/release/livekit_ffi.dll" "$conan_dir/lib/windows/" + cp "../target/$target/release/livekit_ffi.dll.lib" "$conan_dir/lib/windows/livekit_ffi.lib" + ;; + aarch64-linux-android) + mkdir -p "$conan_dir/lib/android/arm64-v8a" + cp "../target/$target/release/liblivekit_ffi.so" "$conan_dir/lib/android/arm64-v8a/" + cp "../target/$target/release/libwebrtc.jar" "$conan_dir/lib/android/arm64-v8a/" + ;; + armv7-linux-androideabi) + mkdir -p "$conan_dir/lib/android/armeabi-v7a" + cp "../target/$target/release/liblivekit_ffi.so" "$conan_dir/lib/android/armeabi-v7a/" + cp "../target/$target/release/libwebrtc.jar" "$conan_dir/lib/android/armeabi-v7a/" + ;; + *) + echo "Error: Unrecognized target: $target" + return 1 + ;; + esac + + cp "$script_path/include/livekit_ffi.h" "$conan_dir/include/" + + echo "Folder structure created and files copied successfully for $target." +} + +main() { + if [ $# -ne 2 ]; then usage; fi + + local platform="" + while [[ $# -gt 0 ]]; do + case $1 in + --platform) + platform="$2" + shift 2 + ;; + *) + usage + ;; + esac + done + + local initial_path=$(pwd) + local script_path=$(dirname "$(readlink -f "$0")") + + cd "$script_path" + + case $platform in + windows) + build_windows + ;; + android) + build_android + ;; + *) + usage + ;; + esac + + cd "$initial_path" +} + +main "$@" diff --git a/livekit-ffi/include/livekit_ffi.h b/livekit-ffi/include/livekit_ffi.h index 1d2c86b96..5ad11ee66 100644 --- a/livekit-ffi/include/livekit_ffi.h +++ b/livekit-ffi/include/livekit_ffi.h @@ -11,11 +11,14 @@ #include using FfiHandleId = uint64_t; +using FfiCallbackFn = void (*)(const uint8_t*, size_t); constexpr static const FfiHandleId INVALID_HANDLE = 0; extern "C" { +void livekit_ffi_initialize(FfiCallbackFn cb, bool capture_logs); + FfiHandleId livekit_ffi_request(const uint8_t *data, size_t len, const uint8_t **res_ptr, size_t *res_len); diff --git a/webrtc-sys/build/Cargo.toml b/webrtc-sys/build/Cargo.toml index e2502ae82..54ac495ae 100644 --- a/webrtc-sys/build/Cargo.toml +++ b/webrtc-sys/build/Cargo.toml @@ -7,7 +7,10 @@ description = "Build utilities when working with libwebrtc" repository = "https://github.com/livekit/client-sdk-rust" [dependencies] -reqwest = { version = "0.11", default-features = false, features = ["rustls-tls-native-roots", "blocking"] } +reqwest = { version = "0.11", default-features = false, features = [ + "rustls-tls-native-roots", + "blocking", +] } zip = "0.6" regex = "1.0" scratch = "1.0" diff --git a/webrtc-sys/build/src/lib.rs b/webrtc-sys/build/src/lib.rs index 6b858ef2c..2657d83f3 100644 --- a/webrtc-sys/build/src/lib.rs +++ b/webrtc-sys/build/src/lib.rs @@ -22,9 +22,11 @@ use std::{ }; use anyhow::{anyhow, Context, Result}; +use flate2::read::GzDecoder; use fs2::FileExt; use regex::Regex; use reqwest::StatusCode; +use tar::Archive; pub const SCRATH_PATH: &str = "livekit_webrtc"; pub const WEBRTC_TAG: &str = "webrtc-f4967ef"; @@ -60,6 +62,16 @@ pub fn target_arch() -> String { .to_owned() } +pub fn target_prefixed_arch() -> String { + let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap(); + match target_arch.as_str() { + "arm" => "armeabi-v7a", + "aarch64" => "arm64-v8a", + _ => &target_arch, + } + .to_owned() +} + /// The full name of the webrtc library /// e.g. mac-x64-release (Same name on GH releases) pub fn webrtc_triple() -> String { @@ -83,6 +95,14 @@ pub fn custom_dir() -> Option { None } +/// The location of the artifact build is defined by the user +pub fn artifact_dir() -> Option { + if let Ok(path) = env::var("LK_ARTIFACT_WEBRTC") { + return Some(path::PathBuf::from(path)); + } + None +} + /// Location of the downloaded webrtc binaries /// The reason why we don't use OUT_DIR is because we sometimes need to share the same binaries /// across multiple crates without dependencies constraints @@ -111,12 +131,22 @@ pub fn webrtc_dir() -> path::PathBuf { return path; } - prebuilt_dir() + return prebuilt_dir(); } pub fn webrtc_defines() -> Vec<(String, Option)> { // read preprocessor definitions from webrtc.ninja let defines_re = Regex::new(r"-D(\w+)(?:=([^\s]+))?").unwrap(); + + let webrtc_path = webrtc_dir(); + if let Ok(entries) = fs::read_dir(&webrtc_path) { + for entry in entries.flatten() { + if let Ok(file_name) = entry.file_name().into_string() { + println!("- {}", file_name); + } + } + } + let webrtc_gni = fs::File::open(webrtc_dir().join("webrtc.ninja")).unwrap(); let mut defines_line = String::default(); @@ -154,7 +184,7 @@ pub fn configure_jni_symbols() -> Result<()> { .output() .expect("failed to run llvm-readelf"); - let jni_regex = Regex::new(r"(Java_org_webrtc.*)").unwrap(); + let jni_regex = Regex::new(r"(Java_livekit_org_webrtc.*)").unwrap(); let content = String::from_utf8_lossy(&readelf_output.stdout); let jni_symbols: Vec<&str> = jni_regex.captures_iter(&content).map(|cap| cap.get(1).unwrap().as_str()).collect(); diff --git a/webrtc-sys/libwebrtc/build_windows.cmd b/webrtc-sys/libwebrtc/build_windows.cmd index 1de0d83a1..7ab6c38df 100644 --- a/webrtc-sys/libwebrtc/build_windows.cmd +++ b/webrtc-sys/libwebrtc/build_windows.cmd @@ -1,5 +1,4 @@ -@echo off - +@echo on setlocal enabledelayedexpansion set arch= @@ -33,6 +32,7 @@ echo "Arch: !arch!" echo "Profile: !profile!" if not exist depot_tools ( + echo "Cloning depot_tools..." git clone --depth 1 https://chromium.googlesource.com/chromium/tools/depot_tools.git ) @@ -50,6 +50,8 @@ if not exist src ( ) cd src +@echo on +echo "Applying patches..." call git apply "%COMMAND_DIR%/patches/add_licenses.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn call git apply "%COMMAND_DIR%/patches/add_deps.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn call git apply "%COMMAND_DIR%/patches/windows_silence_warnings.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn @@ -57,6 +59,8 @@ call git apply "%COMMAND_DIR%/patches/ssl_verify_callback_with_native_handle.pat cd .. +@echo on +echo "Creating artifacts directory..." mkdir "%ARTIFACTS_DIR%\lib" set "debug=false" @@ -64,24 +68,36 @@ if "!profile!" == "debug" ( set "debug=true" ) -rem generate ninja for release +@echo on +echo "Generating ninja build files..." call gn.bat gen %OUTPUT_DIR% --root="src" ^ --args="is_debug=!debug! is_clang=true target_cpu=\"!arch!\" use_custom_libcxx=false rtc_libvpx_build_vp9=true enable_libaom=true rtc_include_tests=false rtc_build_examples=false rtc_build_tools=false is_component_build=false rtc_enable_protobuf=false rtc_use_h264=true ffmpeg_branding=\"Chrome\" symbol_level=0 enable_iterator_debugging=false" -rem build -ninja.exe -C %OUTPUT_DIR% :default +@echo on +echo "Building with ninja..." +call ninja -C %OUTPUT_DIR% :default + +REM Verificar el código de salida de ninja +if errorlevel 1 ( + echo "Error: Ninja build failed with exit code %errorlevel%." + exit /b %errorlevel% +) -rem copy static library for release build +@echo on +echo "Copying static library..." copy "%OUTPUT_DIR%\obj\webrtc.lib" "%ARTIFACTS_DIR%\lib" -rem generate license +echo "Generating license..." call python3 "%cd%\src\tools_webrtc\libs\generate_licenses.py" ^ --target :default %OUTPUT_DIR% %OUTPUT_DIR% +@echo on +echo "Copying additional files..." copy "%OUTPUT_DIR%\obj\webrtc.ninja" "%ARTIFACTS_DIR%" copy "%OUTPUT_DIR%\args.gn" "%ARTIFACTS_DIR%" copy "%OUTPUT_DIR%\LICENSE.md" "%ARTIFACTS_DIR%" -rem copy header +@echo on +echo "Copying headers..." xcopy src\*.h "%ARTIFACTS_DIR%\include" /C /S /I /F /H xcopy src\*.inc "%ARTIFACTS_DIR%\include" /C /S /I /F /H diff --git a/webrtc-sys/src/android.cpp b/webrtc-sys/src/android.cpp index 30ca78dd5..ed9052870 100644 --- a/webrtc-sys/src/android.cpp +++ b/webrtc-sys/src/android.cpp @@ -37,10 +37,10 @@ std::unique_ptr CreateAndroidVideoEncoderFactory() { JNIEnv* env = webrtc::AttachCurrentThreadIfNeeded(); webrtc::ScopedJavaLocalRef factory_class = - webrtc::GetClass(env, "org/webrtc/DefaultVideoEncoderFactory"); + webrtc::GetClass(env, "livekit/org/webrtc/DefaultVideoEncoderFactory"); jmethodID ctor = env->GetMethodID(factory_class.obj(), "", - "(Lorg/webrtc/EglBase$Context;ZZ)V"); + "(Llivekit/org/webrtc/EglBase$Context;ZZ)V"); jobject encoder_factory = env->NewObject(factory_class.obj(), ctor, nullptr, true, false); @@ -53,10 +53,10 @@ CreateAndroidVideoDecoderFactory() { JNIEnv* env = webrtc::AttachCurrentThreadIfNeeded(); webrtc::ScopedJavaLocalRef factory_class = - webrtc::GetClass(env, "org/webrtc/WrappedVideoDecoderFactory"); + webrtc::GetClass(env, "livekit/org/webrtc/WrappedVideoDecoderFactory"); jmethodID ctor = env->GetMethodID(factory_class.obj(), "", - "(Lorg/webrtc/EglBase$Context;)V"); + "(Llivekit/org/webrtc/EglBase$Context;)V"); jobject decoder_factory = env->NewObject(factory_class.obj(), ctor, nullptr); return webrtc::JavaToNativeVideoDecoderFactory(env, decoder_factory); From 76760e6e714f95c159afeb1b0c206cab5919b6dc Mon Sep 17 00:00:00 2001 From: Francisco Nadales Date: Thu, 2 Oct 2025 11:30:59 +0200 Subject: [PATCH 257/274] update android_build to repackage to livekit.org.webrtc --- webrtc-sys/libwebrtc/build_android.sh | 388 +++++++++++++++++++++++--- 1 file changed, 346 insertions(+), 42 deletions(-) diff --git a/webrtc-sys/libwebrtc/build_android.sh b/webrtc-sys/libwebrtc/build_android.sh index 1222cfdfb..bfb90be1e 100755 --- a/webrtc-sys/libwebrtc/build_android.sh +++ b/webrtc-sys/libwebrtc/build_android.sh @@ -1,18 +1,8 @@ -#!/bin/bash -# Copyright 2023 LiveKit, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +#!/usr/bin/env bash +# LiveKit WebRTC Android build with pre-build package migration +# Rewrites org.webrtc -> livekit.org.webrtc across sources prior to build. +set -euo pipefail arch="" profile="release" @@ -22,7 +12,7 @@ while [ "$#" -gt 0 ]; do --arch) arch="$2" if [ "$arch" != "arm" ] && [ "$arch" != "x64" ] && [ "$arch" != "arm64" ]; then - echo "Error: Invalid value for --arch. Must be 'arm', 'x64' or 'arm64'." + echo "Error: Invalid value for --arch. Must be 'arm', 'x64' or 'arm64'." >&2 exit 1 fi shift 2 @@ -30,20 +20,20 @@ while [ "$#" -gt 0 ]; do --profile) profile="$2" if [ "$profile" != "debug" ] && [ "$profile" != "release" ]; then - echo "Error: Invalid value for --profile. Must be 'debug' or 'release'." + echo "Error: Invalid value for --profile. Must be 'debug' or 'release'." >&2 exit 1 fi shift 2 ;; *) - echo "Error: Unknown argument '$1'" + echo "Error: Unknown argument '$1'" >&2 exit 1 ;; esac done if [ -z "$arch" ]; then - echo "Error: --arch must be set." + echo "Error: --arch must be set." >&2 exit 1 fi @@ -51,29 +41,334 @@ echo "Building LiveKit WebRTC - Android" echo "Arch: $arch" echo "Profile: $profile" -if [ ! -e "$(pwd)/depot_tools" ] -then +# --- fetch depot_tools --- +if [ ! -e "$(pwd)/depot_tools" ]; then git clone --depth 1 https://chromium.googlesource.com/chromium/tools/depot_tools.git fi - -export COMMAND_DIR=$(cd $(dirname $0); pwd) +export COMMAND_DIR="$(cd "$(dirname "$0")"; pwd)" export PATH="$(pwd)/depot_tools:$PATH" export OUTPUT_DIR="$(pwd)/src/out-$arch-$profile" export ARTIFACTS_DIR="$(pwd)/android-$arch-$profile" -if [ ! -e "$(pwd)/src" ] -then - gclient sync -D --no-history +# --- fetch src (WebRTC) --- +if [ ! -e "$(pwd)/src" ]; then + echo "Setting up WebRTC Android checkout..." + gclient sync +else + echo "WebRTC checkout already exists, syncing dependencies..." + + # --- reset src and subrepos to clean state --- + echo "Resetting WebRTC source to clean state..." + + # Reset main src directory + pushd src >/dev/null + echo " Resetting main src repo..." + git reset --hard HEAD + git clean -fd + + # Reset all submodules/subrepos in src + echo " Resetting src submodules..." + git submodule foreach --recursive 'git reset --hard HEAD && git clean -fd' || true + popd >/dev/null + + # Reset build directory if it exists and is a git repo + if [[ -d "src/build" && -d "src/build/.git" ]]; then + echo " Resetting build directory..." + pushd src/build >/dev/null + git reset --hard HEAD + git clean -fd + git submodule foreach --recursive 'git reset --hard HEAD && git clean -fd' || true + popd >/dev/null + fi + + # Reset buildtools directory if it exists and is a git repo + if [[ -d "src/buildtools" && -d "src/buildtools/.git" ]]; then + echo " Resetting buildtools directory..." + pushd src/buildtools >/dev/null + git reset --hard HEAD + git clean -fd + git submodule foreach --recursive 'git reset --hard HEAD && git clean -fd' || true + popd >/dev/null + fi + + # Reset third_party directories that are commonly git repos + for third_party_dir in "src/third_party/libc++" "src/third_party/libc++abi" "src/third_party/libunwind"; do + if [[ -d "$third_party_dir" && -d "$third_party_dir/.git" ]]; then + echo " Resetting $(basename "$third_party_dir") in third_party..." + pushd "$third_party_dir" >/dev/null + git reset --hard HEAD + git clean -fd + popd >/dev/null + fi + done + + echo "WebRTC source reset complete." fi -cd src -# git apply "$COMMAND_DIR/patches/add_licenses.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn -git apply "$COMMAND_DIR/patches/ssl_verify_callback_with_native_handle.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn -git apply "$COMMAND_DIR/patches/add_deps.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn -git apply "$COMMAND_DIR/patches/android_use_libunwind.patch" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn +# Ensure Android SDK is properly set up +echo "Setting up Android SDK environment..." +if [[ -z "${ANDROID_HOME:-}" && -z "${ANDROID_SDK_ROOT:-}" ]]; then + if [[ -d "$HOME/Android/Sdk" ]]; then + export ANDROID_HOME="$HOME/Android/Sdk" + export ANDROID_SDK_ROOT="$ANDROID_HOME" + echo " Detected Android SDK at $ANDROID_HOME" + else + echo "Error: ANDROID_HOME or ANDROID_SDK_ROOT is not set, and default location not found." >&2 + exit 1 + fi +else + echo " Using existing Android SDK environment variables." +fi + +# --- apply patches (if any) --- +echo "Applying patches..." +pushd src >/dev/null + +# List of patches to apply +patches=( + # "add_licenses.patch" + "ssl_verify_callback_with_native_handle.patch" + "add_deps.patch" + "android_use_libunwind.patch" +) + +patch_failed=false +for patch in "${patches[@]}"; do + patch_file="$COMMAND_DIR/patches/$patch" + if [[ -f "$patch_file" ]]; then + echo "Applying patch: $patch" + if git apply "$patch_file" -v --ignore-space-change --ignore-whitespace --whitespace=nowarn; then + echo " ✓ Successfully applied: $patch" + else + echo " ✗ Failed to apply: $patch" + echo " Attempting to apply with 3-way merge..." + if git apply "$patch_file" --3way --ignore-space-change --ignore-whitespace --whitespace=nowarn; then + echo " ✓ Successfully applied with 3-way merge: $patch" + else + echo " ✗ Failed to apply even with 3-way merge: $patch" + echo " WARNING: Continuing without this patch - build may fail" + patch_failed=true + fi + fi + else + echo " ⚠ Patch file not found: $patch_file" + fi +done + +if [[ "$patch_failed" == "true" ]]; then + echo "WARNING: Some patches failed to apply. The build may not work correctly." + echo "You may need to update the patches to match the current WebRTC version." +fi + +popd >/dev/null +echo "Patch application complete." + +# --- migrate org.webrtc -> livekit.org.webrtc before build --- +migrate_webrtc() { + local root="$1" + if [[ ! -d "$root" ]]; then + echo "migrate_webrtc: '$root' is not a directory" >&2 + return 1 + fi + echo "Running migrate_webrtc in: $root" + + # WebRTC-related folders to process (avoids unnecessary processing of unrelated code) + local webrtc_folders=( + "sdk" + "api" + "modules/audio_device/android" + "modules/audio_coding/audio_network_adaptor" + "modules/video_capture/android" + "modules/video_coding/codecs/h264/android" + "modules/video_coding/codecs/vp8/android" + "modules/video_coding/codecs/vp9/android" + "rtc_base" + "examples/android*" + "test/android" + ) + + # File types to process + local exts=( + "*.java" "*.kt" "*.xml" "*.gn" "*.gni" "*.proto" + "*.c" "*.cc" "*.cpp" "*.cxx" "*.hpp" "*.hh" "*.h" "*.hxx" + "*.m" "*.mm" "*.gradle" "*.properties" "*.mk" "*.cmake" + ) + + local find_expr=() + for i in "${!exts[@]}"; do + if [[ $i -gt 0 ]]; then find_expr+=(-o); fi + find_expr+=(-name "${exts[$i]}") + done -cd .. + local changed=0 + local total_files=0 + # Helper function to process a single folder + process_folder() { + local folder_path="$1" + local folder_files=0 + + echo " Starting to process folder: $folder_path" + + # Check if folder exists and is accessible + if [[ ! -d "$folder_path" ]]; then + echo " Error: Folder does not exist: $folder_path" + return 1 + fi + + # Create find command and test it first + local find_cmd="find \"$folder_path\" -type f \\( ${find_expr[*]} \\) -print0 2>/dev/null" + echo " Find command: $find_cmd" + + # Use safer approach without process substitution + local temp_list + temp_list="$(mktemp)" + + if ! find "$folder_path" -type f \( "${find_expr[@]}" \) -print0 2>/dev/null > "$temp_list"; then + echo " Warning: Find command failed for $folder_path" + rm -f "$temp_list" + return 0 + fi + + # Process files from the temp list + while IFS= read -r -d '' file; do + if [[ -z "$file" ]]; then + continue + fi + + local tmp + tmp="$(mktemp)" + if ! cp "$file" "$tmp" 2>/dev/null; then + echo " Warning: Failed to copy $file, skipping" + rm -f "$tmp" + continue + fi + + ((total_files++)) + ((folder_files++)) + + # Dot-notation and slash-notation replacements with double-prefix protection + if ! perl -0777 -i -pe ' + s/(?/dev/null; then + echo " Warning: Failed to process $file with perl, skipping" + rm -f "$tmp" + continue + fi + + + if ! cmp -s "$file" "$tmp" 2>/dev/null; then + echo " updated: $file" + ((changed++)) + fi + rm -f "$tmp" + done < "$temp_list" + + rm -f "$temp_list" + echo " processed $folder_files files in $(basename "$folder_path")" + return 0 + } + + # Process each WebRTC-related folder + for folder in "${webrtc_folders[@]}"; do + echo " Checking folder: $folder" + + # Handle wildcard patterns by expanding them + if [[ "$folder" == *"*"* ]]; then + echo " Expanding wildcard pattern: $folder" + # Use shell globbing for wildcard patterns + local found_folders=() + if pushd "$root" >/dev/null 2>&1; then + shopt -s nullglob # Enable nullglob to handle no matches + for expanded_folder in $folder; do + if [[ -d "$expanded_folder" ]]; then + found_folders+=("$expanded_folder") + echo " Found: $expanded_folder" + fi + done + shopt -u nullglob # Disable nullglob + popd >/dev/null 2>&1 + else + echo " Warning: Failed to change to directory $root" + continue + fi + + # Process each expanded folder + if [[ ${#found_folders[@]} -eq 0 ]]; then + echo " No folders found matching pattern: $folder" + else + for expanded_folder in "${found_folders[@]}"; do + local folder_path="$root/$expanded_folder" + echo " Processing folder: $expanded_folder" + if ! process_folder "$folder_path"; then + echo " Warning: Failed to process folder $expanded_folder" + fi + done + fi + else + # Handle regular folder paths + local folder_path="$root/$folder" + if [[ -d "$folder_path" ]]; then + echo " Processing folder: $folder" + if ! process_folder "$folder_path"; then + echo " Warning: Failed to process folder $folder" + fi + else + echo " Skipping non-existent folder: $folder" + fi + fi + done + + echo "migrate_webrtc: processed $total_files files, updated $changed files" +} + +migrate_webrtc "$(pwd)/src" + +# --- create new package structure for migrated files --- +echo "Creating new package directory structure..." +pushd src >/dev/null + +# Find all directories containing org/webrtc structure +find . -type d -path "*/org/webrtc" | while read -r webrtc_dir; do + # Get the parent directory (e.g., ./sdk/android/src/java) + parent_dir=$(dirname "$(dirname "$webrtc_dir")") + + # Create the new livekit/org/webrtc structure + new_dir="$parent_dir/livekit/org/webrtc" + if [[ ! -d "$new_dir" ]]; then + echo " Creating directory: $new_dir" + mkdir -p "$new_dir" + + # Move all files and subdirectories from org/webrtc to livekit/org/webrtc + if [[ -d "$webrtc_dir" ]]; then + echo " Moving contents from $webrtc_dir to $new_dir" + mv "$webrtc_dir"/* "$new_dir/" 2>/dev/null || true + + # Remove the now-empty org/webrtc directory + rmdir "$webrtc_dir" 2>/dev/null || true + + # Clean up empty parent directories if they exist + org_dir=$(dirname "$webrtc_dir") + if [[ -d "$org_dir" && -z "$(ls -A "$org_dir" 2>/dev/null)" ]]; then + rmdir "$org_dir" 2>/dev/null || true + fi + fi + fi +done + +popd >/dev/null +echo "Package structure creation complete." + +# --- build setup --- mkdir -p "$ARTIFACTS_DIR/lib" debug="false" @@ -100,33 +395,42 @@ args="is_debug=$debug \ enable_iterator_debugging=false \ use_rtti=true" -if [ "$debug" = "true" ]; then - args="${args} is_asan=true is_lsan=true"; -fi +# if [ "$debug" = "true" ]; then +# args="${args} is_asan=true is_lsan=true" +# fi -# generate ninja files +# --- GN gen --- gn gen "$OUTPUT_DIR" --root="src" --args="${args}" -# build shared library +# --- build --- autoninja -C "$OUTPUT_DIR" :default \ sdk/android:native_api \ sdk/android:libwebrtc \ sdk/android:libjingle_peerconnection_so -# make libwebrtc.a -# don't include nasm -ar -rc "$ARTIFACTS_DIR/lib/libwebrtc.a" `find "$OUTPUT_DIR/obj" -name '*.o' -not -path "*/third_party/nasm/*"` +# --- archive static lib (exclude nasm objs) --- +ar -rc "$ARTIFACTS_DIR/lib/libwebrtc.a" \ + $(find "$OUTPUT_DIR/obj" -name '*.o' -not -path "*/third_party/nasm/*") +# --- (REMOVED) shadow packaging --- +# Previously: ./shadow_jar.sh ... org.webrtc -> livekit.org.webrtc +# Not needed; Java sources are already migrated pre-build. + +# --- licenses --- python3 "./src/tools_webrtc/libs/generate_licenses.py" \ --target :default "$OUTPUT_DIR" "$OUTPUT_DIR" +# --- copy artifacts --- cp "$OUTPUT_DIR/obj/webrtc.ninja" "$ARTIFACTS_DIR" cp "$OUTPUT_DIR/libjingle_peerconnection_so.so" "$ARTIFACTS_DIR/lib" cp "$OUTPUT_DIR/args.gn" "$ARTIFACTS_DIR" -cp "$OUTPUT_DIR/LICENSE.md" "$ARTIFACTS_DIR" -cp "$OUTPUT_DIR/lib.java/sdk/android/libwebrtc.jar" "$ARTIFACTS_DIR" +cp "$OUTPUT_DIR/lib.java/sdk/android/libwebrtc.jar" "$ARTIFACTS_DIR/libwebrtc.jar" cp "src/sdk/android/AndroidManifest.xml" "$ARTIFACTS_DIR" -cd src +# --- headers --- +pushd src >/dev/null find . -name "*.h" -print | cpio -pd "$ARTIFACTS_DIR/include" find . -name "*.inc" -print | cpio -pd "$ARTIFACTS_DIR/include" +popd >/dev/null + +echo "Done. Artifacts in: $ARTIFACTS_DIR" From e2a303ee273c6e44e253db22a6b39ffc102f3939 Mon Sep 17 00:00:00 2001 From: Francisco Nadales Date: Thu, 2 Oct 2025 15:11:11 +0200 Subject: [PATCH 258/274] Ensure webrtc Android build is properly configured --- webrtc-sys/libwebrtc/build_android.sh | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/webrtc-sys/libwebrtc/build_android.sh b/webrtc-sys/libwebrtc/build_android.sh index bfb90be1e..7533f8ddf 100755 --- a/webrtc-sys/libwebrtc/build_android.sh +++ b/webrtc-sys/libwebrtc/build_android.sh @@ -50,6 +50,15 @@ export PATH="$(pwd)/depot_tools:$PATH" export OUTPUT_DIR="$(pwd)/src/out-$arch-$profile" export ARTIFACTS_DIR="$(pwd)/android-$arch-$profile" +# --- ensure .gclient configuration for android --- +echo "Updating existing .gclient file..." +if ! grep -q 'target_os.*=.*\[.*"android".*"unix".*\]' .gclient; then + # Remove any existing target_os line + sed -i '/^target_os\s*=/d' .gclient + # Add the new target_os line + echo 'target_os = ["android", "unix"]' >> .gclient +fi + # --- fetch src (WebRTC) --- if [ ! -e "$(pwd)/src" ]; then echo "Setting up WebRTC Android checkout..." From f8ee8914dbda81000c28d62661396b3ba80c956e Mon Sep 17 00:00:00 2001 From: Francisco Nadales Date: Thu, 2 Oct 2025 15:47:13 +0200 Subject: [PATCH 259/274] Implement WebRTC package migration and structure creation scripts --- webrtc-sys/libwebrtc/build_android.sh | 205 +----------------- .../libwebrtc/create_package_structure.sh | 65 ++++++ webrtc-sys/libwebrtc/migrate_webrtc.sh | 184 ++++++++++++++++ 3 files changed, 253 insertions(+), 201 deletions(-) create mode 100755 webrtc-sys/libwebrtc/create_package_structure.sh create mode 100755 webrtc-sys/libwebrtc/migrate_webrtc.sh diff --git a/webrtc-sys/libwebrtc/build_android.sh b/webrtc-sys/libwebrtc/build_android.sh index 7533f8ddf..a2d66df4c 100755 --- a/webrtc-sys/libwebrtc/build_android.sh +++ b/webrtc-sys/libwebrtc/build_android.sh @@ -173,209 +173,12 @@ popd >/dev/null echo "Patch application complete." # --- migrate org.webrtc -> livekit.org.webrtc before build --- -migrate_webrtc() { - local root="$1" - if [[ ! -d "$root" ]]; then - echo "migrate_webrtc: '$root' is not a directory" >&2 - return 1 - fi - echo "Running migrate_webrtc in: $root" - - # WebRTC-related folders to process (avoids unnecessary processing of unrelated code) - local webrtc_folders=( - "sdk" - "api" - "modules/audio_device/android" - "modules/audio_coding/audio_network_adaptor" - "modules/video_capture/android" - "modules/video_coding/codecs/h264/android" - "modules/video_coding/codecs/vp8/android" - "modules/video_coding/codecs/vp9/android" - "rtc_base" - "examples/android*" - "test/android" - ) - - # File types to process - local exts=( - "*.java" "*.kt" "*.xml" "*.gn" "*.gni" "*.proto" - "*.c" "*.cc" "*.cpp" "*.cxx" "*.hpp" "*.hh" "*.h" "*.hxx" - "*.m" "*.mm" "*.gradle" "*.properties" "*.mk" "*.cmake" - ) - - local find_expr=() - for i in "${!exts[@]}"; do - if [[ $i -gt 0 ]]; then find_expr+=(-o); fi - find_expr+=(-name "${exts[$i]}") - done - - local changed=0 - local total_files=0 - - # Helper function to process a single folder - process_folder() { - local folder_path="$1" - local folder_files=0 - - echo " Starting to process folder: $folder_path" - - # Check if folder exists and is accessible - if [[ ! -d "$folder_path" ]]; then - echo " Error: Folder does not exist: $folder_path" - return 1 - fi - - # Create find command and test it first - local find_cmd="find \"$folder_path\" -type f \\( ${find_expr[*]} \\) -print0 2>/dev/null" - echo " Find command: $find_cmd" - - # Use safer approach without process substitution - local temp_list - temp_list="$(mktemp)" - - if ! find "$folder_path" -type f \( "${find_expr[@]}" \) -print0 2>/dev/null > "$temp_list"; then - echo " Warning: Find command failed for $folder_path" - rm -f "$temp_list" - return 0 - fi - - # Process files from the temp list - while IFS= read -r -d '' file; do - if [[ -z "$file" ]]; then - continue - fi - - local tmp - tmp="$(mktemp)" - if ! cp "$file" "$tmp" 2>/dev/null; then - echo " Warning: Failed to copy $file, skipping" - rm -f "$tmp" - continue - fi - - ((total_files++)) - ((folder_files++)) - - # Dot-notation and slash-notation replacements with double-prefix protection - if ! perl -0777 -i -pe ' - s/(?/dev/null; then - echo " Warning: Failed to process $file with perl, skipping" - rm -f "$tmp" - continue - fi - - - if ! cmp -s "$file" "$tmp" 2>/dev/null; then - echo " updated: $file" - ((changed++)) - fi - rm -f "$tmp" - done < "$temp_list" - - rm -f "$temp_list" - echo " processed $folder_files files in $(basename "$folder_path")" - return 0 - } - - # Process each WebRTC-related folder - for folder in "${webrtc_folders[@]}"; do - echo " Checking folder: $folder" - - # Handle wildcard patterns by expanding them - if [[ "$folder" == *"*"* ]]; then - echo " Expanding wildcard pattern: $folder" - # Use shell globbing for wildcard patterns - local found_folders=() - if pushd "$root" >/dev/null 2>&1; then - shopt -s nullglob # Enable nullglob to handle no matches - for expanded_folder in $folder; do - if [[ -d "$expanded_folder" ]]; then - found_folders+=("$expanded_folder") - echo " Found: $expanded_folder" - fi - done - shopt -u nullglob # Disable nullglob - popd >/dev/null 2>&1 - else - echo " Warning: Failed to change to directory $root" - continue - fi - - # Process each expanded folder - if [[ ${#found_folders[@]} -eq 0 ]]; then - echo " No folders found matching pattern: $folder" - else - for expanded_folder in "${found_folders[@]}"; do - local folder_path="$root/$expanded_folder" - echo " Processing folder: $expanded_folder" - if ! process_folder "$folder_path"; then - echo " Warning: Failed to process folder $expanded_folder" - fi - done - fi - else - # Handle regular folder paths - local folder_path="$root/$folder" - if [[ -d "$folder_path" ]]; then - echo " Processing folder: $folder" - if ! process_folder "$folder_path"; then - echo " Warning: Failed to process folder $folder" - fi - else - echo " Skipping non-existent folder: $folder" - fi - fi - done - - echo "migrate_webrtc: processed $total_files files, updated $changed files" -} - -migrate_webrtc "$(pwd)/src" +echo "Running package migration..." +"$COMMAND_DIR/migrate_webrtc.sh" "$(pwd)/src" # --- create new package structure for migrated files --- -echo "Creating new package directory structure..." -pushd src >/dev/null - -# Find all directories containing org/webrtc structure -find . -type d -path "*/org/webrtc" | while read -r webrtc_dir; do - # Get the parent directory (e.g., ./sdk/android/src/java) - parent_dir=$(dirname "$(dirname "$webrtc_dir")") - - # Create the new livekit/org/webrtc structure - new_dir="$parent_dir/livekit/org/webrtc" - if [[ ! -d "$new_dir" ]]; then - echo " Creating directory: $new_dir" - mkdir -p "$new_dir" - - # Move all files and subdirectories from org/webrtc to livekit/org/webrtc - if [[ -d "$webrtc_dir" ]]; then - echo " Moving contents from $webrtc_dir to $new_dir" - mv "$webrtc_dir"/* "$new_dir/" 2>/dev/null || true - - # Remove the now-empty org/webrtc directory - rmdir "$webrtc_dir" 2>/dev/null || true - - # Clean up empty parent directories if they exist - org_dir=$(dirname "$webrtc_dir") - if [[ -d "$org_dir" && -z "$(ls -A "$org_dir" 2>/dev/null)" ]]; then - rmdir "$org_dir" 2>/dev/null || true - fi - fi - fi -done - -popd >/dev/null -echo "Package structure creation complete." +echo "Running package structure creation..." +"$COMMAND_DIR/create_package_structure.sh" src # --- build setup --- mkdir -p "$ARTIFACTS_DIR/lib" diff --git a/webrtc-sys/libwebrtc/create_package_structure.sh b/webrtc-sys/libwebrtc/create_package_structure.sh new file mode 100755 index 000000000..aa602fbcf --- /dev/null +++ b/webrtc-sys/libwebrtc/create_package_structure.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +# LiveKit WebRTC Package Structure Creation Script +# Creates new package directory structure for migrated files + +set -euo pipefail + +# create_package_structure function - creates new livekit/org/webrtc directory structure +create_package_structure() { + local src_dir="$1" + + if [[ ! -d "$src_dir" ]]; then + echo "create_package_structure: '$src_dir' is not a directory" >&2 + return 1 + fi + + echo "Creating new package directory structure in: $src_dir" + + pushd "$src_dir" >/dev/null + + # Find all directories containing org/webrtc structure, excluding out* folders + find . -type d -path "*/org/webrtc" -not -path "./out*" -not -path "*/out*" | while read -r webrtc_dir; do + # Get the parent directory (e.g., ./sdk/android/src/java) + parent_dir=$(dirname "$(dirname "$webrtc_dir")") + + # Check if we already have a livekit directory in this parent - if so, skip to avoid duplicates + if [[ -d "$parent_dir/livekit" ]]; then + echo " Skipping $webrtc_dir - livekit directory already exists in $parent_dir" + continue + fi + + # Create the new livekit/org/webrtc structure + new_dir="$parent_dir/livekit/org/webrtc" + echo " Creating directory: $new_dir" + mkdir -p "$new_dir" + + # Move all files and subdirectories from org/webrtc to livekit/org/webrtc + if [[ -d "$webrtc_dir" ]]; then + echo " Moving contents from $webrtc_dir to $new_dir" + mv "$webrtc_dir"/* "$new_dir/" 2>/dev/null || true + + # Remove the now-empty org/webrtc directory + rmdir "$webrtc_dir" 2>/dev/null || true + + # Clean up empty parent directories if they exist + org_dir=$(dirname "$webrtc_dir") + if [[ -d "$org_dir" && -z "$(ls -A "$org_dir" 2>/dev/null)" ]]; then + rmdir "$org_dir" 2>/dev/null || true + fi + fi + done + + popd >/dev/null + echo "Package structure creation complete." +} + +# Main execution +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + if [[ $# -ne 1 ]]; then + echo "Usage: $0 " >&2 + echo "Example: $0 src" >&2 + exit 1 + fi + + create_package_structure "$1" +fi \ No newline at end of file diff --git a/webrtc-sys/libwebrtc/migrate_webrtc.sh b/webrtc-sys/libwebrtc/migrate_webrtc.sh new file mode 100755 index 000000000..1594734cd --- /dev/null +++ b/webrtc-sys/libwebrtc/migrate_webrtc.sh @@ -0,0 +1,184 @@ +#!/usr/bin/env bash +# LiveKit WebRTC Package Migration Script +# Migrates org.webrtc -> livekit.org.webrtc across WebRTC sources + +set -euo pipefail + +# migrate_webrtc function - migrates org.webrtc references to livekit.org.webrtc +migrate_webrtc() { + local root="$1" + if [[ ! -d "$root" ]]; then + echo "migrate_webrtc: '$root' is not a directory" >&2 + return 1 + fi + echo "Running migrate_webrtc in: $root" + + # WebRTC-related folders to process (avoids unnecessary processing of unrelated code) + local webrtc_folders=( + "sdk" + "api" + "modules/audio_device/android" + "modules/audio_coding/audio_network_adaptor" + "modules/video_capture/android" + "modules/video_coding/codecs/h264/android" + "modules/video_coding/codecs/vp8/android" + "modules/video_coding/codecs/vp9/android" + "rtc_base" + "examples/android*" + "test/android" + ) + + # File types to process + local exts=( + "*.java" "*.kt" "*.xml" "*.gn" "*.gni" "*.proto" + "*.c" "*.cc" "*.cpp" "*.cxx" "*.hpp" "*.hh" "*.h" "*.hxx" + "*.m" "*.mm" "*.gradle" "*.properties" "*.mk" "*.cmake" + ) + + local find_expr=() + for i in "${!exts[@]}"; do + if [[ $i -gt 0 ]]; then find_expr+=(-o); fi + find_expr+=(-name "${exts[$i]}") + done + + local changed=0 + local total_files=0 + + # Helper function to process a single folder + process_folder() { + local folder_path="$1" + local folder_files=0 + + echo " Starting to process folder: $folder_path" + + # Check if folder exists and is accessible + if [[ ! -d "$folder_path" ]]; then + echo " Error: Folder does not exist: $folder_path" + return 1 + fi + + # Create find command and test it first + local find_cmd="find \"$folder_path\" -type f \\( ${find_expr[*]} \\) -print0 2>/dev/null" + echo " Find command: $find_cmd" + + # Use safer approach without process substitution + local temp_list + temp_list="$(mktemp)" + + if ! find "$folder_path" -type f \( "${find_expr[@]}" \) -print0 2>/dev/null > "$temp_list"; then + echo " Warning: Find command failed for $folder_path" + rm -f "$temp_list" + return 0 + fi + + # Process files from the temp list + while IFS= read -r -d '' file; do + if [[ -z "$file" ]]; then + continue + fi + + local tmp + tmp="$(mktemp)" + if ! cp "$file" "$tmp" 2>/dev/null; then + echo " Warning: Failed to copy $file, skipping" + rm -f "$tmp" + continue + fi + + ((total_files++)) + ((folder_files++)) + + # Dot-notation and slash-notation replacements with double-prefix protection + if ! perl -0777 -i -pe ' + s/(?/dev/null; then + echo " Warning: Failed to process $file with perl, skipping" + rm -f "$tmp" + continue + fi + + + if ! cmp -s "$file" "$tmp" 2>/dev/null; then + echo " updated: $file" + ((changed++)) + fi + rm -f "$tmp" + done < "$temp_list" + + rm -f "$temp_list" + echo " processed $folder_files files in $(basename "$folder_path")" + return 0 + } + + # Process each WebRTC-related folder + for folder in "${webrtc_folders[@]}"; do + echo " Checking folder: $folder" + + # Handle wildcard patterns by expanding them + if [[ "$folder" == *"*"* ]]; then + echo " Expanding wildcard pattern: $folder" + # Use shell globbing for wildcard patterns + local found_folders=() + if pushd "$root" >/dev/null 2>&1; then + shopt -s nullglob # Enable nullglob to handle no matches + for expanded_folder in $folder; do + if [[ -d "$expanded_folder" ]]; then + found_folders+=("$expanded_folder") + echo " Found: $expanded_folder" + fi + done + shopt -u nullglob # Disable nullglob + popd >/dev/null 2>&1 + else + echo " Warning: Failed to change to directory $root" + continue + fi + + # Process each expanded folder + if [[ ${#found_folders[@]} -eq 0 ]]; then + echo " No folders found matching pattern: $folder" + else + for expanded_folder in "${found_folders[@]}"; do + local folder_path="$root/$expanded_folder" + echo " Processing folder: $expanded_folder" + if ! process_folder "$folder_path"; then + echo " Warning: Failed to process folder $expanded_folder" + fi + done + fi + else + # Handle regular folder paths + local folder_path="$root/$folder" + if [[ -d "$folder_path" ]]; then + echo " Processing folder: $folder" + if ! process_folder "$folder_path"; then + echo " Warning: Failed to process folder $folder" + fi + else + echo " Skipping non-existent folder: $folder" + fi + fi + done + + echo "migrate_webrtc: processed $total_files files, updated $changed files" +} + +# Main execution +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + if [[ $# -ne 1 ]]; then + echo "Usage: $0 " >&2 + echo "Example: $0 \$(pwd)/src" >&2 + exit 1 + fi + + migrate_webrtc "$1" +fi \ No newline at end of file From fda1fc056a9ce674f545231c4702b9af0727957c Mon Sep 17 00:00:00 2001 From: Francisco Nadales Date: Fri, 3 Oct 2025 14:46:33 +0200 Subject: [PATCH 260/274] Add x264 video encoder if no HW encoding available --- .gitignore | 19 + .gitmodules | 3 + webrtc-sys/Cargo.toml | 1 + webrtc-sys/build/Cargo.toml | 2 + webrtc-sys/include/livekit/android.h | 13 +- .../livekit/android/video_decoder_factory.h | 27 ++ .../livekit/android/video_encoder_factory.h | 55 +++ .../x264/x264_encoder_template_adapter.h | 59 +++ .../include/livekit/x264/x264_video_encoder.h | 99 +++++ webrtc-sys/src/android.cpp | 36 -- .../src/android/video_decoder_factory.cpp | 49 +++ .../src/android/video_encoder_factory.cpp | 270 +++++++++++++ webrtc-sys/src/x264/README.md | 75 ++++ webrtc-sys/src/x264/x264_video_encoder.cpp | 361 ++++++++++++++++++ 14 files changed, 1023 insertions(+), 46 deletions(-) create mode 100644 webrtc-sys/include/livekit/android/video_decoder_factory.h create mode 100644 webrtc-sys/include/livekit/android/video_encoder_factory.h create mode 100644 webrtc-sys/include/livekit/x264/x264_encoder_template_adapter.h create mode 100644 webrtc-sys/include/livekit/x264/x264_video_encoder.h create mode 100644 webrtc-sys/src/android/video_decoder_factory.cpp create mode 100644 webrtc-sys/src/android/video_encoder_factory.cpp create mode 100644 webrtc-sys/src/x264/README.md create mode 100644 webrtc-sys/src/x264/x264_video_encoder.cpp diff --git a/.gitignore b/.gitignore index c258a2708..7c5485b20 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,22 @@ target soxr-sys/test-input.wav soxr-sys/test-output.wav .DS_Store + +# Exclude webrtc build artifacts +**/.gcs_entries + +# Exclude webrtc build directories +**/android-arm-release +**/android-arm64-release +**/android-arm-debug +**/android-arm64-debug + +# Exclude vscode settings +**/.vscode/ + +# Exclude output directories +**/out* +livekit-ffi_conan/ + +# Exclude third_party directories +**/third_party \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index b70842c2a..9c89d1ff8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "yuv-sys/libyuv"] path = yuv-sys/libyuv url = https://chromium.googlesource.com/libyuv/libyuv +[submodule "webrtc-sys/third_party/x264"] + path = webrtc-sys/third_party/x264 + url = https://code.videolan.org/videolan/x264.git diff --git a/webrtc-sys/Cargo.toml b/webrtc-sys/Cargo.toml index 5585ee591..cca605c16 100644 --- a/webrtc-sys/Cargo.toml +++ b/webrtc-sys/Cargo.toml @@ -11,6 +11,7 @@ repository = "https://github.com/livekit/client-sdk-rust" default = [] use_vaapi = [] use_nvidia = [] +use_x264 = [] [dependencies] cxx = "1.0" diff --git a/webrtc-sys/build/Cargo.toml b/webrtc-sys/build/Cargo.toml index 54ac495ae..271335203 100644 --- a/webrtc-sys/build/Cargo.toml +++ b/webrtc-sys/build/Cargo.toml @@ -17,3 +17,5 @@ scratch = "1.0" fs2 = "0.4" semver = "1.0" anyhow = "1.0" +flate2 = "1.0" +tar = "0.4" diff --git a/webrtc-sys/include/livekit/android.h b/webrtc-sys/include/livekit/android.h index 2826ea33f..fb21af805 100644 --- a/webrtc-sys/include/livekit/android.h +++ b/webrtc-sys/include/livekit/android.h @@ -18,20 +18,13 @@ #include -#include - -#include "api/video_codecs/video_decoder_factory.h" -#include "api/video_codecs/video_encoder_factory.h" +#include "livekit/android/video_decoder_factory.h" +#include "livekit/android/video_encoder_factory.h" namespace livekit { + typedef JavaVM JavaVM; -} // namespace livekit -#include "webrtc-sys/src/android.rs.h" -namespace livekit { void init_android(JavaVM* jvm); -std::unique_ptr CreateAndroidVideoEncoderFactory(); -std::unique_ptr CreateAndroidVideoDecoderFactory(); - } // namespace livekit diff --git a/webrtc-sys/include/livekit/android/video_decoder_factory.h b/webrtc-sys/include/livekit/android/video_decoder_factory.h new file mode 100644 index 000000000..10dc3bff9 --- /dev/null +++ b/webrtc-sys/include/livekit/android/video_decoder_factory.h @@ -0,0 +1,27 @@ +/* + * Copyright 2023 LiveKit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include "api/video_codecs/video_decoder_factory.h" + +namespace livekit { + +std::unique_ptr CreateAndroidVideoDecoderFactory(); + +} // namespace livekit \ No newline at end of file diff --git a/webrtc-sys/include/livekit/android/video_encoder_factory.h b/webrtc-sys/include/livekit/android/video_encoder_factory.h new file mode 100644 index 000000000..91b42cca6 --- /dev/null +++ b/webrtc-sys/include/livekit/android/video_encoder_factory.h @@ -0,0 +1,55 @@ +/* + * Copyright 2023 LiveKit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +#include "api/video_codecs/sdp_video_format.h" +#include "api/video_codecs/video_encoder_factory.h" + +namespace livekit { + +class AndroidVideoEncoderFactory : public webrtc::VideoEncoderFactory { + public: + AndroidVideoEncoderFactory(); + ~AndroidVideoEncoderFactory() override; + + std::vector GetSupportedFormats() const override; + + CodecSupport QueryCodecSupport( + const webrtc::SdpVideoFormat& format, + std::optional scalability_mode) const override; + + std::unique_ptr Create( + const webrtc::Environment& env, + const webrtc::SdpVideoFormat& format) override; + + private: + bool IsH264Format(const webrtc::SdpVideoFormat& format) const; + void EnsureH264InSupportedFormats( + std::vector& formats) const; + + const std::unique_ptr m_builtinEncoderFactory; + std::unique_ptr m_hwEncoderFactory; + std::unique_ptr m_swEncoderFactory; +}; + +std::unique_ptr CreateAndroidVideoEncoderFactory(); + +} // namespace livekit \ No newline at end of file diff --git a/webrtc-sys/include/livekit/x264/x264_encoder_template_adapter.h b/webrtc-sys/include/livekit/x264/x264_encoder_template_adapter.h new file mode 100644 index 000000000..5373970ae --- /dev/null +++ b/webrtc-sys/include/livekit/x264/x264_encoder_template_adapter.h @@ -0,0 +1,59 @@ +/* + * Copyright 2023 LiveKit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LIVEKIT_X264_X264_ENCODER_TEMPLATE_ADAPTER_H_ +#define LIVEKIT_X264_X264_ENCODER_TEMPLATE_ADAPTER_H_ + +#if defined(WEBRTC_USE_X264) + +#include +#include + +#include "api/video_codecs/sdp_video_format.h" +#include "api/video_codecs/video_encoder.h" +#include "livekit/x264/x264_video_encoder.h" +#include "media/base/media_constants.h" + +namespace livekit { + +// Template adapter for integrating X264VideoEncoder with +// VideoEncoderFactoryTemplate +struct X264EncoderTemplateAdapter { + static std::vector SupportedFormats() { + return { + webrtc::SdpVideoFormat(cricket::kH264CodecName, + {{cricket::kH264FmtpProfileLevelId, + cricket::kH264ProfileLevelConstrainedBaseline}, + {cricket::kH264FmtpLevelAsymmetryAllowed, "1"}, + {cricket::kH264FmtpPacketizationMode, "1"}})}; + } + + static std::unique_ptr CreateEncoder( + const webrtc::SdpVideoFormat& format) { + return CreateX264VideoEncoder(); + } + + static bool IsScalabilityModeSupported( + webrtc::ScalabilityMode scalability_mode) { + return scalability_mode == webrtc::ScalabilityMode::kL1T1; + } +}; + +} // namespace livekit + +#endif // defined(WEBRTC_USE_X264) && defined(WEBRTC_ANDROID) + +#endif // LIVEKIT_X264_X264_ENCODER_TEMPLATE_ADAPTER_H_ \ No newline at end of file diff --git a/webrtc-sys/include/livekit/x264/x264_video_encoder.h b/webrtc-sys/include/livekit/x264/x264_video_encoder.h new file mode 100644 index 000000000..e0857be3e --- /dev/null +++ b/webrtc-sys/include/livekit/x264/x264_video_encoder.h @@ -0,0 +1,99 @@ +/* + * Copyright 2023 LiveKit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LIVEKIT_X264_X264_VIDEO_ENCODER_H_ +#define LIVEKIT_X264_X264_VIDEO_ENCODER_H_ + +#if defined(WEBRTC_USE_X264) + +#include +#include + +#include "api/video/video_codec_type.h" +#include "api/video/video_frame.h" +#include "api/video_codecs/video_encoder.h" +#include "rtc_base/synchronization/mutex.h" + +extern "C" { +#include +} + +namespace livekit { + +class X264VideoEncoder : public webrtc::VideoEncoder { + public: + X264VideoEncoder(); + ~X264VideoEncoder() override; + + // VideoEncoder interface implementation + int InitEncode(const webrtc::VideoCodec* codec_settings, + const webrtc::VideoEncoder::Settings& settings) override; + + int32_t RegisterEncodeCompleteCallback( + webrtc::EncodedImageCallback* callback) override; + + int32_t Release() override; + + int32_t Encode( + const webrtc::VideoFrame& frame, + const std::vector* frame_types) override; + + void SetRates(const RateControlParameters& parameters) override; + + webrtc::VideoEncoder::EncoderInfo GetEncoderInfo() const override; + + private: + struct X264Config { + int width = 0; + int height = 0; + int target_bitrate_bps = 0; + int max_framerate = 30; + bool key_frame_requested = false; + }; + + bool InitializeEncoder(); + void DestroyEncoder(); + bool EncodeFrame(const webrtc::VideoFrame& frame, bool force_key_frame); + + // Threading and synchronization + webrtc::Mutex encoder_mutex_; + + // X264 encoder context + x264_t* encoder_ = nullptr; + x264_param_t param_; + x264_picture_t pic_in_; + x264_picture_t pic_out_; + + // Configuration + X264Config config_; + bool initialized_ = false; + int64_t first_frame_time_ms_ = -1; + + // Callback + webrtc::EncodedImageCallback* encoded_image_callback_ = nullptr; + + // Frame counting for debugging + int frame_count_ = 0; +}; + +// Factory function for creating X264VideoEncoder +std::unique_ptr CreateX264VideoEncoder(); + +} // namespace livekit + +#endif // defined(WEBRTC_USE_X264) && defined(WEBRTC_ANDROID) + +#endif // LIVEKIT_X264_X264_VIDEO_ENCODER_H_ \ No newline at end of file diff --git a/webrtc-sys/src/android.cpp b/webrtc-sys/src/android.cpp index ed9052870..14639743f 100644 --- a/webrtc-sys/src/android.cpp +++ b/webrtc-sys/src/android.cpp @@ -18,14 +18,7 @@ #include -#include - -#include "api/video_codecs/video_decoder_factory.h" #include "sdk/android/native_api/base/init.h" -#include "sdk/android/native_api/codecs/wrapper.h" -#include "sdk/android/native_api/jni/class_loader.h" -#include "sdk/android/native_api/jni/scoped_java_ref.h" -#include "sdk/android/src/jni/jni_helpers.h" namespace livekit { @@ -33,33 +26,4 @@ void init_android(JavaVM* jvm) { webrtc::InitAndroid(jvm); } -std::unique_ptr -CreateAndroidVideoEncoderFactory() { - JNIEnv* env = webrtc::AttachCurrentThreadIfNeeded(); - webrtc::ScopedJavaLocalRef factory_class = - webrtc::GetClass(env, "livekit/org/webrtc/DefaultVideoEncoderFactory"); - - jmethodID ctor = env->GetMethodID(factory_class.obj(), "", - "(Llivekit/org/webrtc/EglBase$Context;ZZ)V"); - - jobject encoder_factory = - env->NewObject(factory_class.obj(), ctor, nullptr, true, false); - - return webrtc::JavaToNativeVideoEncoderFactory(env, encoder_factory); -} - -std::unique_ptr -CreateAndroidVideoDecoderFactory() { - JNIEnv* env = webrtc::AttachCurrentThreadIfNeeded(); - - webrtc::ScopedJavaLocalRef factory_class = - webrtc::GetClass(env, "livekit/org/webrtc/WrappedVideoDecoderFactory"); - - jmethodID ctor = env->GetMethodID(factory_class.obj(), "", - "(Llivekit/org/webrtc/EglBase$Context;)V"); - - jobject decoder_factory = env->NewObject(factory_class.obj(), ctor, nullptr); - return webrtc::JavaToNativeVideoDecoderFactory(env, decoder_factory); -} - } // namespace livekit diff --git a/webrtc-sys/src/android/video_decoder_factory.cpp b/webrtc-sys/src/android/video_decoder_factory.cpp new file mode 100644 index 000000000..97ec15967 --- /dev/null +++ b/webrtc-sys/src/android/video_decoder_factory.cpp @@ -0,0 +1,49 @@ +/* + * Copyright 2023 LiveKit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "livekit/android/video_decoder_factory.h" + +#include + +#include + +#include "api/video_codecs/video_decoder_factory.h" +#include "rtc_base/logging.h" +#include "sdk/android/native_api/base/init.h" +#include "sdk/android/native_api/codecs/wrapper.h" +#include "sdk/android/native_api/jni/class_loader.h" +#include "sdk/android/native_api/jni/scoped_java_ref.h" +#include "sdk/android/src/jni/jni_helpers.h" + +namespace livekit { + +std::unique_ptr +CreateAndroidVideoDecoderFactory() { + RTC_LOG(LS_INFO) << "Creating AndroidVideoDecoderFactory"; + + JNIEnv* env = webrtc::AttachCurrentThreadIfNeeded(); + + webrtc::ScopedJavaLocalRef factory_class = + webrtc::GetClass(env, "livekit/org/webrtc/WrappedVideoDecoderFactory"); + + jmethodID ctor = env->GetMethodID(factory_class.obj(), "", + "(Llivekit/org/webrtc/EglBase$Context;)V"); + + jobject decoder_factory = env->NewObject(factory_class.obj(), ctor, nullptr); + return webrtc::JavaToNativeVideoDecoderFactory(env, decoder_factory); +} + +} // namespace livekit \ No newline at end of file diff --git a/webrtc-sys/src/android/video_encoder_factory.cpp b/webrtc-sys/src/android/video_encoder_factory.cpp new file mode 100644 index 000000000..bc1c5aa6f --- /dev/null +++ b/webrtc-sys/src/android/video_encoder_factory.cpp @@ -0,0 +1,270 @@ +/* + * Copyright 2023 LiveKit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "livekit/android/video_encoder_factory.h" + +#include + +#include +#include +#include + +#include "api/video_codecs/sdp_video_format.h" +#include "api/video_codecs/video_encoder_factory_template.h" +#include "api/video_codecs/video_encoder_factory_template_libvpx_vp8_adapter.h" +#include "api/video_codecs/video_encoder_factory_template_libvpx_vp9_adapter.h" +#include "media/base/media_constants.h" +#include "rtc_base/logging.h" +#include "sdk/android/native_api/base/init.h" +#include "sdk/android/native_api/codecs/wrapper.h" +#include "sdk/android/native_api/jni/class_loader.h" +#include "sdk/android/native_api/jni/scoped_java_ref.h" +#include "sdk/android/src/jni/jni_helpers.h" + +#if defined(WEBRTC_USE_X264) && defined(WEBRTC_ANDROID) +#include "livekit/x264/x264_video_encoder.h" +#endif + +#if defined(WEBRTC_USE_H264) +#include "api/video_codecs/video_encoder_factory_template_open_h264_adapter.h" +#endif + +namespace livekit { + +// Helper function to create hardware encoder factory +std::unique_ptr +CreateHardwareVideoEncoderFactory() { + JNIEnv* env = webrtc::AttachCurrentThreadIfNeeded(); + webrtc::ScopedJavaLocalRef factory_class = + webrtc::GetClass(env, "livekit/org/webrtc/HardwareVideoEncoderFactory"); + + if (!factory_class.obj()) { + RTC_LOG(LS_WARNING) << "HardwareVideoEncoderFactory class not found"; + return nullptr; + } + + jmethodID ctor = + env->GetMethodID(factory_class.obj(), "", + "(Llivekit/org/webrtc/EglBase$Context;ZZ)V"); + + jobject encoder_factory = + env->NewObject(factory_class.obj(), ctor, nullptr, true, false); + + return webrtc::JavaToNativeVideoEncoderFactory(env, encoder_factory); +} + +// Helper function to create software encoder factory +std::unique_ptr +CreateSoftwareVideoEncoderFactory() { + JNIEnv* env = webrtc::AttachCurrentThreadIfNeeded(); + webrtc::ScopedJavaLocalRef factory_class = + webrtc::GetClass(env, "livekit/org/webrtc/SoftwareVideoEncoderFactory"); + + if (!factory_class.obj()) { + RTC_LOG(LS_WARNING) << "SoftwareVideoEncoderFactory class not found"; + return nullptr; + } + + jmethodID ctor = env->GetMethodID(factory_class.obj(), "", "()V"); + jobject encoder_factory = env->NewObject(factory_class.obj(), ctor); + + return webrtc::JavaToNativeVideoEncoderFactory(env, encoder_factory); +} + +// AndroidVideoEncoderFactory implementation +AndroidVideoEncoderFactory::AndroidVideoEncoderFactory() + : m_builtinEncoderFactory([]() { + using Factory = webrtc::VideoEncoderFactoryTemplate< + webrtc::LibvpxVp8EncoderTemplateAdapter, +#if defined(WEBRTC_USE_H264) + webrtc::OpenH264EncoderTemplateAdapter, +#endif + webrtc::LibvpxVp9EncoderTemplateAdapter>; + return std::make_unique(); + }()), + m_hwEncoderFactory(CreateHardwareVideoEncoderFactory()), + m_swEncoderFactory(CreateSoftwareVideoEncoderFactory()) { + RTC_LOG(LS_INFO) << "AndroidVideoEncoderFactory created"; +} + +AndroidVideoEncoderFactory::~AndroidVideoEncoderFactory() { + RTC_LOG(LS_INFO) << "AndroidVideoEncoderFactory destroyed"; +} + +bool AndroidVideoEncoderFactory::IsH264Format( + const webrtc::SdpVideoFormat& format) const { + return format.name == cricket::kH264CodecName; +} + +void AndroidVideoEncoderFactory::EnsureH264InSupportedFormats( + std::vector& formats) const { + // Check if H.264 is already in the supported formats + for (const auto& format : formats) { + if (IsH264Format(format)) { + return; // H.264 already supported + } + } + + // Add H.264 format if not present + webrtc::SdpVideoFormat h264_format(cricket::kH264CodecName); + h264_format.parameters[cricket::kH264FmtpProfileLevelId] = + cricket::kH264ProfileLevelConstrainedBaseline; + formats.push_back(h264_format); + + RTC_LOG(LS_INFO) << "Added H.264 to supported formats"; +} + +std::vector +AndroidVideoEncoderFactory::GetSupportedFormats() const { + std::vector formats; + + // Get formats from hardware factory + if (m_hwEncoderFactory) { + auto hw_formats = m_hwEncoderFactory->GetSupportedFormats(); + formats.insert(formats.end(), hw_formats.begin(), hw_formats.end()); + } + + // Get formats from software factory + if (m_swEncoderFactory) { + auto sw_formats = m_swEncoderFactory->GetSupportedFormats(); + formats.insert(formats.end(), sw_formats.begin(), sw_formats.end()); + } + + // Get formats from builtin factory + if (m_builtinEncoderFactory) { + auto builtin_formats = m_builtinEncoderFactory->GetSupportedFormats(); + formats.insert(formats.end(), builtin_formats.begin(), + builtin_formats.end()); + } + + // Ensure H.264 is in supported formats + EnsureH264InSupportedFormats(formats); + + // Remove duplicates + std::sort(formats.begin(), formats.end(), + [](const webrtc::SdpVideoFormat& a, + const webrtc::SdpVideoFormat& b) { return a.name < b.name; }); + formats.erase(std::unique(formats.begin(), formats.end(), + [](const webrtc::SdpVideoFormat& a, + const webrtc::SdpVideoFormat& b) { + return a.IsSameCodec(b); + }), + formats.end()); + + return formats; +} + +webrtc::VideoEncoderFactory::CodecSupport +AndroidVideoEncoderFactory::QueryCodecSupport( + const webrtc::SdpVideoFormat& format, + std::optional scalability_mode) const { + // Try hardware factory first + if (m_hwEncoderFactory) { + auto support = + m_hwEncoderFactory->QueryCodecSupport(format, scalability_mode); + if (support.is_supported) { + return support; + } + } + + // For H.264, we always support it via X264 + if (IsH264Format(format)) { +#if defined(WEBRTC_USE_X264) && defined(WEBRTC_ANDROID) + return {.is_supported = true, .is_power_efficient = false}; +#endif + } + + // Try software factory + if (m_swEncoderFactory) { + auto support = + m_swEncoderFactory->QueryCodecSupport(format, scalability_mode); + if (support.is_supported) { + return support; + } + } + + // Try builtin factory + if (m_builtinEncoderFactory) { + return m_builtinEncoderFactory->QueryCodecSupport(format, scalability_mode); + } + + return {.is_supported = false}; +} + +std::unique_ptr AndroidVideoEncoderFactory::Create( + const webrtc::Environment& env, + const webrtc::SdpVideoFormat& format) { + // Try hardware factory first + if (m_hwEncoderFactory) { + for (const auto& supported_format : + m_hwEncoderFactory->GetSupportedFormats()) { + if (supported_format.IsSameCodec(format)) { + auto encoder = m_hwEncoderFactory->Create(env, format); + if (encoder) { + RTC_LOG(LS_INFO) << "Created hardware encoder for " << format.name; + return encoder; + } + } + } + } + + // For H.264, create X264VideoEncoder + if (IsH264Format(format)) { +#if defined(WEBRTC_USE_X264) && defined(WEBRTC_ANDROID) + RTC_LOG(LS_INFO) << "Creating X264VideoEncoder for H.264"; + return std::make_unique(); +#endif + } + + // Try software factory + if (m_swEncoderFactory) { + for (const auto& supported_format : + m_swEncoderFactory->GetSupportedFormats()) { + if (supported_format.IsSameCodec(format)) { + auto encoder = m_swEncoderFactory->Create(env, format); + if (encoder) { + RTC_LOG(LS_INFO) << "Created software encoder for " << format.name; + return encoder; + } + } + } + } + + // Try builtin factory + if (m_builtinEncoderFactory) { + for (const auto& supported_format : + m_builtinEncoderFactory->GetSupportedFormats()) { + if (supported_format.IsSameCodec(format)) { + auto encoder = m_builtinEncoderFactory->Create(env, format); + if (encoder) { + RTC_LOG(LS_INFO) << "Created builtin encoder for " << format.name; + return encoder; + } + } + } + } + + RTC_LOG(LS_ERROR) << "No encoder found for " << format.name; + return nullptr; +} + +std::unique_ptr +CreateAndroidVideoEncoderFactory() { + RTC_LOG(LS_INFO) << "Creating AndroidVideoEncoderFactory"; + return std::make_unique(); +} + +} // namespace livekit \ No newline at end of file diff --git a/webrtc-sys/src/x264/README.md b/webrtc-sys/src/x264/README.md new file mode 100644 index 000000000..ac0c6f742 --- /dev/null +++ b/webrtc-sys/src/x264/README.md @@ -0,0 +1,75 @@ +# X264 Video Encoder + +This directory contains the implementation of `X264VideoEncoder`, a WebRTC `VideoEncoder` that uses the x264 library for H.264 encoding on Android platforms. + +## Features + +- **Hardware-optimized**: Configured for low-latency real-time encoding +- **Android-specific**: Only available when building for Android with x264 support +- **WebRTC compatible**: Implements the standard `webrtc::VideoEncoder` interface +- **Template adapter**: Includes adapter for use with `VideoEncoderFactoryTemplate` + +## Build Requirements + +1. **x264 library**: Must be available in the Android NDK environment +2. **Feature flag**: Enable the `use_x264` feature when building: + ```bash + cargo build --features use_x264 + ``` +3. **Platform**: Only builds on Android targets + +## Usage + +### Direct Usage +```cpp +#include "livekit/x264/x264_video_encoder.h" + +// Create encoder instance +auto encoder = livekit::CreateX264VideoEncoder(); +``` + +### With VideoEncoderFactoryTemplate +```cpp +#include "livekit/x264/x264_encoder_template_adapter.h" + +using MyFactory = webrtc::VideoEncoderFactoryTemplate< + livekit::X264EncoderTemplateAdapter, + webrtc::LibvpxVp8EncoderTemplateAdapter, + webrtc::LibvpxVp9EncoderTemplateAdapter>; +``` + +## Configuration + +The encoder is pre-configured for optimal real-time performance: + +- **Preset**: `veryfast` with `zerolatency` tune +- **Profile**: H.264 Baseline +- **Rate Control**: Average Bitrate (ABR) +- **Key Frames**: Every 2 seconds maximum +- **B-frames**: Disabled for low latency +- **Threading**: Single-threaded for consistency + +## Conditional Compilation + +The entire implementation is wrapped in conditional compilation guards: + +```cpp +#if defined(WEBRTC_USE_X264) && defined(WEBRTC_ANDROID) +// Implementation here +#endif +``` + +This ensures the code only compiles when: +1. `WEBRTC_USE_X264` is defined (set by the `use_x264` feature) +2. `WEBRTC_ANDROID` is defined (set for Android builds) + +## Files + +- `x264_video_encoder.h` - Header with class declaration +- `x264_video_encoder.cpp` - Implementation +- `x264_encoder_template_adapter.h` - Template adapter for factory integration +- `README.md` - This documentation + +## Integration + +The encoder integrates with the LiveKit WebRTC system and can be used as a drop-in replacement for other H.264 encoders in Android builds where x264 is available. \ No newline at end of file diff --git a/webrtc-sys/src/x264/x264_video_encoder.cpp b/webrtc-sys/src/x264/x264_video_encoder.cpp new file mode 100644 index 000000000..bfcbb5dd4 --- /dev/null +++ b/webrtc-sys/src/x264/x264_video_encoder.cpp @@ -0,0 +1,361 @@ +/* + * Copyright 2023 LiveKit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "livekit/x264/x264_video_encoder.h" + +#if defined(WEBRTC_USE_X264) + +#include +#include + +#include "api/video/i420_buffer.h" +#include "common_video/libyuv/include/webrtc_libyuv.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/time_utils.h" +#include "modules/video_coding/include/video_error_codes.h" +#include "third_party/libyuv/include/libyuv.h" +#include "modules/video_coding/include/video_codec_interface.h" +#include "modules/video_coding/codecs/h264/include/h264_globals.h" +#include "modules/video_coding/codecs/interface/common_constants.h" + +namespace livekit { + +X264VideoEncoder::X264VideoEncoder() { + RTC_LOG(LS_INFO) << "X264VideoEncoder created"; +} + +X264VideoEncoder::~X264VideoEncoder() { + Release(); + RTC_LOG(LS_INFO) << "X264VideoEncoder destroyed"; +} + +int X264VideoEncoder::InitEncode( + const webrtc::VideoCodec* codec_settings, + const webrtc::VideoEncoder::Settings& settings) { + RTC_LOG(LS_INFO) << "X264VideoEncoder::InitEncode"; + + if (!codec_settings) { + RTC_LOG(LS_ERROR) << "No codec settings provided"; + return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; + } + + if (codec_settings->codecType != webrtc::kVideoCodecH264) { + RTC_LOG(LS_ERROR) << "Invalid codec type: " << codec_settings->codecType; + return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; + } + + webrtc::MutexLock lock(&encoder_mutex_); + + // Store configuration + config_.width = codec_settings->width; + config_.height = codec_settings->height; + config_.target_bitrate_bps = + codec_settings->startBitrate * 1000; // Convert to bps + config_.max_framerate = codec_settings->maxFramerate; + + RTC_LOG(LS_INFO) << "Initializing x264 encoder: " << config_.width << "x" + << config_.height << " @ " << config_.target_bitrate_bps + << " bps, " << config_.max_framerate << " fps"; + + // Initialize x264 encoder + if (!InitializeEncoder()) { + RTC_LOG(LS_ERROR) << "Failed to initialize x264 encoder"; + return WEBRTC_VIDEO_CODEC_ERROR; + } + + initialized_ = true; + first_frame_time_ms_ = -1; + frame_count_ = 0; + + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t X264VideoEncoder::RegisterEncodeCompleteCallback( + webrtc::EncodedImageCallback* callback) { + RTC_LOG(LS_INFO) << "X264VideoEncoder::RegisterEncodeCompleteCallback"; + webrtc::MutexLock lock(&encoder_mutex_); + encoded_image_callback_ = callback; + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t X264VideoEncoder::Release() { + RTC_LOG(LS_INFO) << "X264VideoEncoder::Release"; + webrtc::MutexLock lock(&encoder_mutex_); + + DestroyEncoder(); + encoded_image_callback_ = nullptr; + initialized_ = false; + + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t X264VideoEncoder::Encode( + const webrtc::VideoFrame& frame, + const std::vector* frame_types) { + if (!initialized_) { + RTC_LOG(LS_ERROR) << "Encoder not initialized"; + return WEBRTC_VIDEO_CODEC_UNINITIALIZED; + } + + if (!encoded_image_callback_) { + RTC_LOG(LS_ERROR) << "No encode callback registered"; + return WEBRTC_VIDEO_CODEC_UNINITIALIZED; + } + + webrtc::MutexLock lock(&encoder_mutex_); + + if (first_frame_time_ms_ == -1) { + first_frame_time_ms_ = rtc::TimeMillis(); + } + + // Check for key frame request + bool force_key_frame = false; + if (frame_types && !frame_types->empty()) { + for (const auto& frame_type : *frame_types) { + if (frame_type == webrtc::VideoFrameType::kVideoFrameKey) { + force_key_frame = true; + break; + } + } + } + + if (!EncodeFrame(frame, force_key_frame)) { + RTC_LOG(LS_ERROR) << "Failed to encode frame"; + return WEBRTC_VIDEO_CODEC_ERROR; + } + + frame_count_++; + return WEBRTC_VIDEO_CODEC_OK; +} + +void X264VideoEncoder::SetRates(const RateControlParameters& parameters) { + RTC_LOG(LS_INFO) << "X264VideoEncoder::SetRates - bitrate: " + << parameters.bitrate.get_sum_bps() << " bps"; + + webrtc::MutexLock lock(&encoder_mutex_); + + if (!initialized_ || !encoder_) { + return; + } + + config_.target_bitrate_bps = parameters.bitrate.get_sum_bps(); + + // Update x264 encoder bitrate + x264_param_t param; + x264_encoder_parameters(encoder_, ¶m); + param.rc.i_bitrate = config_.target_bitrate_bps / 1000; // Convert to kbps + x264_encoder_reconfig(encoder_, ¶m); +} + +webrtc::VideoEncoder::EncoderInfo X264VideoEncoder::GetEncoderInfo() const { + webrtc::VideoEncoder::EncoderInfo info; + info.supports_native_handle = false; + info.implementation_name = "X264VideoEncoder"; + info.scaling_settings = webrtc::VideoEncoder::ScalingSettings::kOff; + info.supports_simulcast = false; + info.preferred_pixel_formats = {webrtc::VideoFrameBuffer::Type::kI420}; + return info; +} + +bool X264VideoEncoder::InitializeEncoder() { + if (encoder_) { + DestroyEncoder(); + } + + // Set default parameters + if (x264_param_default_preset(¶m_, "veryfast", "zerolatency") < 0) { + RTC_LOG(LS_ERROR) << "Failed to set x264 preset"; + return false; + } + + // Configure parameters for real-time encoding + param_.i_threads = 1; // Single thread for consistency + param_.i_width = config_.width; + param_.i_height = config_.height; + param_.i_fps_num = config_.max_framerate; + param_.i_fps_den = 1; + + // Rate control + param_.rc.i_rc_method = X264_RC_ABR; + param_.rc.i_bitrate = config_.target_bitrate_bps / 1000; // Convert to kbps + param_.rc.i_vbv_max_bitrate = param_.rc.i_bitrate * 1.2; // 20% overhead + param_.rc.i_vbv_buffer_size = param_.rc.i_bitrate; // 1 second buffer + + // Low latency settings + param_.i_keyint_max = config_.max_framerate * 2; // Key frame every 2 seconds + param_.i_keyint_min = config_.max_framerate / 2; // Minimum interval + param_.b_intra_refresh = 1; + param_.i_bframe = 0; // No B-frames for low latency + param_.b_cabac = 0; // Faster encoding + + // Quality settings + param_.analyse.i_me_method = X264_ME_DIA; // Fast motion estimation + param_.analyse.i_subpel_refine = 2; // Faster subpixel refinement + param_.analyse.b_mixed_references = 0; + param_.analyse.b_chroma_me = 0; + param_.analyse.b_fast_pskip = 1; + + // Apply profile constraints + if (x264_param_apply_profile(¶m_, "baseline") < 0) { + RTC_LOG(LS_ERROR) << "Failed to apply x264 profile"; + return false; + } + + // Open encoder + encoder_ = x264_encoder_open(¶m_); + if (!encoder_) { + RTC_LOG(LS_ERROR) << "Failed to open x264 encoder"; + return false; + } + + // Allocate picture + if (x264_picture_alloc(&pic_in_, X264_CSP_I420, config_.width, + config_.height) < 0) { + RTC_LOG(LS_ERROR) << "Failed to allocate x264 picture"; + x264_encoder_close(encoder_); + encoder_ = nullptr; + return false; + } + + RTC_LOG(LS_INFO) << "x264 encoder initialized successfully"; + return true; +} + +void X264VideoEncoder::DestroyEncoder() { + if (encoder_) { + x264_picture_clean(&pic_in_); + x264_encoder_close(encoder_); + encoder_ = nullptr; + } +} + +bool X264VideoEncoder::EncodeFrame(const webrtc::VideoFrame& frame, + bool force_key_frame) { + if (!encoder_) { + return false; + } + + // Get I420 buffer + rtc::scoped_refptr i420_buffer = + frame.video_frame_buffer()->ToI420(); + + if (!i420_buffer) { + RTC_LOG(LS_ERROR) << "Failed to convert frame to I420"; + return false; + } + + // Check dimensions + if (i420_buffer->width() != config_.width || + i420_buffer->height() != config_.height) { + RTC_LOG(LS_ERROR) << "Frame size mismatch: expected " << config_.width + << "x" << config_.height << ", got " + << i420_buffer->width() << "x" << i420_buffer->height(); + return false; + } + + // Copy frame data to x264 picture + pic_in_.img.i_csp = X264_CSP_I420; + pic_in_.img.i_plane = 3; + + // Y plane + pic_in_.img.plane[0] = const_cast(i420_buffer->DataY()); + pic_in_.img.i_stride[0] = i420_buffer->StrideY(); + + // U plane + pic_in_.img.plane[1] = const_cast(i420_buffer->DataU()); + pic_in_.img.i_stride[1] = i420_buffer->StrideU(); + + // V plane + pic_in_.img.plane[2] = const_cast(i420_buffer->DataV()); + pic_in_.img.i_stride[2] = i420_buffer->StrideV(); + + // Set frame properties + pic_in_.i_pts = frame_count_; + pic_in_.i_type = force_key_frame ? X264_TYPE_IDR : X264_TYPE_AUTO; + + // Encode frame + x264_nal_t* nals = nullptr; + int i_nals = 0; + int frame_size = + x264_encoder_encode(encoder_, &nals, &i_nals, &pic_in_, &pic_out_); + + if (frame_size < 0) { + RTC_LOG(LS_ERROR) << "x264_encoder_encode failed"; + return false; + } + + if (frame_size == 0) { + // No output frame (delayed) + return true; + } + + // Process encoded frame + bool is_key_frame = (pic_out_.i_type == X264_TYPE_IDR); + + // Combine all NAL units + std::vector encoded_data; + for (int i = 0; i < i_nals; i++) { + encoded_data.insert(encoded_data.end(), nals[i].p_payload, + nals[i].p_payload + nals[i].i_payload); + } + + // Create encoded image + webrtc::EncodedImage encoded_image; + encoded_image.SetEncodedData(webrtc::EncodedImageBuffer::Create( + encoded_data.data(), encoded_data.size())); + encoded_image._encodedWidth = config_.width; + encoded_image._encodedHeight = config_.height; + encoded_image.SetRtpTimestamp(static_cast(frame.timestamp_us())); + encoded_image.ntp_time_ms_ = frame.ntp_time_ms(); + encoded_image.capture_time_ms_ = frame.render_time_ms(); + encoded_image._frameType = is_key_frame + ? webrtc::VideoFrameType::kVideoFrameKey + : webrtc::VideoFrameType::kVideoFrameDelta; + encoded_image.content_type_ = webrtc::VideoContentType::UNSPECIFIED; + encoded_image.timing_.flags = webrtc::VideoSendTiming::kInvalid; + + // Send encoded frame + webrtc::CodecSpecificInfo codec_specific; + codec_specific.codecType = webrtc::kVideoCodecH264; + codec_specific.codecSpecific.H264.packetization_mode = + webrtc::H264PacketizationMode::NonInterleaved; + codec_specific.codecSpecific.H264.temporal_idx = webrtc::kNoTemporalIdx; + codec_specific.codecSpecific.H264.idr_frame = is_key_frame; + codec_specific.codecSpecific.H264.base_layer_sync = false; + + webrtc::EncodedImageCallback::Result result = + encoded_image_callback_->OnEncodedImage(encoded_image, &codec_specific); + + if (result.error != webrtc::EncodedImageCallback::Result::OK) { + RTC_LOG(LS_ERROR) << "Encode callback failed with error: " << result.error; + return false; + } + + RTC_LOG(LS_VERBOSE) << "Encoded frame " << frame_count_ << " (" + << (is_key_frame ? "key" : "delta") << ") - " + << encoded_data.size() << " bytes"; + + return true; +} + +std::unique_ptr CreateX264VideoEncoder() { + return std::make_unique(); +} + +} // namespace livekit + +#endif // defined(WEBRTC_USE_X264) && defined(WEBRTC_ANDROID) \ No newline at end of file From 4bc6ee3250f533411d64f72cec883588628cb12e Mon Sep 17 00:00:00 2001 From: Francisco Nadales Date: Fri, 3 Oct 2025 16:02:43 +0200 Subject: [PATCH 261/274] Enhance build scripts to support x264 video encoding and improve library path handling --- Cargo.lock | 64 ++++++---- livekit-ffi/generate_conan_build.bat | 4 +- livekit-ffi/generate_conan_build.sh | 15 ++- webrtc-sys/build.rs | 182 ++++++++++++++++++++++++++- webrtc-sys/build/src/lib.rs | 11 +- 5 files changed, 241 insertions(+), 35 deletions(-) mode change 100644 => 100755 livekit-ffi/generate_conan_build.sh diff --git a/Cargo.lock b/Cargo.lock index 07ff1662d..1faf3287f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -630,15 +630,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" -[[package]] -name = "copy_dir" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "543d1dd138ef086e2ff05e3a48cf9da045da2033d16f8538fd76b86cd49b2ca3" -dependencies = [ - "walkdir", -] - [[package]] name = "core-foundation" version = "0.9.4" @@ -967,14 +958,14 @@ checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "filetime" -version = "0.2.23" +version = "0.2.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" +checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" dependencies = [ "cfg-if", "libc", - "redox_syscall", - "windows-sys 0.52.0", + "libredox", + "windows-sys 0.60.2", ] [[package]] @@ -1033,12 +1024,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "fs_extra" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" - [[package]] name = "futures" version = "0.3.30" @@ -1659,6 +1644,17 @@ dependencies = [ "windows-targets 0.52.0", ] +[[package]] +name = "libredox" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +dependencies = [ + "bitflags 2.4.1", + "libc", + "redox_syscall 0.5.17", +] + [[package]] name = "libwebrtc" version = "0.3.15" @@ -2068,7 +2064,7 @@ dependencies = [ "cfg-if", "libc", "petgraph", - "redox_syscall", + "redox_syscall 0.4.1", "smallvec", "thread-id", "windows-targets 0.48.5", @@ -2445,6 +2441,15 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_syscall" +version = "0.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +dependencies = [ + "bitflags 2.4.1", +] + [[package]] name = "regex" version = "1.10.2" @@ -2916,9 +2921,9 @@ dependencies = [ [[package]] name = "tar" -version = "0.4.43" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c65998313f8e17d0d553d28f91a0df93e4dbbbf770279c7bc21ca0f09ea1a1f6" +checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a" dependencies = [ "filetime", "libc", @@ -2933,7 +2938,7 @@ checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" dependencies = [ "cfg-if", "fastrand 2.0.1", - "redox_syscall", + "redox_syscall 0.4.1", "rustix 0.38.28", "windows-sys 0.48.0", ] @@ -3555,8 +3560,8 @@ name = "webrtc-sys-build" version = "0.3.8" dependencies = [ "anyhow", + "flate2", "fs2", - "fs_extra", "regex", "reqwest", "scratch", @@ -3914,6 +3919,17 @@ dependencies = [ "bitflags 2.4.1", ] +[[package]] +name = "xattr" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914566e6413e7fa959cc394fb30e563ba80f3541fbd40816d4c05a0fc3f2a0f1" +dependencies = [ + "libc", + "linux-raw-sys 0.4.12", + "rustix 0.38.28", +] + [[package]] name = "yuv-sys" version = "0.3.9" diff --git a/livekit-ffi/generate_conan_build.bat b/livekit-ffi/generate_conan_build.bat index d6cef0d4e..3e9e847ac 100644 --- a/livekit-ffi/generate_conan_build.bat +++ b/livekit-ffi/generate_conan_build.bat @@ -66,12 +66,12 @@ if not defined ANDROID_NDK_HOME ( :: Build armv8 (arm64) cargo clean -cargo ndk --bindgen --target aarch64-linux-android build --release --no-default-features --features "rustls-tls-webpki-roots" +cargo ndk --target aarch64-linux-android build --release --no-default-features --features "rustls-tls-webpki-roots,webrtc-sys/use_x264" call :create_folder_structure "aarch64-linux-android" :: Build armv7 (32-bit) cargo clean -cargo ndk --bindgen --target armv7-linux-androideabi build --release --no-default-features --features "rustls-tls-webpki-roots" +cargo ndk --target armv7-linux-androideabi build --release --no-default-features --features "rustls-tls-webpki-roots,webrtc-sys/x264" call :create_folder_structure "armv7-linux-androideabi" exit /b 0 diff --git a/livekit-ffi/generate_conan_build.sh b/livekit-ffi/generate_conan_build.sh old mode 100644 new mode 100755 index a5bb0f996..d04cebdd4 --- a/livekit-ffi/generate_conan_build.sh +++ b/livekit-ffi/generate_conan_build.sh @@ -20,18 +20,21 @@ build_android() { exit 1 fi - ln -sf "$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/lib/aarch64-unknown-linux-musl/{libunwind.so,libc++abi.a}" \ - "$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/lib/" + # Override the incorrect library search paths from configure_android_sysroot for aarch64 + export RUSTFLAGS="-L ${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/aarch64-linux-android" # Build armv8 (arm64) cargo clean - cargo ndk --bindgen --target aarch64-linux-android build --release --no-default-features --features "rustls-tls-webpki-roots" + cargo ndk --target aarch64-linux-android build --release --no-default-features --features "rustls-tls-webpki-roots,webrtc-sys/use_x264" create_folder_structure "aarch64-linux-android" + # Override the incorrect library search paths from configure_android_sysroot for armv7 + # export RUSTFLAGS="-L ${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/arm-linux-androideabi" + # Build armv7 (32-bit) - cargo clean - cargo ndk --bindgen --target armv7-linux-androideabi build --release --no-default-features --features "rustls-tls-webpki-roots" - create_folder_structure "armv7-linux-androideabi" + #cargo clean + #cargo ndk --target armv7-linux-androideabi build --release --no-default-features --features "rustls-tls-webpki-roots,webrtc-sys/use_x264" + #create_folder_structure "armv7-linux-androideabi" } create_folder_structure() { diff --git a/webrtc-sys/build.rs b/webrtc-sys/build.rs index 03bdbd700..68fe42f05 100644 --- a/webrtc-sys/build.rs +++ b/webrtc-sys/build.rs @@ -153,6 +153,12 @@ fn main() { println!("cargo:rustc-link-lib=dylib=pthread"); println!("cargo:rustc-link-lib=dylib=m"); + #[cfg(feature = "use_x264")] + { + // x264 on Linux also needs math library + println!("cargo:rustc-link-lib=dylib=m"); + } + match target_arch.as_str() { "x86_64" => { #[cfg(feature = "use_vaapi")] @@ -186,11 +192,25 @@ fn main() { .file("src/nvidia/implib/libnvcuvid.so.tramp.S") .flag("-Wno-deprecated-declarations") .flag("-DUSE_NVIDIA_VIDEO_CODEC=1"); + + #[cfg(feature = "use_x264")] + { + let (x264_include, x264_lib) = setup_x264(); + + builder + .flag("-DWEBRTC_USE_X264=1") + .file("src/x264/x264_video_encoder.cpp") + .include(&x264_include); + + let x264_lib_abs = x264_lib.canonicalize().unwrap_or_else(|_| x264_lib.clone()); + println!("cargo:rustc-link-search=native={}", x264_lib_abs.display()); + println!("cargo:rustc-link-lib=static=x264"); + } } _ => {} } - builder.flag("-Wno-changes-meaning").flag("-std=c++20"); + builder.flag("-Wno-changes-meaning").flag("-std=c++2a"); } "macos" => { println!("cargo:rustc-link-lib=framework=Foundation"); @@ -252,7 +272,26 @@ fn main() { configure_android_sysroot(&mut builder); - builder.file("src/android.cpp").flag("-std=c++20").cpp_link_stdlib("c++_static"); + #[cfg(feature = "use_x264")] + { + let (x264_include, x264_lib) = setup_x264(); + + builder + .flag("-DWEBRTC_USE_X264=1") + .file("src/x264/x264_video_encoder.cpp") + .include(&x264_include); + + let x264_lib_abs = x264_lib.canonicalize().unwrap_or_else(|_| x264_lib.clone()); + println!("cargo:rustc-link-search=native={}", x264_lib_abs.display()); + println!("cargo:rustc-link-lib=static=x264"); + } + + builder + .file("src/android.cpp") + .file("src/android/video_encoder_factory.cpp") + .file("src/android/video_decoder_factory.cpp") + .flag("-std=c++2a") + .cpp_link_stdlib("c++_static"); } _ => { panic!("Unsupported target, {}", target_os); @@ -275,6 +314,9 @@ fn main() { println!("cargo:rerun-if-changed={}", entry.unwrap().display()); } + // Rebuild if x264 submodule changes + println!("cargo:rerun-if-changed=third_party/x264"); + if target_os.as_str() == "android" { copy_libwebrtc_jar(&PathBuf::from(Path::new(&webrtc_dir))); } @@ -338,6 +380,142 @@ fn configure_darwin_sysroot(builder: &mut cc::Build) { builder.flag(format!("-isysroot{}", sysroot).as_str()); } +fn setup_x264() -> (PathBuf, PathBuf) { + let x264_dir = Path::new("third_party/x264"); + + if x264_dir.exists() { + // Use both headers and library from submodule + let x264_include = x264_dir.to_path_buf(); + let x264_lib = x264_dir.to_path_buf(); + + // Check target architecture to determine the correct library + let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap(); + let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap(); + + let x264_static_lib = match (target_os.as_str(), target_arch.as_str()) { + ("android", "aarch64") => x264_lib.join("libx264-android-aarch64.a"), + ("android", "arm") => x264_lib.join("libx264-android-armv7.a"), + ("linux", "x86_64") => x264_lib.join("libx264.a"), + _ => x264_lib.join("libx264.a"), + }; + + if !x264_static_lib.exists() { + println!("cargo:warning=x264 library {:?} not found, building x264 for target {}...", + x264_static_lib, format!("{}-{}", target_os, target_arch)); + + // Configure for cross-compilation if building for Android + if target_os == "android" { + configure_and_build_x264_android(&x264_dir, &target_arch, &x264_lib, &x264_static_lib); + } else { + // Build x264 from source for other targets + let mut build_cmd = std::process::Command::new("make"); + build_cmd.current_dir(&x264_dir); + build_cmd.arg("-j").arg(env::var("NUM_JOBS").unwrap_or_else(|_| "4".to_string())); + + let output = build_cmd.output().expect("Failed to build x264"); + + if !output.status.success() { + panic!("Failed to build x264: {}", String::from_utf8_lossy(&output.stderr)); + } + } + } + + println!("cargo:warning=Using x264 submodule headers and library for {}-{}", target_os, target_arch); + (x264_include, x264_lib) + } else { + panic!("x264 submodule not found at third_party/x264. Please ensure the submodule is initialized and checked out."); + } +} + +fn configure_and_build_x264_android(x264_dir: &Path, target_arch: &str, x264_lib: &PathBuf, x264_static_lib: &PathBuf) { + let ndk_home = env::var("ANDROID_NDK_HOME").expect("ANDROID_NDK_HOME must be set for Android builds"); + let toolchain_bin = format!("{}/toolchains/llvm/prebuilt/linux-x86_64/bin", ndk_home); + + let (host_triple, cc_name, ar_name, cc_env_var, ar_env_var) = match target_arch { + "aarch64" => ( + "aarch64-linux-android", + "aarch64-linux-android28-clang", + "llvm-ar", + "CC_aarch64_linux_android", + "AR_aarch64_linux_android" + ), + "arm" => ( + "arm-linux-androideabi", + "armv7a-linux-androideabi28-clang", + "llvm-ar", + "CC_armv7_linux_androideabi", + "AR_armv7_linux_androideabi" + ), + _ => panic!("Unsupported Android architecture: {}", target_arch), + }; + + let cc = env::var(cc_env_var).unwrap_or_else(|_| cc_name.to_string()); + let ar = env::var(ar_env_var).unwrap_or_else(|_| ar_name.to_string()); + + // Clean any previous build artifacts + let _ = std::process::Command::new("make") + .current_dir(&x264_dir) + .arg("clean") + .output(); + + // Configure x264 for Android cross-compilation + let mut configure_cmd = std::process::Command::new("./configure"); + configure_cmd + .current_dir(&x264_dir) + .arg(format!("--host={}", host_triple)) + .arg("--enable-static") + .arg("--disable-cli") + .arg("--disable-asm") // Disable assembly optimizations to avoid toolchain issues + .arg("--enable-pic") // Enable position independent code + .env("CC", &cc) + .env("AR", &ar) + .env("PATH", format!("{}:{}", toolchain_bin, env::var("PATH").unwrap_or_default())); + + // Add additional flags for ARMv7 + if target_arch == "arm" { + configure_cmd + .arg("--disable-thread") // Disable threading for better compatibility on older ARM devices + .env("CFLAGS", "-march=armv7-a -mfloat-abi=softfp -mfpu=neon"); + } + + let configure_output = configure_cmd + .output() + .expect("Failed to configure x264 for Android"); + + if !configure_output.status.success() { + println!("cargo:warning=x264 configure stdout: {}", String::from_utf8_lossy(&configure_output.stdout)); + println!("cargo:warning=x264 configure stderr: {}", String::from_utf8_lossy(&configure_output.stderr)); + panic!("Failed to configure x264 for Android {}: {}", target_arch, String::from_utf8_lossy(&configure_output.stderr)); + } + + // Build x264 + let mut build_cmd = std::process::Command::new("make"); + build_cmd + .current_dir(&x264_dir) + .arg("-j").arg(env::var("NUM_JOBS").unwrap_or_else(|_| "4".to_string())) + .env("CC", &cc) + .env("AR", &ar) + .env("PATH", format!("{}:{}", toolchain_bin, env::var("PATH").unwrap_or_default())); + + let output = build_cmd.output().expect("Failed to build x264"); + + if !output.status.success() { + println!("cargo:warning=x264 build stdout: {}", String::from_utf8_lossy(&output.stdout)); + println!("cargo:warning=x264 build stderr: {}", String::from_utf8_lossy(&output.stderr)); + panic!("Failed to build x264 for Android {}: {}", target_arch, String::from_utf8_lossy(&output.stderr)); + } + + // Copy the built library to the target-specific name + let source_lib = x264_lib.join("libx264.a"); + if source_lib.exists() { + std::fs::copy(&source_lib, &x264_static_lib) + .expect("Failed to copy x264 library to target-specific name"); + println!("cargo:warning=Successfully built and copied x264 library for Android {}", target_arch); + } else { + panic!("x264 library was not created at expected location: {:?}", source_lib); + } +} + fn configure_android_sysroot(builder: &mut cc::Build) { let toolchain = webrtc_sys_build::android_ndk_toolchain().unwrap(); let toolchain_lib = toolchain.join("lib"); diff --git a/webrtc-sys/build/src/lib.rs b/webrtc-sys/build/src/lib.rs index 2657d83f3..b2e75057b 100644 --- a/webrtc-sys/build/src/lib.rs +++ b/webrtc-sys/build/src/lib.rs @@ -90,7 +90,16 @@ pub fn use_debug() -> bool { /// The location of the custom build is defined by the user pub fn custom_dir() -> Option { if let Ok(path) = env::var("LK_CUSTOM_WEBRTC") { - return Some(path::PathBuf::from(path)); + let path_buf = path::PathBuf::from(path); + + // If the path is relative, resolve it relative to CARGO_MANIFEST_DIR + if path_buf.is_relative() { + if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") { + return Some(path::PathBuf::from(manifest_dir).join(path_buf)); + } + } + + return Some(path_buf); } None } From f2157ce895923e472ee2d3eac40430b5f65d5448 Mon Sep 17 00:00:00 2001 From: Juan Date: Thu, 9 Oct 2025 16:05:20 +0200 Subject: [PATCH 262/274] Update script to read lk_custom_webrtc from param. Mandatory --- livekit-ffi/generate_conan_build.sh | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/livekit-ffi/generate_conan_build.sh b/livekit-ffi/generate_conan_build.sh index d04cebdd4..d6b540ecd 100755 --- a/livekit-ffi/generate_conan_build.sh +++ b/livekit-ffi/generate_conan_build.sh @@ -3,8 +3,8 @@ set -e # Exit immediately on error usage() { - echo "Usage: $0 --platform " - echo "Example: $0 --platform windows" + echo "Usage: $0 --platform --lk_custom_webrtc " + echo "Example: $0 --platform windows --lk_custom_webrtc /path/to/webrtc" exit 1 } @@ -79,15 +79,20 @@ create_folder_structure() { } main() { - if [ $# -ne 2 ]; then usage; fi + if [ $# -lt 4 ]; then usage; fi local platform="" + local lk_custom_webrtc="" while [[ $# -gt 0 ]]; do case $1 in --platform) platform="$2" shift 2 ;; + --lk_custom_webrtc) + lk_custom_webrtc="$2" + shift 2 + ;; *) usage ;; @@ -99,6 +104,13 @@ main() { cd "$script_path" + if [[ -z "$lk_custom_webrtc" ]]; then + echo "Error: --lk_custom_webrtc is required." + usage + fi + + export LK_CUSTOM_WEBRTC="$lk_custom_webrtc" + case $platform in windows) build_windows From 1b020d883fef43b175ca7c51b190d710f11a6da9 Mon Sep 17 00:00:00 2001 From: Juan Date: Thu, 9 Oct 2025 16:14:02 +0200 Subject: [PATCH 263/274] Update script (.bat) to read lk_custom_webrtc from param. Mandatory --- livekit-ffi/generate_conan_build.bat | 33 ++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/livekit-ffi/generate_conan_build.bat b/livekit-ffi/generate_conan_build.bat index 3e9e847ac..8af7e3186 100644 --- a/livekit-ffi/generate_conan_build.bat +++ b/livekit-ffi/generate_conan_build.bat @@ -11,18 +11,47 @@ if "%~1"=="" ( :: Manejo de argumentos set "platform=" +set "lk_custom_webrtc=" + +:parse_args +if "%~1"=="" goto :after_parse if /i "%~1"=="--platform" ( if "%~2"=="" ( echo Error: No platform specified. goto :usage ) set "platform=%~2" + shift + shift + goto :parse_args +) else if /i "%~1"=="--lk_custom_webrtc" ( + if "%~2"=="" ( + echo Error: No lk_custom_webrtc path specified. + goto :usage + ) + set "lk_custom_webrtc=%~2" + shift + shift + goto :parse_args ) else ( echo Error: Invalid argument %~1 goto :usage ) +:after_parse + +if not defined platform ( + echo Error: --platform is required. + goto :usage +) +if not defined lk_custom_webrtc ( + echo Error: --lk_custom_webrtc is required. + goto :usage +) + echo Selected platform: %platform% +set "LK_CUSTOM_WEBRTC=%lk_custom_webrtc%" +echo LK_CUSTOM_WEBRTC: %LK_CUSTOM_WEBRTC% :: Guardar el directorio actual set "initial_path=%CD%" @@ -44,8 +73,8 @@ exit /b 0 :: Función de ayuda :usage -echo Usage: %~nx0 --platform ^ -echo Example: %~nx0 --platform windows +echo Usage: %~nx0 --platform ^ --lk_custom_webrtc ^ +echo Example: %~nx0 --platform windows --lk_custom_webrtc C:\path\to\webrtc exit /b 1 :: Función para compilar en Windows From dff847e553d09345826ed190a3a378f4785a13b2 Mon Sep 17 00:00:00 2001 From: Francisco Nadales Date: Thu, 9 Oct 2025 16:30:11 +0200 Subject: [PATCH 264/274] Refactor build scripts and configurations for WebRTC and x264 integration; enhance package structure creation and update .gitignore --- .gitignore | 11 +- livekit-ffi/.cargo/{config => config.toml} | 2 +- livekit-ffi/cbindgen.toml | 11 ++ livekit-ffi/conan_assets/conanfile.py | 2 +- livekit-ffi/generate_conan_build.sh | 21 ++-- livekit-ffi/include/livekit_ffi.h | 32 ++++-- webrtc-sys/build.rs | 104 ++++++++++++------ webrtc-sys/libwebrtc/build_android.sh | 37 +++++-- ...ckage_structure.sh => webrtc_structure.sh} | 0 9 files changed, 152 insertions(+), 68 deletions(-) rename livekit-ffi/.cargo/{config => config.toml} (77%) rename webrtc-sys/libwebrtc/{create_package_structure.sh => webrtc_structure.sh} (100%) diff --git a/.gitignore b/.gitignore index 7c5485b20..4abc71c34 100644 --- a/.gitignore +++ b/.gitignore @@ -7,11 +7,14 @@ soxr-sys/test-output.wav # Exclude webrtc build artifacts **/.gcs_entries +# Exclude webrtc source directories +**/libwebrtc/src + # Exclude webrtc build directories -**/android-arm-release -**/android-arm64-release -**/android-arm-debug -**/android-arm64-debug +**/libwebrtc/android-arm-release +**/libwebrtc/android-arm64-release +**/libwebrtc/android-arm-debug +**/libwebrtc/android-arm64-debug # Exclude vscode settings **/.vscode/ diff --git a/livekit-ffi/.cargo/config b/livekit-ffi/.cargo/config.toml similarity index 77% rename from livekit-ffi/.cargo/config rename to livekit-ffi/.cargo/config.toml index 3c47242ba..5b4457f13 100644 --- a/livekit-ffi/.cargo/config +++ b/livekit-ffi/.cargo/config.toml @@ -1,4 +1,4 @@ [profile.release] lto = true opt-level = "z" -strip = true \ No newline at end of file +strip = true diff --git a/livekit-ffi/cbindgen.toml b/livekit-ffi/cbindgen.toml index 4447e7274..d2bd94aff 100644 --- a/livekit-ffi/cbindgen.toml +++ b/livekit-ffi/cbindgen.toml @@ -2,3 +2,14 @@ language = "C++" include_guard = "livekit_ffi" autogen_warning = "/* Warning, this file is autogenerated. Don't modify this manually. */" usize_is_size_t = true + +# Exclude Android/JNI specific modules and functions +[parse] +parse_deps = false +clean = false +exclude = ["android", "JNI_OnLoad", "JavaVM", "jint", "JNI_VERSION_1_6"] + +# Only include explicitly marked items for export +[parse.expand] +all_features = false +default_features = false diff --git a/livekit-ffi/conan_assets/conanfile.py b/livekit-ffi/conan_assets/conanfile.py index eede19f74..d63c5802e 100644 --- a/livekit-ffi/conan_assets/conanfile.py +++ b/livekit-ffi/conan_assets/conanfile.py @@ -45,7 +45,7 @@ def package(self): self.copy("*.so", dst=dst_lib_folder, src=lib_folder, keep_path=False) self.copy("*.jar", dst=dst_lib_folder, src=lib_folder, keep_path=False) - if self.settings.os == "Windows": + elif self.settings.os == "Windows": lib_folder = "lib/windows" dst_lib_folder = "lib" self.copy("*.lib", dst=dst_lib_folder, src=lib_folder, keep_path=False) diff --git a/livekit-ffi/generate_conan_build.sh b/livekit-ffi/generate_conan_build.sh index d04cebdd4..b65598926 100755 --- a/livekit-ffi/generate_conan_build.sh +++ b/livekit-ffi/generate_conan_build.sh @@ -20,21 +20,24 @@ build_android() { exit 1 fi - # Override the incorrect library search paths from configure_android_sysroot for aarch64 - export RUSTFLAGS="-L ${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/aarch64-linux-android" + # Generate the C header file using cbindgen + cbindgen --config cbindgen.toml --crate livekit-ffi --output include/livekit_ffi.h - # Build armv8 (arm64) + # Path to the custom WebRTC build for Android arm64 + export LK_CUSTOM_WEBRTC="$(pwd)/../webrtc-sys/libwebrtc/android-arm64-release" + + # Build armv8 (arm64) - aligned with Qt's arm64-v8a cargo clean - cargo ndk --target aarch64-linux-android build --release --no-default-features --features "rustls-tls-webpki-roots,webrtc-sys/use_x264" + cargo ndk --target aarch64-linux-android build --release --platform 21 --no-default-features --features "rustls-tls-webpki-roots,webrtc-sys/use_x264" create_folder_structure "aarch64-linux-android" - # Override the incorrect library search paths from configure_android_sysroot for armv7 - # export RUSTFLAGS="-L ${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/arm-linux-androideabi" + # Path to the custom WebRTC build for Android arm + export LK_CUSTOM_WEBRTC="$(pwd)/../webrtc-sys/libwebrtc/android-arm-release" # Build armv7 (32-bit) - #cargo clean - #cargo ndk --target armv7-linux-androideabi build --release --no-default-features --features "rustls-tls-webpki-roots,webrtc-sys/use_x264" - #create_folder_structure "armv7-linux-androideabi" + cargo clean + cargo ndk --target armv7-linux-androideabi build --release --platform 21 --no-default-features --features "rustls-tls-webpki-roots,webrtc-sys/use_x264" + create_folder_structure "armv7-linux-androideabi" } create_folder_structure() { diff --git a/livekit-ffi/include/livekit_ffi.h b/livekit-ffi/include/livekit_ffi.h index 5ad11ee66..67e7922d5 100644 --- a/livekit-ffi/include/livekit_ffi.h +++ b/livekit-ffi/include/livekit_ffi.h @@ -7,22 +7,40 @@ #include #include #include -#include #include +#include + +constexpr static const size_t BATCH_SIZE = 32; + +/// # SAFTEY: The "C" callback must be threadsafe and not block +using FfiCallbackFn = void (*)(const uint8_t *, size_t); using FfiHandleId = uint64_t; -using FfiCallbackFn = void (*)(const uint8_t*, size_t); constexpr static const FfiHandleId INVALID_HANDLE = 0; -extern "C" { +extern "C" +{ + + /// # Safety + /// + /// The foreign language must only provide valid pointers + void livekit_ffi_initialize(FfiCallbackFn cb, + bool capture_logs, + const char *sdk, + const char *sdk_version); -void livekit_ffi_initialize(FfiCallbackFn cb, bool capture_logs); + /// # Safety + /// + /// The foreign language must only provide valid pointers + FfiHandleId livekit_ffi_request(const uint8_t *data, + size_t len, + const uint8_t **res_ptr, + size_t *res_len); -FfiHandleId livekit_ffi_request(const uint8_t *data, size_t len, - const uint8_t **res_ptr, size_t *res_len); + bool livekit_ffi_drop_handle(FfiHandleId handle_id); -bool livekit_ffi_drop_handle(FfiHandleId handle_id); + void livekit_ffi_dispose(); } // extern "C" diff --git a/webrtc-sys/build.rs b/webrtc-sys/build.rs index 68fe42f05..12392be97 100644 --- a/webrtc-sys/build.rs +++ b/webrtc-sys/build.rs @@ -202,7 +202,8 @@ fn main() { .file("src/x264/x264_video_encoder.cpp") .include(&x264_include); - let x264_lib_abs = x264_lib.canonicalize().unwrap_or_else(|_| x264_lib.clone()); + let x264_lib_abs = + x264_lib.canonicalize().unwrap_or_else(|_| x264_lib.clone()); println!("cargo:rustc-link-search=native={}", x264_lib_abs.display()); println!("cargo:rustc-link-lib=static=x264"); } @@ -210,7 +211,7 @@ fn main() { _ => {} } - builder.flag("-Wno-changes-meaning").flag("-std=c++2a"); + builder.flag("-Wno-changes-meaning").flag("-std=c++20"); } "macos" => { println!("cargo:rustc-link-lib=framework=Foundation"); @@ -290,8 +291,9 @@ fn main() { .file("src/android.cpp") .file("src/android/video_encoder_factory.cpp") .file("src/android/video_decoder_factory.cpp") - .flag("-std=c++2a") - .cpp_link_stdlib("c++_static"); + .flag("-std=c++20") + // Changed from c++_static to c++_shared for Qt compatibility + .cpp_link_stdlib("c++_shared"); } _ => { panic!("Unsupported target, {}", target_os); @@ -391,7 +393,7 @@ fn setup_x264() -> (PathBuf, PathBuf) { // Check target architecture to determine the correct library let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap(); let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap(); - + let x264_static_lib = match (target_os.as_str(), target_arch.as_str()) { ("android", "aarch64") => x264_lib.join("libx264-android-aarch64.a"), ("android", "arm") => x264_lib.join("libx264-android-armv7.a"), @@ -400,18 +402,26 @@ fn setup_x264() -> (PathBuf, PathBuf) { }; if !x264_static_lib.exists() { - println!("cargo:warning=x264 library {:?} not found, building x264 for target {}...", - x264_static_lib, format!("{}-{}", target_os, target_arch)); - + println!( + "cargo:warning=x264 library {:?} not found, building x264 for target {}...", + x264_static_lib, + format!("{}-{}", target_os, target_arch) + ); + // Configure for cross-compilation if building for Android if target_os == "android" { - configure_and_build_x264_android(&x264_dir, &target_arch, &x264_lib, &x264_static_lib); + configure_and_build_x264_android( + &x264_dir, + &target_arch, + &x264_lib, + &x264_static_lib, + ); } else { // Build x264 from source for other targets let mut build_cmd = std::process::Command::new("make"); build_cmd.current_dir(&x264_dir); build_cmd.arg("-j").arg(env::var("NUM_JOBS").unwrap_or_else(|_| "4".to_string())); - + let output = build_cmd.output().expect("Failed to build x264"); if !output.status.success() { @@ -420,44 +430,50 @@ fn setup_x264() -> (PathBuf, PathBuf) { } } - println!("cargo:warning=Using x264 submodule headers and library for {}-{}", target_os, target_arch); + println!( + "cargo:warning=Using x264 submodule headers and library for {}-{}", + target_os, target_arch + ); (x264_include, x264_lib) } else { panic!("x264 submodule not found at third_party/x264. Please ensure the submodule is initialized and checked out."); } } -fn configure_and_build_x264_android(x264_dir: &Path, target_arch: &str, x264_lib: &PathBuf, x264_static_lib: &PathBuf) { - let ndk_home = env::var("ANDROID_NDK_HOME").expect("ANDROID_NDK_HOME must be set for Android builds"); +fn configure_and_build_x264_android( + x264_dir: &Path, + target_arch: &str, + x264_lib: &PathBuf, + x264_static_lib: &PathBuf, +) { + let ndk_home = + env::var("ANDROID_NDK_HOME").expect("ANDROID_NDK_HOME must be set for Android builds"); let toolchain_bin = format!("{}/toolchains/llvm/prebuilt/linux-x86_64/bin", ndk_home); - + let (host_triple, cc_name, ar_name, cc_env_var, ar_env_var) = match target_arch { "aarch64" => ( "aarch64-linux-android", "aarch64-linux-android28-clang", "llvm-ar", "CC_aarch64_linux_android", - "AR_aarch64_linux_android" + "AR_aarch64_linux_android", ), "arm" => ( "arm-linux-androideabi", "armv7a-linux-androideabi28-clang", "llvm-ar", "CC_armv7_linux_androideabi", - "AR_armv7_linux_androideabi" + "AR_armv7_linux_androideabi", ), _ => panic!("Unsupported Android architecture: {}", target_arch), }; let cc = env::var(cc_env_var).unwrap_or_else(|_| cc_name.to_string()); let ar = env::var(ar_env_var).unwrap_or_else(|_| ar_name.to_string()); - + // Clean any previous build artifacts - let _ = std::process::Command::new("make") - .current_dir(&x264_dir) - .arg("clean") - .output(); - + let _ = std::process::Command::new("make").current_dir(&x264_dir).arg("clean").output(); + // Configure x264 for Android cross-compilation let mut configure_cmd = std::process::Command::new("./configure"); configure_cmd @@ -465,8 +481,8 @@ fn configure_and_build_x264_android(x264_dir: &Path, target_arch: &str, x264_lib .arg(format!("--host={}", host_triple)) .arg("--enable-static") .arg("--disable-cli") - .arg("--disable-asm") // Disable assembly optimizations to avoid toolchain issues - .arg("--enable-pic") // Enable position independent code + .arg("--disable-asm") // Disable assembly optimizations to avoid toolchain issues + .arg("--enable-pic") // Enable position independent code .env("CC", &cc) .env("AR", &ar) .env("PATH", format!("{}:{}", toolchain_bin, env::var("PATH").unwrap_or_default())); @@ -474,43 +490,59 @@ fn configure_and_build_x264_android(x264_dir: &Path, target_arch: &str, x264_lib // Add additional flags for ARMv7 if target_arch == "arm" { configure_cmd - .arg("--disable-thread") // Disable threading for better compatibility on older ARM devices + .arg("--disable-thread") // Disable threading for better compatibility on older ARM devices .env("CFLAGS", "-march=armv7-a -mfloat-abi=softfp -mfpu=neon"); } - let configure_output = configure_cmd - .output() - .expect("Failed to configure x264 for Android"); + let configure_output = configure_cmd.output().expect("Failed to configure x264 for Android"); if !configure_output.status.success() { - println!("cargo:warning=x264 configure stdout: {}", String::from_utf8_lossy(&configure_output.stdout)); - println!("cargo:warning=x264 configure stderr: {}", String::from_utf8_lossy(&configure_output.stderr)); - panic!("Failed to configure x264 for Android {}: {}", target_arch, String::from_utf8_lossy(&configure_output.stderr)); + println!( + "cargo:warning=x264 configure stdout: {}", + String::from_utf8_lossy(&configure_output.stdout) + ); + println!( + "cargo:warning=x264 configure stderr: {}", + String::from_utf8_lossy(&configure_output.stderr) + ); + panic!( + "Failed to configure x264 for Android {}: {}", + target_arch, + String::from_utf8_lossy(&configure_output.stderr) + ); } // Build x264 let mut build_cmd = std::process::Command::new("make"); build_cmd .current_dir(&x264_dir) - .arg("-j").arg(env::var("NUM_JOBS").unwrap_or_else(|_| "4".to_string())) + .arg("-j") + .arg(env::var("NUM_JOBS").unwrap_or_else(|_| "4".to_string())) .env("CC", &cc) .env("AR", &ar) .env("PATH", format!("{}:{}", toolchain_bin, env::var("PATH").unwrap_or_default())); - + let output = build_cmd.output().expect("Failed to build x264"); if !output.status.success() { println!("cargo:warning=x264 build stdout: {}", String::from_utf8_lossy(&output.stdout)); println!("cargo:warning=x264 build stderr: {}", String::from_utf8_lossy(&output.stderr)); - panic!("Failed to build x264 for Android {}: {}", target_arch, String::from_utf8_lossy(&output.stderr)); + panic!( + "Failed to build x264 for Android {}: {}", + target_arch, + String::from_utf8_lossy(&output.stderr) + ); } - + // Copy the built library to the target-specific name let source_lib = x264_lib.join("libx264.a"); if source_lib.exists() { std::fs::copy(&source_lib, &x264_static_lib) .expect("Failed to copy x264 library to target-specific name"); - println!("cargo:warning=Successfully built and copied x264 library for Android {}", target_arch); + println!( + "cargo:warning=Successfully built and copied x264 library for Android {}", + target_arch + ); } else { panic!("x264 library was not created at expected location: {:?}", source_lib); } diff --git a/webrtc-sys/libwebrtc/build_android.sh b/webrtc-sys/libwebrtc/build_android.sh index a2d66df4c..de27fab17 100755 --- a/webrtc-sys/libwebrtc/build_android.sh +++ b/webrtc-sys/libwebrtc/build_android.sh @@ -178,7 +178,7 @@ echo "Running package migration..." # --- create new package structure for migrated files --- echo "Running package structure creation..." -"$COMMAND_DIR/create_package_structure.sh" src +"$COMMAND_DIR/webrtc_structure.sh" src # --- build setup --- mkdir -p "$ARTIFACTS_DIR/lib" @@ -203,6 +203,8 @@ args="is_debug=$debug \ enable_stripping=true \ rtc_use_h264=false \ rtc_use_pipewire=false \ + libyuv_use_sme=false \ + proprietary_codecs=true \ symbol_level=0 \ enable_iterator_debugging=false \ use_rtti=true" @@ -220,24 +222,39 @@ autoninja -C "$OUTPUT_DIR" :default \ sdk/android:libwebrtc \ sdk/android:libjingle_peerconnection_so -# --- archive static lib (exclude nasm objs) --- -ar -rc "$ARTIFACTS_DIR/lib/libwebrtc.a" \ - $(find "$OUTPUT_DIR/obj" -name '*.o' -not -path "*/third_party/nasm/*") - -# --- (REMOVED) shadow packaging --- -# Previously: ./shadow_jar.sh ... org.webrtc -> livekit.org.webrtc -# Not needed; Java sources are already migrated pre-build. - # --- licenses --- python3 "./src/tools_webrtc/libs/generate_licenses.py" \ --target :default "$OUTPUT_DIR" "$OUTPUT_DIR" +# --- Strip org.jni_zero symbols from libwebrtc.jar --- +echo "Stripping org.jni_zero symbols from libwebrtc.jar..." +temp_jar_dir="$(mktemp -d)" +pushd "$temp_jar_dir" >/dev/null + +# Extract the JAR +jar -xf "$OUTPUT_DIR/lib.java/sdk/android/libwebrtc.jar" + +# Remove org.jni_zero classes +if [[ -d "org/jni_zero" ]]; then + echo " Removing org/jni_zero directory..." + rm -rf "org/jni_zero" +fi + +# Recreate the JAR without org.jni_zero +jar -cf "$ARTIFACTS_DIR/libwebrtc.jar" . + +popd >/dev/null +rm -rf "$temp_jar_dir" +echo " ✓ Stripped org.jni_zero symbols from libwebrtc.jar" + # --- copy artifacts --- cp "$OUTPUT_DIR/obj/webrtc.ninja" "$ARTIFACTS_DIR" cp "$OUTPUT_DIR/libjingle_peerconnection_so.so" "$ARTIFACTS_DIR/lib" +cp "$OUTPUT_DIR/obj/libwebrtc.a" "$ARTIFACTS_DIR/libwebrtc.a" cp "$OUTPUT_DIR/args.gn" "$ARTIFACTS_DIR" -cp "$OUTPUT_DIR/lib.java/sdk/android/libwebrtc.jar" "$ARTIFACTS_DIR/libwebrtc.jar" +# cp "$OUTPUT_DIR/lib.java/sdk/android/libwebrtc.jar" "$ARTIFACTS_DIR/libwebrtc.jar" cp "src/sdk/android/AndroidManifest.xml" "$ARTIFACTS_DIR" +cp "$OUTPUT_DIR/LICENSE.md" "$ARTIFACTS_DIR" # --- headers --- pushd src >/dev/null diff --git a/webrtc-sys/libwebrtc/create_package_structure.sh b/webrtc-sys/libwebrtc/webrtc_structure.sh similarity index 100% rename from webrtc-sys/libwebrtc/create_package_structure.sh rename to webrtc-sys/libwebrtc/webrtc_structure.sh From d389a1fedde47f3e1929185e2911d0ba048f149e Mon Sep 17 00:00:00 2001 From: Francisco Nadales Date: Thu, 9 Oct 2025 16:35:18 +0200 Subject: [PATCH 265/274] remove lk_custom_webrtc parameter in script --- livekit-ffi/generate_conan_build.sh | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/livekit-ffi/generate_conan_build.sh b/livekit-ffi/generate_conan_build.sh index f6c22afe8..7e69106b1 100755 --- a/livekit-ffi/generate_conan_build.sh +++ b/livekit-ffi/generate_conan_build.sh @@ -3,8 +3,8 @@ set -e # Exit immediately on error usage() { - echo "Usage: $0 --platform --lk_custom_webrtc " - echo "Example: $0 --platform windows --lk_custom_webrtc /path/to/webrtc" + echo "Usage: $0 --platform " + echo "Example: $0 --platform windows /path/to/webrtc" exit 1 } @@ -85,17 +85,12 @@ main() { if [ $# -lt 4 ]; then usage; fi local platform="" - local lk_custom_webrtc="" while [[ $# -gt 0 ]]; do case $1 in --platform) platform="$2" shift 2 ;; - --lk_custom_webrtc) - lk_custom_webrtc="$2" - shift 2 - ;; *) usage ;; @@ -107,13 +102,6 @@ main() { cd "$script_path" - if [[ -z "$lk_custom_webrtc" ]]; then - echo "Error: --lk_custom_webrtc is required." - usage - fi - - export LK_CUSTOM_WEBRTC="$lk_custom_webrtc" - case $platform in windows) build_windows From bd8ed62c5123be8a2b5293df72c61fc55c6c6c04 Mon Sep 17 00:00:00 2001 From: Francisco Nadales Date: Thu, 9 Oct 2025 16:51:50 +0200 Subject: [PATCH 266/274] improve generate_conan_build adding android arch and build_type --- livekit-ffi/generate_conan_build.sh | 92 ++++++++++++++++++++++------- 1 file changed, 71 insertions(+), 21 deletions(-) diff --git a/livekit-ffi/generate_conan_build.sh b/livekit-ffi/generate_conan_build.sh index 7e69106b1..3fce65ae7 100755 --- a/livekit-ffi/generate_conan_build.sh +++ b/livekit-ffi/generate_conan_build.sh @@ -3,8 +3,16 @@ set -e # Exit immediately on error usage() { - echo "Usage: $0 --platform " - echo "Example: $0 --platform windows /path/to/webrtc" + echo "Usage: $0 --platform [--arch ] [--build_type ]" + echo "Examples:" + echo " $0 --platform windows" + echo " $0 --platform android" + echo " $0 --platform android --arch arm64 --build_type debug" + echo " $0 --platform android --arch arm --build_type release" + echo "" + echo "Android options:" + echo " --arch Architecture (default: arm64)" + echo " --build_type Build type (default: release)" exit 1 } @@ -15,33 +23,45 @@ build_windows() { } build_android() { + local arch="$1" + local build_type="$2" + if [ -z "$ANDROID_NDK_HOME" ]; then echo "Error: ANDROID_NDK_HOME is not set. Please set it before running the script." exit 1 fi # Generate the C header file using cbindgen - cbindgen --config cbindgen.toml --crate livekit-ffi --output include/livekit_ffi.h + # cbindgen --config cbindgen.toml --crate livekit-ffi --output include/livekit_ffi.h + + # Determine cargo build flags based on build_type + local build_flags="" + if [ "$build_type" = "release" ]; then + build_flags="--release" + fi # Path to the custom WebRTC build for Android arm64 - export LK_CUSTOM_WEBRTC="$(pwd)/../webrtc-sys/libwebrtc/android-arm64-release" + export LK_CUSTOM_WEBRTC="$(pwd)/../webrtc-sys/libwebrtc/android-$arch-$build_type" - # Build armv8 (arm64) - aligned with Qt's arm64-v8a cargo clean - cargo ndk --target aarch64-linux-android build --release --platform 21 --no-default-features --features "rustls-tls-webpki-roots,webrtc-sys/use_x264" - create_folder_structure "aarch64-linux-android" - # Path to the custom WebRTC build for Android arm - export LK_CUSTOM_WEBRTC="$(pwd)/../webrtc-sys/libwebrtc/android-arm-release" - - # Build armv7 (32-bit) - cargo clean - cargo ndk --target armv7-linux-androideabi build --release --platform 21 --no-default-features --features "rustls-tls-webpki-roots,webrtc-sys/use_x264" - create_folder_structure "armv7-linux-androideabi" + if [ "$arch" = "arm64" ]; then + # Build armv8 (arm64) - aligned with Qt's arm64-v8a + cargo ndk --target aarch64-linux-android build $build_flags --platform 21 --no-default-features --features "rustls-tls-webpki-roots,webrtc-sys/use_x264" + create_folder_structure "aarch64-linux-android" "$build_type" + elif [ "$arch" = "arm" ]; then + # Build armv7 (32-bit) + cargo ndk --target armv7-linux-androideabi build $build_flags --platform 21 --no-default-features --features "rustls-tls-webpki-roots,webrtc-sys/use_x264" + create_folder_structure "armv7-linux-androideabi" "$build_type" + else + echo "Error: Invalid architecture: $arch. Must be 'arm64' or 'arm'." + exit 1 + fi } create_folder_structure() { local target=$1 + local build_type=${2:-release} # Default to release if not specified if [ -z "$target" ]; then echo "Error: Target not specified" @@ -62,13 +82,13 @@ create_folder_structure() { ;; aarch64-linux-android) mkdir -p "$conan_dir/lib/android/arm64-v8a" - cp "../target/$target/release/liblivekit_ffi.so" "$conan_dir/lib/android/arm64-v8a/" - cp "../target/$target/release/libwebrtc.jar" "$conan_dir/lib/android/arm64-v8a/" + cp "../target/$target/$build_type/liblivekit_ffi.so" "$conan_dir/lib/android/arm64-v8a/" + cp "../target/$target/$build_type/libwebrtc.jar" "$conan_dir/lib/android/arm64-v8a/" ;; armv7-linux-androideabi) mkdir -p "$conan_dir/lib/android/armeabi-v7a" - cp "../target/$target/release/liblivekit_ffi.so" "$conan_dir/lib/android/armeabi-v7a/" - cp "../target/$target/release/libwebrtc.jar" "$conan_dir/lib/android/armeabi-v7a/" + cp "../target/$target/$build_type/liblivekit_ffi.so" "$conan_dir/lib/android/armeabi-v7a/" + cp "../target/$target/$build_type/libwebrtc.jar" "$conan_dir/lib/android/armeabi-v7a/" ;; *) echo "Error: Unrecognized target: $target" @@ -78,25 +98,54 @@ create_folder_structure() { cp "$script_path/include/livekit_ffi.h" "$conan_dir/include/" - echo "Folder structure created and files copied successfully for $target." + echo "Folder structure created and files copied successfully for $target ($build_type)." } main() { - if [ $# -lt 4 ]; then usage; fi + if [ $# -lt 2 ]; then usage; fi local platform="" + local arch="arm64" # Default architecture for Android + local build_type="release" # Default build type + while [[ $# -gt 0 ]]; do case $1 in --platform) platform="$2" shift 2 ;; + --arch) + arch="$2" + shift 2 + ;; + --build_type) + build_type="$2" + shift 2 + ;; *) usage ;; esac done + # Validate required platform parameter + if [ -z "$platform" ]; then + echo "Error: --platform is required" + usage + fi + + # Validate arch parameter for Android + if [ "$platform" = "android" ] && [[ "$arch" != "arm64" && "$arch" != "arm" ]]; then + echo "Error: Invalid architecture '$arch'. Must be 'arm64' or 'arm'." + usage + fi + + # Validate build_type parameter + if [[ "$build_type" != "debug" && "$build_type" != "release" ]]; then + echo "Error: Invalid build type '$build_type'. Must be 'debug' or 'release'." + usage + fi + local initial_path=$(pwd) local script_path=$(dirname "$(readlink -f "$0")") @@ -107,7 +156,8 @@ main() { build_windows ;; android) - build_android + echo "Building Android for arch: $arch, build_type: $build_type" + build_android "$arch" "$build_type" ;; *) usage From 397dcfc5158c28ff47151688f973e9fd6da635db Mon Sep 17 00:00:00 2001 From: Juan Date: Thu, 9 Oct 2025 16:56:13 +0200 Subject: [PATCH 267/274] Update cabi.toml to do not generate JNI_OnLoad --- livekit-ffi/cbindgen.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/livekit-ffi/cbindgen.toml b/livekit-ffi/cbindgen.toml index d2bd94aff..fabf5c8e5 100644 --- a/livekit-ffi/cbindgen.toml +++ b/livekit-ffi/cbindgen.toml @@ -13,3 +13,7 @@ exclude = ["android", "JNI_OnLoad", "JavaVM", "jint", "JNI_VERSION_1_6"] [parse.expand] all_features = false default_features = false + +# Explicitly exclude specific symbols from export +[export] +exclude = ["JNI_OnLoad"] From 6c0ee130717100cae0a6dc4839409f1b020b78e3 Mon Sep 17 00:00:00 2001 From: Juan Date: Thu, 9 Oct 2025 17:23:45 +0200 Subject: [PATCH 268/274] Activate cbindgen --- livekit-ffi/generate_conan_build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/livekit-ffi/generate_conan_build.sh b/livekit-ffi/generate_conan_build.sh index 3fce65ae7..2cceb1b6d 100755 --- a/livekit-ffi/generate_conan_build.sh +++ b/livekit-ffi/generate_conan_build.sh @@ -32,7 +32,7 @@ build_android() { fi # Generate the C header file using cbindgen - # cbindgen --config cbindgen.toml --crate livekit-ffi --output include/livekit_ffi.h + cbindgen --config cbindgen.toml --crate livekit-ffi --output include/livekit_ffi.h # Determine cargo build flags based on build_type local build_flags="" From b9504434a700fd99eb63f98f8e9c19e8199ce26c Mon Sep 17 00:00:00 2001 From: Francisco Nadales Date: Fri, 10 Oct 2025 10:23:33 +0200 Subject: [PATCH 269/274] Add detailed fork documentation and update build scripts for improved WebRTC integration - Created comprehensive documentation for the LiveKit-FFI fork, outlining modifications and build steps. - Updated `generate_conan_build.sh` to use 'profile' instead of 'build_type' for consistency. - Enhanced `build_android.sh` to include profile-specific build settings. - Introduced `strip_jni_zero.sh` script to streamline the removal of unwanted symbols from the WebRTC JAR. --- FORK_DOCUMENTATION.md | 227 +++++++++++++++++++++++++ README.md | 88 ---------- livekit-ffi/generate_conan_build.sh | 48 +++--- webrtc-sys/libwebrtc/build_android.sh | 44 ++--- webrtc-sys/libwebrtc/strip_jni_zero.sh | 36 ++++ 5 files changed, 304 insertions(+), 139 deletions(-) create mode 100644 FORK_DOCUMENTATION.md create mode 100644 webrtc-sys/libwebrtc/strip_jni_zero.sh diff --git a/FORK_DOCUMENTATION.md b/FORK_DOCUMENTATION.md new file mode 100644 index 000000000..258fca6a7 --- /dev/null +++ b/FORK_DOCUMENTATION.md @@ -0,0 +1,227 @@ +# LiveKit-FFI Fork for WebRTC with livekit.org.webrtc Prefix + +This repository is a fork of [livekit-ffi](https://github.com/livekit/livekit-ffi). + +--- + +This fork ensures compatibility with projects requiring multiple WebRTC implementations while maintaining seamless integration with Conan package management. + +--- + +## Modifications Done with Reference to upstream/main (Original Repo) + +The main modifications in this fork compared to the original repository include: + +- **WebRTC Package Prefix**: Modified WebRTC libraries to use `livekit.org.webrtc` prefix instead of the default `org.webrtc` +- **Android Library Integration**: Updated Android build configurations to support dual WebRTC library integration +- **Conan Package Support**: Enhanced Conan package generation and export processes +- **Build System Adjustments**: Modified build scripts and configurations to accommodate the prefix changes +- **Dependency Management**: Updated dependency declarations to prevent conflicts with other WebRTC implementations + +## Dependencies and Tools + +Before building this project, ensure you have the following dependencies and tools installed: + +### Required Tools: +- **Rust**: Latest stable version (install via [rustup](https://rustup.rs/)) + - **cargo-ndk**: For Android NDK integration (install via `cargo install cargo-ndk`) + - **cbindgen**: For C binding generation (install via `cargo install cbindgen`) +- **Python**: 3.7 or higher +- **CMake**: 3.16 or higher +- **Git**: For version control and repository management +- **Conan**: Package manager for C/C++ (install via `pip install conan`) + +### Platform-specific Dependencies: + +#### Linux: +```sh +# Ubuntu/Debian +sudo apt-get update +sudo apt-get install build-essential pkg-config libssl-dev +``` + +#### Windows: +- Visual Studio 2019 or 2022 with C++ development tools +- Windows SDK 10 + +#### Android: +- Android NDK r23 or higher (align with current in use) +- Android SDK with API level +21 (align with current in use) +- Android SDK Platform Tools +- Java Development Kit (JDK) +17 + +### Rust Additional Tools and Targets: +```sh +# Install required Rust tools +cargo install cargo-ndk +cargo install cbindgen + +# Add required Rust targets for cross-compilation +rustup target add aarch64-linux-android +rustup target add armv7-linux-androideabi +rustup target add i686-linux-android +rustup target add x86_64-linux-android +``` + +## Update from upstream + +To get new changes from upstream branch we must to: + +### 1. Add upstream remote (if not already added) +First, ensure the upstream remote is configured to point to the original repository: +```sh +git remote add upstream https://github.com/livekit/livekit-ffi.git +``` + +You can verify the remote is added correctly: +```sh +git remote -v +``` + +### 2. Create a new support branch +Create a new branch from `main` called `support/ffi-vx.xx.xx` in order to get the changes there. +```sh +git checkout -b support/ffi-v0.13.0 +``` + +### 3. Fetch changes from upstream +Get all new changes from upstream: +```sh +git fetch upstream --tags +``` + +### 4. Rebase onto upstream changes +Do a rebase: +```sh +git rebase ffi-v0.13.0 +``` + +### 5. Resolve conflicts and continue +If there are conflicts during the rebase, you must resolve them and then continue: + +```sh +# After resolving conflicts in your editor +git add . +git rebase --continue +``` + +If you want to apply the rebase without stopping for conflicts (use with caution): +```sh +git rebase --apply +``` + +## Steps to Build WebRTC + +WebRTC needs to be built first before building livekit-ffi. Follow these steps: + +### 1. Navigate to WebRTC Build Directory +```sh +cd webrtc-sys/libwebrtc +``` + +### 2. Build WebRTC for Different Platforms + +#### For macOS (ARM64): +```sh +./build_macos.sh --arch arm64|x64 --profile debug|release +``` + +#### For Android: +```sh +./build_android.sh --arch arm|arm64|x64 --profile debug|release +``` + +#### For Windows: +```sh +# From Windows command prompt or PowerShell +build_windows.bat --arch arm64|x64 --profile debug|release +``` + +### 3. Verify WebRTC Build +After successful build, you should see the compiled libraries in: +- `webrtc-sys/libwebrtc/{platform}-{arch}-{profile}/lib/` + +## Steps to Build livekit-ffi and Create Conan Packages + +**IMPORTANT:** Before proceeding with livekit-ffi build, you **MUST** complete the WebRTC build process first (see previous section). The livekit-ffi build depends on the compiled WebRTC libraries and will fail if they are not present. + +The livekit-ffi build is integrated with the Conan package creation process through the `generate_conan_build` script. This script automatically configures the build environment and compiles livekit-ffi for the target platforms. + +### 1. Prerequisites + +Ensure that: +- **WebRTC libraries are built for all required platforms** (MANDATORY - see "Steps to Build WebRTC" section above) +- Conan is installed and configured +- Required environment variables are set (see below) + +### 2. Set Required Environment Variables + +For Android builds, you must have: +```sh +# Android NDK path (required for Android builds) +export ANDROID_NDK_ROOT="/path/to/android-ndk" +export ANDROID_NDK_HOME="/path/to/android-ndk" +``` + +**Note:** The `generate_conan_build` script will automatically configure `LK_CUSTOM_WEBRTC` to point to the appropriate WebRTC build based on the platform and architecture being built. This requires that WebRTC libraries have been successfully compiled first. + +### 3. Generate Conan Build Directory and Build livekit-ffi + +The `generate_conan_build` script performs the following actions: +- Configures `LK_CUSTOM_WEBRTC` environment variable automatically +- Builds livekit-ffi for the specified platform +- Creates the Conan package structure +- Generates the necessary build artifacts + +#### Windows: +```sh +generate_conan_build.bat --platform windows +``` + +#### Linux: +```sh +./generate_conan_build.sh --platform android --arch arm|arm64 --profile debug|release +``` + +**Important Notes:** +- **WebRTC must be built first**: Ensure WebRTC libraries are compiled for your target platform before running this step +- Windows builds cannot be compiled from Linux +- The script will automatically detect and use the appropriate WebRTC build (if available) +- For Android builds, ensure `ANDROID_NDK_HOME` is properly set before running +- If WebRTC libraries are missing, the build will fail with linking errors + +### 4. Verify Conan Package Structure +After execution, a `livekit-ffi_conan` directory will be created at the root of the project, containing: +- `conanfile.py` - Package configuration +- `include/` - Header files +- `lib/` - Compiled libraries for different platforms/architectures + +### 5. Export Conan Packages + +Navigate to the generated conan directory: +```sh +cd livekit-ffi_conan +``` + +Export the package for all different profiles: + +```sh +conan export-pkg . livekit-ffi/{version}@dn/stable -pr {profile} -f +``` + +**Note:** Replace `{version}` and `{profile}` with the appropriate values. + +### 6. Upload to Conan Repository + +Upload the package to your Conan repository: +```sh +conan upload livekit-ffi/{version}@dn/stable -r dn --all +``` + +### 7. Verify Package Upload +Verify that the package was uploaded successfully: +```sh +conan search livekit-ffi/{version}@dn/stable -r dn +``` + +**Note:** Replace `{version}` with the appropriate tag version as needed for your release. diff --git a/README.md b/README.md index 688cda532..6503e3b70 100644 --- a/README.md +++ b/README.md @@ -1,91 +1,3 @@ -# LiveKit-FFI Fork for WebRTC with livekit.org.webrtc Prefix - -This repository is a fork of [livekit-ffi](https://github.com/livekit/livekit-ffi) to enable the use of WebRTC with the `livekit.org.webrtc` prefix in the Android library. This allows integrating two different WebRTC libraries within the same project. - -## Cloning and Checking Out a Tag - -To get started, clone the repository and switch to the desired tag: - -```sh -git clone https://github.com/DisplayNote/dn_livekit_ffi.git -cd dn_livekit_ffi -git checkout -``` - -## Applying Changes - -To get new changes from upstream branch we must to: - -Create a new branch from `main` called `support/ffi-vx.xx.xx` in order to get the changes there. -```sh -git checkout -b support/ffi-v0.13.0 -``` - -Get all new changes from upstream: -```sh -git fetch upstream --tags -``` - -Do a rebase: -```sh -git rebase ffi-v0.13.0 -``` - -Then you must to solve conflicts, then, you must to compile, first webrtc and then ffi - -```sh -git rebase --apply -``` - -## Generating Conan Build Directory - -Once changes are applied and webrtc is built, generate the Conan build directory, which will contain the necessary files for uploading to Conan. - -To get the webrtc zip file generated you must to set the environ `LK_ARTIFACT_WEBRTC` where will be the path where the webrtc zip generated is located. - -### Windows: -```sh -generate_conan_build.bat --platform windows -generate_conan_build.bat --platform android -``` - -### Linux: -```sh -./generate_conan_build.sh --platform android -``` -**Note:** Windows builds cannot be compiled from Linux. - -After execution, a `livekit-ffi_conan` directory will be created at the root of the project, containing the necessary files for Conan package export and upload. -You must be to insert the correct version to upload in your `conanfile.py` - -## Exporting and Uploading to Conan - -To export the package for different profiles, execute the following command: - -```sh -conan export-pkg . livekit-ffi/0.7.2@dn/stable -pr android.arm64-v8a.debug -f && -conan export-pkg . livekit-ffi/0.7.2@dn/stable -pr android.arm64-v8a.release -f && -conan export-pkg . livekit-ffi/0.7.2@dn/stable -pr android.armeabi-v7a.debug -f && -conan export-pkg . livekit-ffi/0.7.2@dn/stable -pr android.armeabi-v7a.release -f && -conan export-pkg . livekit-ffi/0.7.2@dn/stable -pr msvc19.x86_64.debug -f && -conan export-pkg . livekit-ffi/0.7.2@dn/stable -pr msvc19.x86_64.release -f -``` - -Finally, upload the package to the Conan repository: - -```sh -conan upload livekit-ffi/0.7.2@dn/stable -r dn --all -``` - -Replace `0.7.2` with the appropriate tag version as needed. - ---- - -This fork ensures compatibility with projects requiring multiple WebRTC implementations while maintaining seamless integration with Conan package management. - ---- - - diff --git a/livekit-ffi/generate_conan_build.sh b/livekit-ffi/generate_conan_build.sh index 3fce65ae7..97d13f214 100755 --- a/livekit-ffi/generate_conan_build.sh +++ b/livekit-ffi/generate_conan_build.sh @@ -3,16 +3,16 @@ set -e # Exit immediately on error usage() { - echo "Usage: $0 --platform [--arch ] [--build_type ]" + echo "Usage: $0 --platform [--arch ] [--profile ]" echo "Examples:" echo " $0 --platform windows" echo " $0 --platform android" - echo " $0 --platform android --arch arm64 --build_type debug" - echo " $0 --platform android --arch arm --build_type release" + echo " $0 --platform android --arch arm64 --profile debug" + echo " $0 --platform android --arch arm --profile release" echo "" echo "Android options:" echo " --arch Architecture (default: arm64)" - echo " --build_type Build type (default: release)" + echo " --profile Build profile (default: release)" exit 1 } @@ -24,7 +24,7 @@ build_windows() { build_android() { local arch="$1" - local build_type="$2" + local profile="$2" if [ -z "$ANDROID_NDK_HOME" ]; then echo "Error: ANDROID_NDK_HOME is not set. Please set it before running the script." @@ -34,25 +34,25 @@ build_android() { # Generate the C header file using cbindgen # cbindgen --config cbindgen.toml --crate livekit-ffi --output include/livekit_ffi.h - # Determine cargo build flags based on build_type + # Determine cargo build flags based on profile local build_flags="" - if [ "$build_type" = "release" ]; then + if [ "$profile" = "release" ]; then build_flags="--release" fi # Path to the custom WebRTC build for Android arm64 - export LK_CUSTOM_WEBRTC="$(pwd)/../webrtc-sys/libwebrtc/android-$arch-$build_type" + export LK_CUSTOM_WEBRTC="$(pwd)/../webrtc-sys/libwebrtc/android-$arch-$profile" cargo clean if [ "$arch" = "arm64" ]; then # Build armv8 (arm64) - aligned with Qt's arm64-v8a cargo ndk --target aarch64-linux-android build $build_flags --platform 21 --no-default-features --features "rustls-tls-webpki-roots,webrtc-sys/use_x264" - create_folder_structure "aarch64-linux-android" "$build_type" + create_folder_structure "aarch64-linux-android" "$profile" elif [ "$arch" = "arm" ]; then # Build armv7 (32-bit) cargo ndk --target armv7-linux-androideabi build $build_flags --platform 21 --no-default-features --features "rustls-tls-webpki-roots,webrtc-sys/use_x264" - create_folder_structure "armv7-linux-androideabi" "$build_type" + create_folder_structure "armv7-linux-androideabi" "$profile" else echo "Error: Invalid architecture: $arch. Must be 'arm64' or 'arm'." exit 1 @@ -61,7 +61,7 @@ build_android() { create_folder_structure() { local target=$1 - local build_type=${2:-release} # Default to release if not specified + local profile=${2:-release} # Default to release if not specified if [ -z "$target" ]; then echo "Error: Target not specified" @@ -82,13 +82,13 @@ create_folder_structure() { ;; aarch64-linux-android) mkdir -p "$conan_dir/lib/android/arm64-v8a" - cp "../target/$target/$build_type/liblivekit_ffi.so" "$conan_dir/lib/android/arm64-v8a/" - cp "../target/$target/$build_type/libwebrtc.jar" "$conan_dir/lib/android/arm64-v8a/" + cp "../target/$target/$profile/liblivekit_ffi.so" "$conan_dir/lib/android/arm64-v8a/" + cp "../target/$target/$profile/libwebrtc.jar" "$conan_dir/lib/android/arm64-v8a/" ;; armv7-linux-androideabi) mkdir -p "$conan_dir/lib/android/armeabi-v7a" - cp "../target/$target/$build_type/liblivekit_ffi.so" "$conan_dir/lib/android/armeabi-v7a/" - cp "../target/$target/$build_type/libwebrtc.jar" "$conan_dir/lib/android/armeabi-v7a/" + cp "../target/$target/$profile/liblivekit_ffi.so" "$conan_dir/lib/android/armeabi-v7a/" + cp "../target/$target/$profile/libwebrtc.jar" "$conan_dir/lib/android/armeabi-v7a/" ;; *) echo "Error: Unrecognized target: $target" @@ -98,7 +98,7 @@ create_folder_structure() { cp "$script_path/include/livekit_ffi.h" "$conan_dir/include/" - echo "Folder structure created and files copied successfully for $target ($build_type)." + echo "Folder structure created and files copied successfully for $target ($profile)." } main() { @@ -106,7 +106,7 @@ main() { local platform="" local arch="arm64" # Default architecture for Android - local build_type="release" # Default build type + local profile="release" # Default build profile while [[ $# -gt 0 ]]; do case $1 in @@ -118,8 +118,8 @@ main() { arch="$2" shift 2 ;; - --build_type) - build_type="$2" + --profile) + profile="$2" shift 2 ;; *) @@ -140,9 +140,9 @@ main() { usage fi - # Validate build_type parameter - if [[ "$build_type" != "debug" && "$build_type" != "release" ]]; then - echo "Error: Invalid build type '$build_type'. Must be 'debug' or 'release'." + # Validate profile parameter + if [[ "$profile" != "debug" && "$profile" != "release" ]]; then + echo "Error: Invalid build profile '$profile'. Must be 'debug' or 'release'." usage fi @@ -156,8 +156,8 @@ main() { build_windows ;; android) - echo "Building Android for arch: $arch, build_type: $build_type" - build_android "$arch" "$build_type" + echo "Building Android for arch: $arch, profile: $profile" + build_android "$arch" "$profile" ;; *) usage diff --git a/webrtc-sys/libwebrtc/build_android.sh b/webrtc-sys/libwebrtc/build_android.sh index de27fab17..dd80b3b90 100755 --- a/webrtc-sys/libwebrtc/build_android.sh +++ b/webrtc-sys/libwebrtc/build_android.sh @@ -200,18 +200,26 @@ args="is_debug=$debug \ rtc_build_examples=false \ rtc_libvpx_build_vp9=false \ is_component_build=false \ - enable_stripping=true \ rtc_use_h264=false \ rtc_use_pipewire=false \ libyuv_use_sme=false \ proprietary_codecs=true \ - symbol_level=0 \ - enable_iterator_debugging=false \ use_rtti=true" -# if [ "$debug" = "true" ]; then -# args="${args} is_asan=true is_lsan=true" -# fi +# Profile-specific overrides +if [ "$debug" = "true" ]; then + # Debug build settings + args="${args} \ + symbol_level=2 \ + enable_iterator_debugging=true \ + is_asan=true \ + is_lsan=true" +else + # Release build settings + args="${args} \ + symbol_level=0 \ + enable_stripping=true +fi # --- GN gen --- gn gen "$OUTPUT_DIR" --root="src" --args="${args}" @@ -226,33 +234,15 @@ autoninja -C "$OUTPUT_DIR" :default \ python3 "./src/tools_webrtc/libs/generate_licenses.py" \ --target :default "$OUTPUT_DIR" "$OUTPUT_DIR" -# --- Strip org.jni_zero symbols from libwebrtc.jar --- -echo "Stripping org.jni_zero symbols from libwebrtc.jar..." -temp_jar_dir="$(mktemp -d)" -pushd "$temp_jar_dir" >/dev/null - -# Extract the JAR -jar -xf "$OUTPUT_DIR/lib.java/sdk/android/libwebrtc.jar" - -# Remove org.jni_zero classes -if [[ -d "org/jni_zero" ]]; then - echo " Removing org/jni_zero directory..." - rm -rf "org/jni_zero" -fi - -# Recreate the JAR without org.jni_zero -jar -cf "$ARTIFACTS_DIR/libwebrtc.jar" . - -popd >/dev/null -rm -rf "$temp_jar_dir" -echo " ✓ Stripped org.jni_zero symbols from libwebrtc.jar" +# --- strip org.jni_zero from libwebrtc.jar --- +"$COMMAND_DIR/strip_jni_zero.sh" "$OUTPUT_DIR" # --- copy artifacts --- cp "$OUTPUT_DIR/obj/webrtc.ninja" "$ARTIFACTS_DIR" cp "$OUTPUT_DIR/libjingle_peerconnection_so.so" "$ARTIFACTS_DIR/lib" cp "$OUTPUT_DIR/obj/libwebrtc.a" "$ARTIFACTS_DIR/libwebrtc.a" cp "$OUTPUT_DIR/args.gn" "$ARTIFACTS_DIR" -# cp "$OUTPUT_DIR/lib.java/sdk/android/libwebrtc.jar" "$ARTIFACTS_DIR/libwebrtc.jar" +cp "$OUTPUT_DIR/lib.java/sdk/android/libwebrtc.jar" "$ARTIFACTS_DIR/libwebrtc.jar" cp "src/sdk/android/AndroidManifest.xml" "$ARTIFACTS_DIR" cp "$OUTPUT_DIR/LICENSE.md" "$ARTIFACTS_DIR" diff --git a/webrtc-sys/libwebrtc/strip_jni_zero.sh b/webrtc-sys/libwebrtc/strip_jni_zero.sh new file mode 100644 index 000000000..e6382e39c --- /dev/null +++ b/webrtc-sys/libwebrtc/strip_jni_zero.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +set -e + +OUTPUT_DIR="${OUTPUT_DIR:-$1}" + +if [[ -z "$OUTPUT_DIR" ]]; then + echo "Error: OUTPUT_DIR not set and no argument provided" + echo "Usage: $0 " + exit 1 +fi + +if [[ ! -f "$OUTPUT_DIR/lib.java/sdk/android/libwebrtc.jar" ]]; then + echo "Error: libwebrtc.jar not found at $OUTPUT_DIR/lib.java/sdk/android/libwebrtc.jar" + exit 1 +fi + +echo "Stripping org.jni_zero symbols from libwebrtc.jar..." +temp_jar_dir="$(mktemp -d)" +pushd "$temp_jar_dir" >/dev/null + +# Extract the JAR +jar -xf "$OUTPUT_DIR/lib.java/sdk/android/libwebrtc.jar" + +# Remove org.jni_zero classes +if [[ -d "org/jni_zero" ]]; then + echo " Removing org/jni_zero directory..." + rm -rf "org/jni_zero" +fi + +# Recreate the JAR without org.jni_zero +jar -cf "$OUTPUT_DIR/lib.java/sdk/android/libwebrtc.jar" . + +popd >/dev/null +rm -rf "$temp_jar_dir" +echo " ✓ Stripped org.jni_zero symbols from libwebrtc.jar" \ No newline at end of file From d882b38b28cc3975469195797f708d45bbc17921 Mon Sep 17 00:00:00 2001 From: Francisco Nadales Date: Fri, 10 Oct 2025 10:27:40 +0200 Subject: [PATCH 270/274] Fix formatting in build_android.sh for release build settings --- webrtc-sys/libwebrtc/build_android.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webrtc-sys/libwebrtc/build_android.sh b/webrtc-sys/libwebrtc/build_android.sh index dd80b3b90..41a39e224 100755 --- a/webrtc-sys/libwebrtc/build_android.sh +++ b/webrtc-sys/libwebrtc/build_android.sh @@ -218,7 +218,7 @@ else # Release build settings args="${args} \ symbol_level=0 \ - enable_stripping=true + enable_stripping=true" fi # --- GN gen --- From 196cc11802e7728ff80966eb77c199d1127bf4e1 Mon Sep 17 00:00:00 2001 From: Francisco Nadales Date: Fri, 10 Oct 2025 10:41:27 +0200 Subject: [PATCH 271/274] Change permissions of strip_jni_zero.sh to make it executable --- webrtc-sys/libwebrtc/strip_jni_zero.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 webrtc-sys/libwebrtc/strip_jni_zero.sh diff --git a/webrtc-sys/libwebrtc/strip_jni_zero.sh b/webrtc-sys/libwebrtc/strip_jni_zero.sh old mode 100644 new mode 100755 From 9d784f0b921c9864a6295ec54331ffd6a371cab9 Mon Sep 17 00:00:00 2001 From: Francisco Nadales Date: Fri, 10 Oct 2025 11:41:47 +0200 Subject: [PATCH 272/274] fix livekit_ffi.h format --- livekit-ffi/include/livekit_ffi.h | 41 +++++++++++++++---------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/livekit-ffi/include/livekit_ffi.h b/livekit-ffi/include/livekit_ffi.h index 67e7922d5..06243e1ae 100644 --- a/livekit-ffi/include/livekit_ffi.h +++ b/livekit-ffi/include/livekit_ffi.h @@ -13,35 +13,34 @@ constexpr static const size_t BATCH_SIZE = 32; /// # SAFTEY: The "C" callback must be threadsafe and not block -using FfiCallbackFn = void (*)(const uint8_t *, size_t); +using FfiCallbackFn = void(*)(const uint8_t*, size_t); using FfiHandleId = uint64_t; constexpr static const FfiHandleId INVALID_HANDLE = 0; -extern "C" -{ +extern "C" { - /// # Safety - /// - /// The foreign language must only provide valid pointers - void livekit_ffi_initialize(FfiCallbackFn cb, - bool capture_logs, - const char *sdk, - const char *sdk_version); +/// # Safety +/// +/// The foreign language must only provide valid pointers +void livekit_ffi_initialize(FfiCallbackFn cb, + bool capture_logs, + const char *sdk, + const char *sdk_version); - /// # Safety - /// - /// The foreign language must only provide valid pointers - FfiHandleId livekit_ffi_request(const uint8_t *data, - size_t len, - const uint8_t **res_ptr, - size_t *res_len); +/// # Safety +/// +/// The foreign language must only provide valid pointers +FfiHandleId livekit_ffi_request(const uint8_t *data, + size_t len, + const uint8_t **res_ptr, + size_t *res_len); - bool livekit_ffi_drop_handle(FfiHandleId handle_id); +bool livekit_ffi_drop_handle(FfiHandleId handle_id); - void livekit_ffi_dispose(); +void livekit_ffi_dispose(); -} // extern "C" +} // extern "C" -#endif // livekit_ffi +#endif // livekit_ffi From 6104ae2c61ab432499c6a2e0b78d4dedfa6b4818 Mon Sep 17 00:00:00 2001 From: Francisco Nadales Date: Fri, 10 Oct 2025 12:37:54 +0200 Subject: [PATCH 273/274] Fix cargo format --- webrtc-sys/build/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webrtc-sys/build/src/lib.rs b/webrtc-sys/build/src/lib.rs index b2e75057b..d3c04e399 100644 --- a/webrtc-sys/build/src/lib.rs +++ b/webrtc-sys/build/src/lib.rs @@ -91,14 +91,14 @@ pub fn use_debug() -> bool { pub fn custom_dir() -> Option { if let Ok(path) = env::var("LK_CUSTOM_WEBRTC") { let path_buf = path::PathBuf::from(path); - + // If the path is relative, resolve it relative to CARGO_MANIFEST_DIR if path_buf.is_relative() { if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") { return Some(path::PathBuf::from(manifest_dir).join(path_buf)); } } - + return Some(path_buf); } None From a9837158e777e077f1ded684aa0a5afbff71e70a Mon Sep 17 00:00:00 2001 From: Francisco Nadales Date: Fri, 10 Oct 2025 13:16:50 +0200 Subject: [PATCH 274/274] Refactor build_android.sh: Update debug build settings and adjust artifact copying --- webrtc-sys/libwebrtc/build_android.sh | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/webrtc-sys/libwebrtc/build_android.sh b/webrtc-sys/libwebrtc/build_android.sh index 41a39e224..e242f2683 100755 --- a/webrtc-sys/libwebrtc/build_android.sh +++ b/webrtc-sys/libwebrtc/build_android.sh @@ -211,9 +211,10 @@ if [ "$debug" = "true" ]; then # Debug build settings args="${args} \ symbol_level=2 \ - enable_iterator_debugging=true \ - is_asan=true \ - is_lsan=true" + enable_iterator_debugging=true" + # These options force min_supported_sdk_version = 27 and fails due to default 21 + # is_asan=true \ + # is_lsan=true" else # Release build settings args="${args} \ @@ -240,9 +241,9 @@ python3 "./src/tools_webrtc/libs/generate_licenses.py" \ # --- copy artifacts --- cp "$OUTPUT_DIR/obj/webrtc.ninja" "$ARTIFACTS_DIR" cp "$OUTPUT_DIR/libjingle_peerconnection_so.so" "$ARTIFACTS_DIR/lib" -cp "$OUTPUT_DIR/obj/libwebrtc.a" "$ARTIFACTS_DIR/libwebrtc.a" +cp "$OUTPUT_DIR/obj/libwebrtc.a" "$ARTIFACTS_DIR/lib" cp "$OUTPUT_DIR/args.gn" "$ARTIFACTS_DIR" -cp "$OUTPUT_DIR/lib.java/sdk/android/libwebrtc.jar" "$ARTIFACTS_DIR/libwebrtc.jar" +cp "$OUTPUT_DIR/lib.java/sdk/android/libwebrtc.jar" "$ARTIFACTS_DIR" cp "src/sdk/android/AndroidManifest.xml" "$ARTIFACTS_DIR" cp "$OUTPUT_DIR/LICENSE.md" "$ARTIFACTS_DIR"
    LiveKit Ecosystem
    LiveKit SDKsBrowser · iOS/macOS/visionOS · Android · Flutter · React Native · Rust · Node.js · Python · Unity · Unity (WebGL)
    LiveKit SDKsBrowser · iOS/macOS/visionOS · Android · Flutter · React Native · Rust · Node.js · Python · Unity · Unity (WebGL) · ESP32
    Server APIsNode.js · Golang · Ruby · Java/Kotlin · Python · Rust · PHP (community) · .NET (community)
    UI ComponentsReact · Android Compose · SwiftUI
    UI ComponentsReact · Android Compose · SwiftUI · Flutter
    Agents FrameworksPython · Node.js · Playground
    ServicesLiveKit server · Egress · Ingress · SIP
    ResourcesDocs · Example apps · Cloud · Self-hosting · CLI
  • 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ª`-^Lz2dZh4E-$w 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 Ee^!rzPyhe` diff --git a/.github/banner_light.png b/.github/banner_light.png index 1ddd6597156e27d6399b1dede3b7ef858b5ba332..cdfb7cdfad4b7fde5b37426f9a15d918effe60d2 100644 GIT binary patch literal 46138 zcmeFZc{r5s|2`}fic~7ulcdPVzEdfZkZnYkWZwrd7(-IDh$8z|3E8s`6Q+b1OP0wt z!(?B_ZZL-5rS$%MKkx5z9M3<`as2*y=BUG9-1oJ3E8X6YO z8`tz{Xc&Ib(Ck}bqy;~juHcsfUzlJwOuT4lP73b+->0d6c7cZGG>zu9D~7(wvjoJ` zi|&C#aB2*n$XOZMbb9RhFM9WnTf;6g9J_z%{Fck{{X*wFp0b4o)4gbzh9WP8ar~_-|uL2gDJ{?zhSJ`>1V?4+xxbc zDOmCEZ;qaZG3>oRO-oPu`{Qd5F+6+kA2J?6(e1rIvX2)2_akR66A$mbzf8~g&&8k8 z{QnpK|87_wGK5;S*c&w7X|W$@i*V%3w2IHpBpYt zeEORt0;Dh+dv>;m$%BpdZnn`u(()($W*%QouGQk|nZODp(VR}sd~bCS_XVSC&HP_r z%zNTOEbM>Gv}9WA6dJJN5rm(?a2Dc&y&KZlYb7-K-grWR1XEx?%F@hokIPm2I9}*- zbOcw@Vcc6LcLq4@24W>L9e)Zxagr~@-L(IypNacugxzqG=lj5}6pS!buTu$De8_fn zZ@ry4gttDQgF%YWll1suk>Bc~YBL>8pi*KC_muaRX9Pxy+1xTQvl0i5wVm2rqRH;d zrYALo#O!!6;$=;zHX%YWR(ZTR+jO6=K4n{f6^wRF%*6?tv65p7+!6A8`U)G{YvVb% zJU+_hhszp&Zj3oOUgB68FUnq$5<9*<FT2T)B)6$_--qR-abBtdU&pGtMT^tfKCoF!7CYlO=K&lMI z<;lmH$wp6)_A;$k*@q6^94rxw^^=<%rS zU<#F5tz>VmT=u3w@hYrKN-~?P>}Hi-p+i|zBBIK&OA=O*+hd#CTTWM(EG@XJNPMWfu^DzX`$lx0 zt@i9Ep~VvYi5TUSfI>0$r7ZR8GeMp$?-XGbjcQr1Cs03&^8yR=E!bZtV-PI(#^y-L z&FZ!;tDWe1yGwK}7^YSXvSlwT!JcBwqZ&_pi9)46_Mr3?a-)i&^7Kg-smqOBTn3{?pASJ<#nFH4RL5MjcD zbwIX6w+nrmh-B@?R9hgu6>Wlq7Pe{O*kYxiv3o*D=zK1--9wJ28_cH+{{(YbrKs(j z>cYlPk#hc9!Pku)aslo5UF>`BETw)JidM7(kS_bH*cYXcS$KLM&IC` zO~n*PWw~=_cD}bE+>)7Yv)g1pfkG4za(uEpq<+BZNvYa{WDnYTF<6>#?*5Ou=U_r} zwg+@<4=@Y=0TZZR3?bq$*h)%t8u?J4(WEHJ!9lZ_r3*V9^X0lCFyrW;pVx~T8wz*7tOfh2j-TW^dv6$aU9dp%E*!+~dXY?dvsNO>$h1mz)~Y&5$>Os(4%Oh73(oe5tJ zxwX;pEIsIOu%e@wBurTH-tAw%(Gw;+Iv!>A9ktW{(Rhmv9%FTiYH>H;&v>TTQX?(* z<51%w9asmW)$MkyEMIcRwhTUyV)i?ibsC7A%0C8y>7FalUgp1Mkq<4N2|EF`lzmbA z3RA5ohTf@)Zx2u*=hKou>f95Ak#BUuD=)=i>#<20VU(h1g9DQH_VP3=*skkY%8xOL zLMWxQsVe0PEgU{Qal^6rywm87wPvaO$_1oKiEoP_0o04pWnQH`{sjt!0J<@Glk%6Q;cQdEPyYFzQ)ko*&{wLqfqhjbuPs}Yr=|_ruzbhy1i||8K)S9_vBy33)ZDO zz=u8@z!P+ob1>#FzcNq@f(nG6OVgiRxcp338) z$52CqW}-1~`k)|R99*to!3)R-uTCiHgY;RpUBUPZa?9k@TinN{5PFh^t{z9r!|Q+d z`)#dSoUIT@Jw^v-Je9GL$HSSDh9ES)1##N=x-y zHtAE)^dvHwoY4I;J+8WaGojP#T%0DV?!MxiLZgEbD*@(2HxoO^@|$;A(engRESw3y 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

  • eko5P`#{uu7x0k0KssAI=Bw!f%_HB@I!4Bm6JWf%)1aQ&rD0+7meu zzPn>j3ujONe1qql1OhOApa8?BE>IEt$8dDGoE2hg#W~F3gs1qo^Efy zxP#h|opMmk<#}zMD{@C*JWezFHx5|=*yMLD#14-q@7NrZnl{h!_EuOPwb}IDFF(eR zW&FH8pILM0m|xAspCV{+BaIgL10S_*_^mepxYj^WAc-Udz{n^t+Xe{PY$X7qh(!5x zA-b72(>Cf8+=y9p9&JYNcsJRkra!E*zzLb;`jm^DA6tV{=rwk-&3YQhb~2Z9zx3Yt zCl@S^+cmiGucGwP^>NeVOv<>oxLDMq`b*1<4D>Ib>5%pgX9@Xw*}q@J?&b`+c=AiE z(Ne?WkV{}Zpx!AvH-G|1e&O5WZcYqZ#<>RbZANNozt4wgbV0crE6~K+_~on4Od z@m8naI!qO#mWdNQWtr@SIIQYAkJ3gUbt5{h2gn^TQpS4I;Res~K`~p$NQpy-)#`@H zmZ+DP_;_rujDFF}_jL?on@|MQS~`$1y#A}zuA*M&W<6{{uUG0=vq7^*vrR`foLyL6 zf}JXV0apKzW#8s30xo3pNK~N-AYdOg`>^~m0U=BOXahs%J{Ph+-#uIWEma!V=~l9S z;CGB0?W4O_&js>=h{Kebf~`B{>G@_@^kS0}#I? za6b!yM{S+K1%KZ^BsrP1cZB$5^$#Mug&(p*NM_a2Od_3}p5aCwwhxI%z8fpQ#_!H)m%ZjDT9_p6Pd4}C5*3JZ zFlD$=BQBDckB1jzk-S2aJO0^g7+V64im5lVGayZCCrs2+_BU46A`C0I^oSUd>$1 zmBj1UPDIOH8Ed#v zAVFyO0NkB3T~XwyEMQ~JO5F0>fw|;hW7?K;QWF05HKM@}4Y-`*KPgNW8d_Xn4^tqM z%o8bYC)+Qlo)o(nHMGJ6pCvZx`S^e zeF(_U$)F;Dph7GjWdOpZUHF+={t;+PVx>UPL zlC9dv`TLW3GR_?-i&UD^ru}BaC+&sqIUNw z=EAx*^dIK&4 zpt|x8o2kyIyzrb3X~fH0+G4@OqG41ig}`L6qrkc8$ZCDxJfD06?$nC!vENyjpZ)>t zUHFOgt?BIb>X3``5)KLwFIVz6CS-OE^S}7XkOWk?W~QgR#Oa1U9(a+mKQm%u5vd## z0C5(eM7j~O>5Iblu5RbVZChiO;Z+)DV#lomv}F=(s0%Paqt2_S)>d)4r;kENbYNaY z?OHL!xO%=;z0}K~k4RMVO*xXIfz=>(46JMHjQy2d@n?pOYJ6LjBjQ5wBuN8R$F?Fi`6zkllK)GaTO;)Evk)*M8PUR41Vc@{UR%w0|mrtQxoK3|daE-p?xjOGLdXoMnt^vL}xTij)v(8|+4 zC0kGabo7fu8>fWdZ|BUE-s@i13}3lGz#>_RalFj;^w)zN@taXOofEQX@T^Rb!3VPG z*^Goq7D=C(w1?XEv{sH+2IjsEf6l9$(Nwq^bWYEX>gGlRYGIoj^e3Ec`w8P=Z2iuX z=Gx%ZUTv~lm1H*qqc6i#?qTm&k*cWT&L=rc%=TWP+$jR+U~T zYoA9xQ2|=*K0?q>kUB+yKHTvd4wuE0z`@^3o1>3KTKL}#_wuHtIi1c8zdGD|5wZ&s zn*7mq|FZH?YAr+ZCX=?IlibKgEq9lz$ob`Z@yFekSv~`_-!xMrpX7ffq`Mnoe_0G5 zmWf|b@3JpZh;Y++Z!fo3!>jK+GM$w~ycgM08_3!b?c){lW-pJ^3yTkkm8uCMcR5!7 zx$K}S4-JF$eSx^Pu=~R1&;+_D&8toEK_wnqV}fZftk`q%~Qv#DG~`Z3u5O8rnVZ`8Az?dF5n3+uYI;5}rj0AMzt z=P(<=hF$)yu9I&(GQwH5f5$sDkCl1X@hwpRDg+3QL=U3I%D|#W6QRo|ElBo{>T~M; zPd)e8$lR`WkG%tgQJPa(x>RK2Nh>yjWbR794h)6lB9L>2Sp;X8Zqr6xA1PXpwO51t z7x0)N_SEaR5+Pggg>>m`lR`}?YaYAYDlH=sRkG#ce&a;S+3n_n?_9J_bEy(E_lwb! zVxj+3U;3_+fQ#=b=_n$r1-@vah;PHG&J2v;!|I+%P?8N9fi zMljM-7Y2*9c{Ym)*k;ZFFFe3Qa!FrlZn{G!21PN+wC8PgNP>Z4>-9e&nVX~TBUS)} zgMhAkA>9u3PyAqI33A0QTLE;6U=Ckkp_m$pkT9V(4)HBfPn9luWC4viRAWI61~@^S+|GEVevc4an+oWDoIT3gQojp8p}lJe&1Ka zyRcgCVH0O7P9=9g2TN+fBFaF`6^Jmz^$A8g}irAPPh&b@{&3%{bsS_w3 zy0iM^pPX$yTsVoHIkkONu=uI>uKoBg;MH=LsTc@$vuDZ4*E*s!jcspGBb?%DCAc{T}JDc)Igo?c9rAª`-^Lz2dZh4E-$w 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 Ee^!rzPyhe` literal 184240 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%0DQl6sZFj=7l1V3%~8!- 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≧FfkU1W-P}lApsQ+e5Wsi* zQHw<_j6LMRkJdSlE~YdlTgLxH$LeBr(@Z?;QRXhHi3U<` zj(-}D1q`%-(evK-3`6{syVD+u!sbm}+w720Y*u&7~ zPovtv+8e>&r{Ni$dllJBC!Hz^*{N{srLHt>qQilhd;mc{RKe>hPBbje^uD2*<`eH@Sjv0IU``mg@vr_E? z$UQ1uk9K>cJ}zZ1Wkh89k%9flansQmfx61Wwr9*5&K6{g-dpZ!mD;V-BzZW)p(am# zxnTz`Qp7Jwy0ilY7Dyj{w$wQ|jP(*rPaTEeoW_Qp^Ag}{7I9^R*$y+GWIi2klja> z@sY_5+le9tc8txXhPy+3GG`wrHw>pB`Fvi3pKWz^9!x!$UFv2@wbKQOZusx3+?%7ct#b$h(tn_Y|LPgo`v_I)KHnqWRz{AEKD|J+`f#YBEFSxMk0 z6UOC9Y|Qui(Zr`ju4dJ?Q8J_=KRE0g)e1Bow9esD05s(QA%3*L3v(`?;erBK8Yg)70oy8Yw zB4De?JSGJ!v`GrjfAhQW00*?RL#~x69n)VSFCPDC>ZDIcuH7`-Y-x{Jotmc=HuTs% zmSt)g>kE(l?@yjQZ(YHCJ}$($9!csmX5(tj*5FTef{{?&3z6M`#pkrTrl#kJ9j&z!<9kKMW1Qd0bP8+4yK z=raY>FuADD23`vLwg2&Y-=Z7wTvly)=7@NBDcoNWli@x}ANr$T z!Tfvr0;s?>v7K0h2)oe0#l76(MXTp(#?vzVY#9Q+C05b_Ve4 zT0SpR-q&p&G55-yYt6Bsf>kBGd>(*`JYtdS!?-siAZ?>>^^mF_LHzvjVnd~W6A+0% zuN{_W=i)mZ=ng3)1Ku_r153;XsU6da(+@Y5maa-2mpxx{J?65h!uRd3^rhK6UWUV* z)UMeM&D5N_i$(e;?T)D)G+E*xb?pL(p?7=TTy#^Vyp#4vBVgZ(W4{F4;2uo#XwlsY zJ9P9uB9bI&wEfEYh{|i_tI8JbU^*&hjni=LxIfQIoNtlIU*pIR<`K}3yWpx!O-(ShW{HC6%JW&Veloa>SDPqV}P-KDD-Dsip{sF+Nx z+shzfPF)f6zcz_!jUvB{b)0}5{D+51N?ca2Qm|_8d%a{2>Bc37>ZvZ3jCFkmjDdd* zB3g_S>I0K%uf(>99fnjZr$w+34odtLYASx@Ym7osJ7gqG6!31Dx>Cjphqi+L7c$%z zAMu}K)KB@^(&-x-o=KF+_G#(=8u&Y^1-x((g-k+Pp9SWsVHP~SvMToI{* z$5nJ^3dL(ONfIBoIzSkAH#!`qf{n>y_DV?{GWscuJL=~D>InZS z{u*?309ER;Bb>%w+Daop1NaJ+wG|3n6K+5WKdy`hW`Y)aJbFPL|2GK$`VblE4J=HV zw*Yf;v-gF|Hx7wLis>^zD$HC$e}%abX=k?0(>ICH4k59wOg`XaFDF|$nG>-?uo*KmwnjmG@a4>+9 zF3}chu4Cz*N_#gwgtHdW^dNWmK9gj}Z@mY@&P0^-K}RlNmo1hMQkh90c zuHWHl-wuY|m=2qA-@E;T31eulueo-7L=$$m;JWp#*-cQQRsJfN&iH0|Ci-6_ejoJ^ zRePl(4x$NQAmClP!pb#?!beVDAww?TTJVMFB`N=> zDeiO6B0$AFVIN3l3=LaNiWmAufWwCUqH!sllSqVo4A?;P=HHhgj#}d z>@K^$1#rlcM3!$guYGOLHm{`ozIl)(w^p)r%I@vDN$>O?_ue};du)cRB{vmokX$m6 zk}@^MF6d{nH7&H%TD*ovKyI}h3-v{R8ryr!L0+60{<$DU*=bKqZ<4HoQV<&re2Nt) z3H2!0w7ceG1a4>)xMGN@<(*E#y|baytYd@PSz|nOjQ>AwqB(3dXAbO}A%Xc7i@`Uz z%-#yT_t1Ho%~ASQ={xcFRd1O-iA;{@W>o|?{sj5MWtzaLcsxazuQD6BTPXzSd)-|) zKi#6;nT6aTs$JsqwEvnH1Ft7SPC0_qt143}ou83F=29q#~3r72#d# zsW48jyNfTL<0Dwea4{XkUVz8f2f2)D9d-mfa^}coU^-Jg%&>khg^5>f8MhaDnDNuKr zbJ`TwX>kV(c5k|R$GR5d4#blcx|N3Bz#xC9$wzg6c1Z1H1!k=}w(|odm5WZ_)V@D3 zJ-ZR)|8G$GAA6>pW&}H2#ye9ZoCF(9LjW}r!k>cEvbFcuNO-L2DjE}8=u8{Snku3n z{L z`eO4nK4KR?Txw^6_w+5!qM+NVZYT(OX-bLd#P1w9vIXpN>nP1yo>M zVM`;RcwISG3#{7<5rz|u$J^|ae;&XgbzT8#`oBVV<(mFAar{57j~jV>Dew$fXP^Y;FzamkNCi6mwWmtVqTlCrQe&$C4HuU9g{O+ z@KxkA`nm&?HjyN(%=zg`mODsy(9#M=?-@2PE~dfU2?#J?P>xICHF%y0^b7g=G%G`) z1Fm_RZUw?#gJUvxq1lMe>TE)EO7SW2qI&v59{rgVo|q+QtCwZ@baCo76Lc_XHlj?L zYUZ0kh_)Dmbl*7$2SWxC>0b%hl(;+$njprzWX)1hVc=!m1RRVfh(-t-o!r+IWe7U~OtCofH^k#$oGDux_2XQwS9C|I+O@)^=yx}T53}gX zbza<|D@rb%!1GHa&=7{{n8R<4Zq4V1SCPX$Qv_&7wXAX!r!-3mJn8D9@7F|_#okl=;NJ#$Nk|Gv6ox}d}$S!nE?K4p=c%jCu9f)vi@u$ zFFn+jW5gv7?THkiWSdk3s1S3}SI&*dTJ~giQm-)L=u~s!Cz1Z6vitg7w}KG^VfBj@ z@gOM+QekW~AfUYoI*?UK2>&ny@iA!Iytx-J)1QoDsXSrS7(5HSZ~jgHW+9C=7|?HT zIS-b4f~?u~&F~O3H&}8=f7^S|wKs)qUMZ&1-homhKYx9o3`jYkZtc%-0h1zRR5orx zPbL?6g67qz@Z%8>XHkDF_K`CeKfX}~9oaYA*VKYRyOrKgQAID6t}JKh+kj%ZJ#09b z&e64Qw1$nLIx0-D0WEXHyVfrn7Nhif4mu%eTOf=38=>nCgPlJ&5?>hV6}#&%z@ z)c*_L{Nr^E-X{)-V#k1BQ5vg%haRqX3hiSTHxDZdOyUfrfGu$Acx#n?BmCHiO!owS z29to*OTc4N{_{N{B5JH_)0)<2T<^UA~4tieE!cZ}xlo)yAO&qIfj+BL*G{R+$PD6=8y4HF@jJ{#&*qG3) z*0sf8sXxHt5l^|pbc*t62ZiHT*2tSir5E%q$RtANeyXl1-2{VY`BZeeoiI|Y>9 zpvL92gb2a&LvTnzNj_k9@%3nj1FKSg5jHqf(!Gg80r-{Z#7nH(+GOWW-s zXP??xQl;&|Mke(0bo*4mbGzowrPONiXj>=X8OLDImkEUjkg)y`k{lz2*MBD2)16?6 z6Vnp9ZjJBa+*H!n8Fd~w?@i?Md=9<eF05uqc)(XP{(KhZPeTQ~@IN+;zPW_Tx#0A2PcKgaprIn+|cgP{oyY`jL^ zR1qV(1+Z}u8uQ@qvQ+uNDNw&-;q_W@d^E*vudGTXpkI^ux~MYm7q5R=YUnD|HUz;& zllnSxj=KEhxK^1ptI6Cf|P7uGSYYuS4{5@DOxBTj_7Fb{uq28hTmKO`AxM;=& z0ef_b%%cH)Zl9DaHNe>_8X|kgw++<*T<4t9%zTO6Jm66Sr9GXj_BjPWZK#TS2E9sA z6xXPrh&eqY(xwl7&4-14;-^be3Sz{Aj>=g%!e#mphfm(sr(DH$K+aeQ5wDYHBDz(*R#ROd9k68 zx6YzvlXN(<+pHB=baQCP(`nT4n`7x^>7>EFI-ZC4N9qBHzUM^sU_EN6Z|7c=$(Wci zFjZcx^cF(BF6_OJyu&%sDQ56Vd~cY$dhz<%!xPbC!bOf6J+AjdMQ22=9xV=9Xr*4g zX*iPsKRuMI1tku=+#m*wPyleNkI4|;5B1rHLL~tg!X-xsjeW<_?tMHPIm^Xl2lQQa z)h?E|YYR)VDRX+<7N06UbXq@5D6tGhVgU{M;H9vHz~Xi<3*S`_FzJq;eh8yex~xJj z<;_mNLEj~Rl^uR=3$)2*SAYLiC`Gio(Pf4ffn~F0V(WAQR_guu&98J*~-T6 z89)7aQ7ig5c&okZua3DexUf|7tUZM>X2^0VLW+)R(Hklrn<^9db98yJo^$afRl7N0Y6ee=#`J0Dh42fz>x#Dl;xk|f3=|u;1T#(DVt+7R84&#P+XsZQ*0d!JLE^JL1 zew{vF6&s+RbHaQk)&Gj5MJ<_W*%q^W?0zoI@MW)r9^`ztkz(+2itFw@nS`e`nw+De zVU5U-V7W(dNHK~XS$O%29UGmR258(NC#!ApBm;Kq0+Gf0XJNj`P+vdt-`5+GfKv8~ znHY{<`XtuxeT?@PM@LB6`rC#7gJ|?EEREd}PAX`MSl18shAEsl=e0Kk)SX5HOHW?4 z$jj$`MFXmK9g0tjGI~|V=WMNaRy;4I1)R%RH+un{c_DPktr{0|49ffTOq*hsw#HL2 zhAf$!jZpd{7c_noZ;%&+c5^ZQ~r{|J|`=xxw-iEfL@>+`?im9$pf`Mer=x zv?(H)@1nsP$U-|WHZL_IrC3n{bP*nOj>t<*yYuULm5|#z;1iWg{(K!fM4UmUNxB=>&aM4LvCe#K*Q*S685b~$aj{WrK*`;w%>*3FhV zt!u0aSOI@VBfwp$GdXJwe7aLIcId*z%S5S`_{eRfCcxtj0HX9kA=7P>WaZXt9&B_8 zT=l?D9KWJ7q>XW{%xO;kP=vb@Wq)2Mg@2MOm3h@VeU&IhtEZ|S=qzU7^EFhhYA9qs zRSX@FdQA?jIy>|yihiqGGx#=nU9*kh&8Dj`zS64d6fvc;LQ+Hs z4gym~pvNFIQ(E1YyoznrT&%OYaYsHpIPr{gI|X=Z@nR zLJPY;sIPE<)caU0nXZJAgVZjTteGpdd~v`9=0d?5fVnS#W58u+!{`7juJ`p?*7Y6# z#xxkGOW?SAjjZ2zxWKsaIT=KAz(GdH#DUa}PJGr4%mpVbjYqXt9=;-{EL`Z}m_(f3 zYmxQ*iL;O#4vXRoD(UoLY@O}DO9{8$5iK1UZ)rF~u z%(p^DpLWeswMvJ~2PRKb`H!?TO?KQV;>hD0n%mk+ZK2Y8U}aRa)Tz8x03e(ubA&{l z5#0KS?+<0=lINE_;gwsRh|5`cP(--kYjM51cl41d`2AtzL>(2;-JcWheSjBzz~ZA4 zXTbq}bJrNT|D7EZ#@#+Lqe7LbFh&Zhf1)4pYm5Py*cLACV+<&zWeX{fD+mSr-UGtp zf{aMzoW3tC*UnADUE_$M*1@rwDfg#K-ur=1PdK|CocWq=vCVt7bpAD3UU;%R8K#ow zxPLO;#H8F*zM{P8VWpBVy0}Yb$>&dj)b(cn@kXtGDaWfJ{P>q6!ow_Xz3qTtI$H9# zZu`TkyNHXQ1{u;vC-S1@D}|SD+vRA_q>@_KN&fst3!qx@A^&@XAK9lTdp0!V_3OdE zlY{PW42`E7iyJTx2q-P4+nLznHQPMDx1>wb?lz{@W$>6{KD)%GY1*^B zbQ!@QJ*m1}wzA*4E9^G;5v&m?b$DbUsOlCtYKxmBf-&?*&u>qTgF2)Hn8-%y&ZGe7 z%R`$?U_I(%eE&=F#m6`J4|LzVPYCgUF$rEIAv_j1hiVI<{*L$C+Uf{()R~`*QPgJH@)E8EXYeCTwh&cG>R0%1Z_JK;8X^VMly`Fjzw zJ9HQjD2US);E`Xkzfl^4Xog1VaHe(CFlhPDs&^%P=o4g1h+bB#}a)NnQlhT-0FMbahQM?Si zniOPO5Loyzi$IUJ-oH45cTv5pPi)vd#C!)TWa&R%lFT~m@CbR;9DAUt@oJHy!Ge?MSd